import { JWT_HEADER, XSRF_COOKIE, XSRF_HEADER } from '../consts' import { getCookie } from '../lib/cookies' export type QueryParams = Record export type Payload = BodyInit | Record | null export type BodylessMethod = (url: string, query?: QueryParams) => Promise export type BodyMethod = (url: string, query?: QueryParams, body?: Payload) => Promise export interface Client { get: BodylessMethod put: BodyMethod post: BodyMethod delete: BodylessMethod } /** JWT token received from server and will be send by each request, if it present */ let token: string | undefined export const createFetcher = (site: string, baseUrl: string): Client => { const client = { get: (uri: string, query?: QueryParams): Promise => request('get', uri, query), put: (uri: string, query?: QueryParams, body?: Payload): Promise => request('put', uri, query, body), post: (uri: string, query?: QueryParams, body?: Payload): Promise => request('post', uri, query, body), delete: (uri: string, query?: QueryParams, body?: Payload): Promise => request('delete', uri, query, body), } /** * Fetcher is abstraction on top of fetch * * @method - a string to set http method * @uri – uri to API endpoint * @query - collection of query params. They will be concatenated to URL. `siteId` will be added automatically. * @body - data for sending to the server. If you pass object it will be stringified. If you pass form data it will be sent as is. Content type headers will be added automatically. */ async function request( method: string, uri: string, query: QueryParams = {}, body?: Payload ): Promise { const searchParams = new URLSearchParams({ site, ...query }) searchParams.sort() const url = `${baseUrl}${uri}?${searchParams.toString()}` const headers = new Headers() const params: RequestInit = { method, headers } // Save token in memory and pass it into headers in case if storing cookies is disabled if (token) { headers.set(JWT_HEADER, token) } // An HTTP header cannot be empty. // Although some webservers allow this (nginx, Apache), others answer 400 Bad Request (lighttpd). const xsrfToken = getCookie(XSRF_COOKIE) if (typeof xsrfToken === 'string') { headers.set(XSRF_HEADER, xsrfToken) } if (typeof body === 'object' && body !== null && !(body instanceof FormData)) { headers.set('Content-Type', 'application/json') params.body = JSON.stringify(body) } else { params.body = body } return fetch(url, params).then((res) => { if ([401, 403].includes(res.status)) { token = undefined return Promise.reject('Unauthorized') } token = res.headers.get(JWT_HEADER) ?? token return res .text() .catch(Object) .then((data: string) => { if (res.status < 200 || res.status > 299) { return Promise.reject(data) } try { return JSON.parse(data) as T } catch (e) { return data as unknown as T } }) }) } return client }