@procore/core-http
Version:
A HTTP Client
192 lines (185 loc) • 5.33 kB
JavaScript
// src/index.ts
import {
isJsonifiable,
isResponseError as isResponseError2,
isValidationError
} from "up-fetch";
// src/authPlugin.ts
import { isResponseError } from "up-fetch";
import { SystemEvents, SystemEventNames } from "@procore/web-sdk-events";
var systemEvents = new SystemEvents("core-http");
function detectAndPublishStepUp(response, request2) {
if (response.status === 401 && response.headers.get("www-authenticate")) {
const eventData = {
type: SystemEventNames.NETWORKING_STEPUP_REQUIRED,
url: request2?.url,
status: response.status,
wwwAuthenticate: response.headers.get("www-authenticate") || "",
timestamp: Date.now()
};
systemEvents.publish(SystemEventNames.NETWORKING_EXCEPTION, eventData);
}
}
var authPlugin = {
onSuccess: (response, request2) => {
if (response instanceof Response) {
detectAndPublishStepUp(response, request2);
}
},
onError: (error, request2) => {
if (isResponseError(error)) {
const response = error.response;
if (response instanceof Response) {
detectAndPublishStepUp(response, request2);
}
}
}
};
// src/create-client.ts
import { SystemEvents as SystemEvents3 } from "@procore/web-sdk-events";
import { up } from "up-fetch";
// src/utils.ts
function collectFunctions(plugins, fnName) {
const functions = [];
for (const plugin of plugins) {
if (plugin[fnName]) {
functions.push(plugin[fnName]);
}
}
return functions;
}
function createFunctionChain(fns) {
return fns.length === 0 ? void 0 : fns.length === 1 ? fns[0] : async (...args) => {
for (const fn of fns) {
await fn(...args);
}
};
}
// src/error-reporter-plugin.ts
import { SystemEventNames as SystemEventNames2 } from "@procore/web-sdk-events";
function normalizeError(error) {
if (error instanceof Error) {
return {
name: error.name,
message: error.message,
stack: error.stack
};
}
return {
name: "UnknownError",
message: String(error),
stack: void 0
};
}
function createErrorReporterPlugin(systemEvents2, errorReportingApiKey) {
return {
onError: (error) => {
if (!errorReportingApiKey) return;
systemEvents2.publish(SystemEventNames2.UNCAUGHT_EXCEPTION, {
errorReportingApiKey,
error: normalizeError(error)
});
}
};
}
// src/create-client.ts
var SERVICE_NAME = "core-http";
function getCSRFToken() {
const token = document.cookie.match("(^|;)\\s*csrf_token\\s*=\\s*([^;]+)");
if (token?.[2] && token[2] !== "") {
return decodeURIComponent(token[2]);
}
return void 0;
}
function resolveDefaultErrorHandling(options, fallback) {
if (options && typeof options === "object" && "defaultErrorHandling" in options) {
const v = options.defaultErrorHandling;
if (typeof v === "boolean") return v;
}
return fallback;
}
function createClient({
// This grabs a reference to window.fetch on each request, rather than hard-coding it at initialization time.
fetchFn = (input, options) => fetch(input, options),
defaults = () => ({}),
plugins = [],
defaultErrorHandling = true,
errorReportingApiKey,
systemEvents: systemEvents2
}) {
const upFetch = up(fetchFn, async (input, fetcherOpts, ctx) => {
const defaultOptions = await defaults(input, fetcherOpts, ctx);
const shouldReportErrors = resolveDefaultErrorHandling(fetcherOpts, defaultErrorHandling) && Boolean(errorReportingApiKey);
const allPlugins = [
defaultOptions,
...plugins,
...shouldReportErrors ? [
createErrorReporterPlugin(
systemEvents2 ?? new SystemEvents3(SERVICE_NAME),
errorReportingApiKey
)
] : []
];
const onError = createFunctionChain(collectFunctions(allPlugins, "onError"));
const onRequest = createFunctionChain(
collectFunctions(allPlugins, "onRequest")
);
const onSuccess = createFunctionChain(
collectFunctions(allPlugins, "onSuccess")
);
const csrfToken = getCSRFToken();
return {
credentials: "same-origin",
mode: "same-origin",
...defaultOptions,
headers: {
...defaultOptions.headers,
...csrfToken ? { "X-CSRF-TOKEN": csrfToken } : {}
},
onError,
onRequest,
onSuccess
};
});
return (input, options) => upFetch(input, options);
}
// src/request.ts
function request(url, requestParams = {}) {
const {
signal,
baseUrl,
errorReportingApiKey = "",
systemEvents: systemEvents2,
...options
} = requestParams;
return createClient({
errorReportingApiKey,
...systemEvents2 && { systemEvents: systemEvents2 },
defaults: () => ({
...baseUrl ? { baseUrl: new URL(baseUrl, document.baseURI).toString() } : {},
// Skips the default parseResponse and reject so that
// the function behaves like default fetch.
parseResponse(response) {
return response;
},
reject: () => false
})
})(url, {
...options,
signal: signal ?? void 0
});
}
function requestJSON(url, requestParams = {}) {
return request(url, requestParams).then(
(response) => response.json()
);
}
export {
authPlugin,
createClient,
isJsonifiable,
isResponseError2 as isResponseError,
isValidationError,
request,
requestJSON
};