bigbuffet-rw/packages/pl-api/lib/request.ts
marcin mikołajczak db19522a54 pl-api: add server directory client
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
2024-08-30 19:28:12 +02:00

137 lines
3.7 KiB
TypeScript

import LinkHeader from 'http-link-header';
import { serialize } from 'object-to-formdata';
import PlApiClient from './client';
import { buildFullPath } from './utils/url';
type Response<T = any> = {
headers: Headers;
ok: boolean;
redirected: boolean;
status: number;
statusText: string;
type: ResponseType;
url: string;
data: string;
json: T;
};
/**
Parse Link headers, mostly for pagination.
@param {object} response - Fetch API response object
@returns {object} Link object
*/
const getLinks = (response: Pick<Response, 'headers'>): LinkHeader =>
new LinkHeader(response.headers?.get('link') || undefined);
const getNextLink = (response: Pick<Response, 'headers'>): string | null =>
getLinks(response).refs.find(link => link.rel.toLocaleLowerCase() === 'next')?.uri || null;
const getPrevLink = (response: Pick<Response, 'headers'>): string | null =>
getLinks(response).refs.find(link => link.rel.toLocaleLowerCase() === 'prev')?.uri || null;
interface RequestBody<Params = Record<string, any>> {
method?: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
body?: any;
params?: Params;
onUploadProgress?: (e: ProgressEvent) => void;
signal?: AbortSignal;
contentType?: string;
idempotencyKey?: string;
}
type RequestMeta = Pick<RequestBody, 'idempotencyKey' | 'onUploadProgress' | 'signal'>;
function request<T = any>(this: Pick<PlApiClient, 'accessToken' | 'baseURL'>, input: URL | RequestInfo, {
body,
method = body ? 'POST' : 'GET',
params,
onUploadProgress,
signal,
contentType = 'application/json',
idempotencyKey,
}: RequestBody = {}) {
const fullPath = buildFullPath(input.toString(), this.baseURL, params);
const headers = new Headers();
if (this.accessToken) headers.set('Authorization', `Bearer ${this.accessToken}`);
if (contentType !== '') headers.set('Content-Type', contentType);
if (idempotencyKey) headers.set('Idempotency-Key', contentType);
body = body && contentType === '' ? serialize(body, { indices: true }) : JSON.stringify(body);
// Fetch API doesn't report upload progress, use XHR
if (onUploadProgress) {
return new Promise<Response<T>>((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.addEventListener('progress', onUploadProgress!);
xhr.addEventListener('loadend', () => {
const data = xhr.response;
let json: T = undefined!;
if (xhr.getResponseHeader('content-type')?.includes('application/json')) {
try {
json = JSON.parse(data);
} catch (e) {
//
}
}
if (xhr.status >= 400) reject({ response: {
status: xhr.status,
statusText: xhr.statusText,
url: xhr.responseURL,
data,
json,
} });
resolve({ status: xhr.status, data, json } as any as Response<T>);
});
xhr.open(method, fullPath, true);
headers.forEach((value, key) => xhr.setRequestHeader(key, value));
xhr.responseType = 'text';
xhr.send(body as FormData);
});
}
return fetch(fullPath, {
method,
headers,
body,
signal,
}).then(async (res) => {
const data = await res.text();
let json: T = undefined!;
if (res.headers.get('content-type')?.includes('application/json')) {
try {
json = JSON.parse(data);
} catch (e) {
//
}
}
const { headers, ok, redirected, status, statusText, type, url } = res;
const response = { headers, ok, redirected, status, statusText, type, url, data, json };
if (!ok) {
throw { response };
}
return response as any as Response<T>;
});
}
export {
type Response,
type RequestBody,
type RequestMeta,
getLinks,
getNextLink,
getPrevLink,
request,
request as default,
};