@kontent-ai/core-sdk
Version:
Core package with shared / common functionality for Kontent.ai SDKs
247 lines • 10.5 kB
JavaScript
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