48ede1420d
Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz> Reviewed-on: http://git.tnsu.kz/gorbenko.pavel/tnsu-approval-route/pulls/12
119 lines
4.0 KiB
TypeScript
119 lines
4.0 KiB
TypeScript
import { AadHttpClient, HttpClientResponse } from '@microsoft/sp-http';
|
|
|
|
// Ответ бэкенда всегда в конверте { data: T } на успехе (Api/Common/ApiResponse.cs)
|
|
// и { messages: string[] } на ошибке (ApiError). 204 No Content возвращается без тела.
|
|
interface IApiEnvelope<T> {
|
|
data?: T;
|
|
messages?: string[];
|
|
}
|
|
|
|
export class ApiClientError extends Error {
|
|
public readonly status: number;
|
|
public readonly messages: string[];
|
|
|
|
constructor(status: number, messages: string[]) {
|
|
super(messages.join('; ') || `HTTP ${status}`);
|
|
this.status = status;
|
|
this.messages = messages;
|
|
}
|
|
}
|
|
|
|
export type QueryParams = Record<string, string | number | boolean | undefined>;
|
|
|
|
export interface IHttpClient {
|
|
get<T>(path: string, query?: QueryParams): Promise<T>;
|
|
post<T>(path: string, body?: unknown): Promise<T>;
|
|
put<T>(path: string, body?: unknown): Promise<T>;
|
|
patch<T>(path: string, body?: unknown): Promise<T>;
|
|
delete(path: string): Promise<void>;
|
|
}
|
|
|
|
function buildUrl(baseUrl: string, path: string, query?: QueryParams): string {
|
|
const normalizedBase = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
|
|
const url = new URL(path.replace(/^\//, ''), normalizedBase);
|
|
if (query) {
|
|
Object.keys(query).forEach(key => {
|
|
const value = query[key];
|
|
if (value !== undefined && value !== '') {
|
|
url.searchParams.set(key, String(value));
|
|
}
|
|
});
|
|
}
|
|
return url.toString();
|
|
}
|
|
|
|
interface IMinimalResponse {
|
|
ok: boolean;
|
|
status: number;
|
|
text(): Promise<string>;
|
|
}
|
|
|
|
async function parseResponse<T>(response: IMinimalResponse): Promise<T> {
|
|
if (response.status === 204) {
|
|
return undefined as T;
|
|
}
|
|
|
|
const text = await response.text();
|
|
let json: IApiEnvelope<T> | undefined;
|
|
try {
|
|
json = text ? JSON.parse(text) : undefined;
|
|
} catch {
|
|
// Ответ пришёл не в конверте ApiResponse/ApiError (например, "сырая" страница
|
|
// необработанного исключения ASP.NET Core или ошибка от прокси перед API).
|
|
if (!response.ok) {
|
|
throw new ApiClientError(response.status, [text || `HTTP ${response.status}`]);
|
|
}
|
|
throw new ApiClientError(response.status, ['Сервер вернул не-JSON ответ.']);
|
|
}
|
|
|
|
if (!response.ok) {
|
|
const messages = json?.messages ?? [`HTTP ${response.status}`];
|
|
throw new ApiClientError(response.status, messages);
|
|
}
|
|
|
|
return (json?.data as T) ?? (undefined as T);
|
|
}
|
|
|
|
/**
|
|
* Прод/staging: вызовы через AAD-защищённое SPFx-подключение (AadHttpClient).
|
|
* Требует webApiPermissionRequests в package-solution.json и админского
|
|
* согласия в SharePoint admin center → Advanced → API access.
|
|
*/
|
|
export class AadApiHttpClient implements IHttpClient {
|
|
constructor(private readonly client: AadHttpClient, private readonly baseUrl: string) {}
|
|
|
|
public get<T>(path: string, query?: QueryParams): Promise<T> {
|
|
const url = buildUrl(this.baseUrl, path, query);
|
|
return this.client
|
|
.get(url, AadHttpClient.configurations.v1)
|
|
.then((r: HttpClientResponse) => parseResponse<T>(r));
|
|
}
|
|
|
|
public post<T>(path: string, body?: unknown): Promise<T> {
|
|
return this.send<T>('POST', path, body);
|
|
}
|
|
|
|
public put<T>(path: string, body?: unknown): Promise<T> {
|
|
return this.send<T>('PUT', path, body);
|
|
}
|
|
|
|
public patch<T>(path: string, body?: unknown): Promise<T> {
|
|
return this.send<T>('PATCH', path, body);
|
|
}
|
|
|
|
public async delete(path: string): Promise<void> {
|
|
await this.send<void>('DELETE', path);
|
|
}
|
|
|
|
private send<T>(method: string, path: string, body?: unknown): Promise<T> {
|
|
const url = buildUrl(this.baseUrl, path);
|
|
return this.client
|
|
.fetch(url, AadHttpClient.configurations.v1, {
|
|
method,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: body !== undefined ? JSON.stringify(body) : undefined
|
|
})
|
|
.then((r: HttpClientResponse) => parseResponse<T>(r));
|
|
}
|
|
}
|