UNPKG

@baqhub/sdk

Version:

The official JavaScript SDK for the BAQ federated app platform.

232 lines (231 loc) 6.44 kB
import memoize from "lodash/memoize.js"; import pickBy from "lodash/pickBy.js"; import snakeCase from "lodash/snakeCase.js"; import { Constants } from "../constants.js"; import { AbortedError } from "../helpers/async.js"; import { CustomError } from "../helpers/customError.js"; import { Str } from "../helpers/string.js"; import { isDefined } from "../helpers/type.js"; import { Uuid } from "../helpers/uuid.js"; import { HttpMethod } from "../model/core/httpMethod.js"; import { fetchEventSource } from "./eventSource.js"; const getClientId = memoize(() => Uuid.new()); let api = { fetch: (...args) => fetch(...args), eventSourceFetch: (...args) => fetch(...args), }; export function setHttpApi(newApi) { api = newApi; } export class RequestError extends CustomError { options; status; headers; constructor(options, status, headers) { super("Request error."); this.options = options; this.status = status; this.headers = headers; } } export class RequestFailedError extends CustomError { baseError; constructor(baseError) { super("Request failed."); this.baseError = baseError; } } function isError(error, statuses) { if (error instanceof RequestFailedError && !statuses) { return true; } if (!(error instanceof RequestError)) { return false; } if (!statuses) { return true; } return statuses.includes(error.status); } async function httpHead(url, options) { const response = await sendAsync({ ...options, method: HttpMethod.HEAD, url, }); return response.headers; } async function httpGet(url, options) { const response = await sendAsync({ ...options, method: HttpMethod.GET, url, }); const json = await response.json(); return [response.headers, json]; } async function httpDownload(url, options) { const response = await sendAsync({ ...options, headers: { Accept: "*/*", ...options?.headers, }, method: HttpMethod.GET, url, }); const blob = await response.blob(); return [response.headers, blob]; } async function httpPost(body, url, options = {}) { const headers = { "Content-Type": "application/json; charset=utf-8", ...options.headers, }; const response = await sendAsync({ ...options, headers, method: HttpMethod.POST, url, body, }); const json = await response.json(); return [response.headers, json]; } async function httpPut(body, url, options = {}) { const headers = { "Content-Type": "application/json; charset=utf-8", ...options.headers, }; const response = await sendAsync({ ...options, headers, method: HttpMethod.PUT, url, body, }); const json = await response.json(); return [response.headers, json]; } async function httpPatch(body, url, options = {}) { const headers = { "Content-Type": "application/json; charset=utf-8", ...options.headers, }; const response = await sendAsync({ ...options, headers, method: HttpMethod.PATCH, url, body, }); const json = await response.json(); return [response.headers, json]; } async function httpDelete(url, options) { return sendAsync({ ...options, method: HttpMethod.DELETE, url, }); } async function httpDeleteBody(body, url, options = {}) { const headers = { "Content-Type": "application/json; charset=utf-8", ...options.headers, }; const response = await sendAsync({ ...options, headers, method: HttpMethod.DELETE, url, body, }); const json = await response.json(); return [response.headers, json]; } function buildUrl(url, query) { const queryArray = Object.entries(query || {}) .map(([key, value]) => value ? [snakeCase(key), value] : undefined) .filter(isDefined); return url + Str.buildQuery(queryArray); } function conformBody(body) { if (body instanceof File) { return body; } if (body instanceof Blob) { return body; } if (body) { return JSON.stringify(body); } return null; } async function sendAsync(options) { const { method, url, body } = options; const fullUrl = buildUrl(url, options.query); const initialHeaders = { Accept: "application/json", ...options.headers, [Constants.clientIdHeader]: getClientId(), }; const headers = { ...initialHeaders, Authorization: options.authorizationBuilder?.(method, fullUrl, initialHeaders), }; try { const response = await api.fetch(fullUrl, { method, headers: pickBy(headers, isDefined), body: conformBody(body), signal: options.signal, }); if (!response.ok) { throw new RequestError(options, response.status, response.headers); } return response; } catch (error) { // If this request was aborted, throw a custom error. if (options.signal?.aborted) { throw new AbortedError(); } if (error instanceof RequestError) { throw error; } // Otherwise, bubble up. throw new RequestFailedError(error); } } function httpEventSource(onMessage, url, options = {}) { const buildHeaders = lastEventId => { const initialHeaders = pickBy({ ...options.headers, [Constants.clientIdHeader]: getClientId(), [Constants.lastEventIdHeader]: lastEventId, }, isDefined); return pickBy({ ...initialHeaders, Authorization: options.authorizationBuilder?.(HttpMethod.GET, url, initialHeaders), }, isDefined); }; fetchEventSource(api.eventSourceFetch, buildUrl(url, options.query), { buildHeaders, onMessage: onMessage, openWhenHidden: true, signal: options.signal, }); } export const Http = { isError, head: httpHead, get: httpGet, post: httpPost, put: httpPut, patch: httpPatch, delete: httpDelete, deleteBody: httpDeleteBody, download: httpDownload, eventSource: httpEventSource, };