UNPKG

@kontent-ai/core-sdk

Version:

Core package with shared / common functionality for Kontent.ai SDKs

247 lines 10.5 kB
import { sdkInfo } from "../sdk-info.js"; import { isNotUndefined } from "../utils/core.utils.js"; import { getErrorMessage } from "../utils/error.utils.js"; import { getSdkIdHeader } from "../utils/header.utils.js"; import { runWithRetryAsync, toRequiredRetryStrategyOptions } from "../utils/retry.utils.js"; import { tryCatch, tryCatchAsync } from "../utils/try.utils.js"; import { getDefaultHttpAdapter } from "./http.adapter.js"; export function getDefaultHttpService(config) { const withUnknownErrorHandlingAsync = async ({ url, funcAsync, }) => { const { success, data, error } = await tryCatchAsync(funcAsync); if (success) { return data; } return { success: false, error: { reason: "unknown", message: "Unknown error. See the error object for more details.", url: url, originalError: error, }, }; }; const resolveRequestAsync = async ({ options, resolveDataAsync, }) => { return await withUnknownErrorHandlingAsync({ url: options.url, funcAsync: async () => { const adapter = config?.adapter ?? getDefaultHttpAdapter(); const getCombinedRequestHeaders = () => { return getRequestHeaders([...(config?.requestHeaders ?? []), ...(options.requestHeaders ?? [])], options.body); }; const getRequestBody = () => { if (options.body === null) { return { success: true, data: null, }; } if (options.body instanceof Blob) { return { success: true, data: options.body, }; } const { success, data: parsedBody, error } = tryCatch(() => JSON.stringify(options.body)); if (!success) { return { success: false, error: { message: "Failed to stringify body of request.", url: options.url, reason: "invalidBody", originalError: error, }, }; } return { success: true, data: parsedBody, }; }; const getUrl = () => { const { success, data: parsedUrl, error } = tryCatch(() => new URL(options.url)); if (!success) { return { success: false, error: { message: `Failed to parse url '${options.url}'.`, url: options.url, reason: "invalidUrl", originalError: error, }, }; } return { success: true, data: parsedUrl, }; }; const requestHeaders = getCombinedRequestHeaders(); const retryStrategyOptions = toRequiredRetryStrategyOptions(config?.retryStrategy); const withRetryAsync = async (funcAsync) => { return await runWithRetryAsync({ url: options.url, retryStrategyOptions, retryAttempt: 0, requestHeaders, method: options.method, funcAsync: async () => { return await funcAsync(); }, }); }; const { success: urlParsedSuccess, data: parsedUrl, error: urlError } = getUrl(); if (!urlParsedSuccess) { return { success: false, error: urlError, }; } const { success: requestBodyParsedSuccess, data: requestBody, error: requestBodyError } = getRequestBody(); if (!requestBodyParsedSuccess) { return { success: false, error: requestBodyError, }; } const getResponseAsync = async () => { return await adapter.requestAsync({ url: parsedUrl.toString(), method: options.method, requestHeaders, body: requestBody, }); }; const getErrorForInvalidResponseAsync = async (response) => { const sharedErrorData = { message: getErrorMessage({ url: options.url, adapterResponse: response, method: options.method, }), url: options.url, }; if (response.status === 404) { const error = { ...sharedErrorData, reason: "notFound", isValidResponse: response.isValidResponse, responseHeaders: response.responseHeaders, status: 404, statusText: response.statusText, kontentErrorResponse: await getKontentErrorDataAsync(response), }; return error; } const error = { ...sharedErrorData, reason: "invalidResponse", isValidResponse: response.isValidResponse, responseHeaders: response.responseHeaders, status: response.status, statusText: response.statusText, kontentErrorResponse: await getKontentErrorDataAsync(response), }; return error; }; const resolveResponseAsync = async (response) => { if (!response.isValidResponse) { return { success: false, error: await getErrorForInvalidResponseAsync(response), }; } return { success: true, response: { data: await resolveDataAsync(response), body: options.body, method: options.method, adapterResponse: { isValidResponse: response.isValidResponse, responseHeaders: response.responseHeaders, status: response.status, statusText: response.statusText, }, requestHeaders: requestHeaders, }, }; }; return await withRetryAsync(async () => await resolveResponseAsync(await getResponseAsync())); }, }); }; return { requestAsync: async (options) => { return await resolveRequestAsync({ options, resolveDataAsync: async (response) => { return (await response.toJsonAsync()); }, }); }, downloadFileAsync: async (options) => { return await resolveRequestAsync({ options: { ...options, method: "GET", body: null, }, resolveDataAsync: async (response) => { return await response.toBlobAsync(); }, }); }, uploadFileAsync: async (options) => { return await resolveRequestAsync({ options, resolveDataAsync: async (response) => { return (await response.toJsonAsync()); }, }); }, }; } async function getKontentErrorDataAsync(response) { if (response.responseHeaders .find((header) => header.name.toLowerCase() === "Content-Type".toLowerCase()) ?.value.toLowerCase() .includes("application/json")) { const json = (await response.toJsonAsync()); // We check the existence of 'message' property which should always be set when the error is a Kontent API error if (!json.message) { return undefined; } return { ...json, message: json.message, }; } return undefined; } function getRequestHeaders(headers, body) { const existingContentTypeHeader = headers?.find((header) => header.name.toLowerCase() === "Content-Type".toLowerCase()); const existingSdkVersionHeader = headers?.find((header) => header.name.toLowerCase() === "X-KC-SDKID".toLowerCase()); const contentTypeHeader = existingContentTypeHeader ? undefined : { name: "Content-Type", value: body instanceof Blob ? body.type : "application/json", }; const sdkVersionHeader = existingSdkVersionHeader ? undefined : getSdkIdHeader({ host: sdkInfo.host, name: sdkInfo.name, version: sdkInfo.version, }); const contentLengthHeader = body instanceof Blob ? { name: "Content-Length", value: body.size.toString(), } : undefined; return [...(headers ?? []), contentTypeHeader, contentLengthHeader, sdkVersionHeader].filter(isNotUndefined); } //# sourceMappingURL=http.service.js.map