UNPKG

@commercetools/ts-client

Version:
1,623 lines (1,535 loc) 52 kB
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function byteLength(body) { if (body && typeof body === 'string') { return new TextEncoder().encode(body).length.toString(); } if (body && body instanceof Uint8Array) { return body.byteLength.toString(); } if (body && typeof body === 'object') { return new TextEncoder().encode(JSON.stringify(body)).length.toString(); } return '0'; } const HEADERS_CONTENT_TYPES = ['application/json', 'application/graphql']; const CONCURRENCT_REQUEST = 20; const CTP_API_URL = 'https://api.europe-west1.gcp.commercetools.com'; const CTP_AUTH_URL = 'https://auth.europe-west1.gcp.commercetools.com'; const DEFAULT_HEADERS = ['content-type', 'access-control-allow-origin', 'access-control-allow-headers', 'access-control-allow-methods', 'access-control-expose-headers', 'access-control-max-ag', 'x-correlation-id', 'server-timing', 'date', 'server', 'transfer-encoding', 'access-control-max-age', 'content-encoding', 'x-envoy-upstream-service-time', 'via', 'alt-svc', 'connection']; function DefineError(statusCode, message, meta = {}) { this.code = meta['code'] ??= this.constructor.name; this.statusCode = statusCode; this.status = statusCode; this.message = message; Object.assign(this, meta); this.name = this.constructor.name; this.constructor.prototype.__proto__ = Error.prototype; if (Error.captureStackTrace) Error.captureStackTrace(this, this.constructor); } function NetworkError(...args) { DefineError.call(this, 0, ...args); } function HttpError(...args) { DefineError.call(this, ...args); } function BadRequest(...args) { DefineError.call(this, 400, ...args); } function Unauthorized(...args) { DefineError.call(this, 401, ...args); } function Forbidden(...args) { DefineError.call(this, 403, ...args); } function NotFound(...args) { DefineError.call(this, 404, ...args); } function ConcurrentModification(...args) { DefineError.call(this, 409, ...args); } function InternalServerError(...args) { DefineError.call(this, 500, ...args); } function ServiceUnavailable(...args) { DefineError.call(this, 503, ...args); } function getErrorByCode(code) { switch (code) { case 0: return NetworkError; case 400: return BadRequest; case 401: return Unauthorized; case 403: return Forbidden; case 404: return NotFound; case 409: return ConcurrentModification; case 500: return InternalServerError; case 503: return ServiceUnavailable; default: return undefined; } } function createError({ statusCode, message, ...rest }) { let errorMessage = message || 'Unexpected non-JSON error response'; if (statusCode === 404) errorMessage = `URI not found: ${rest.originalRequest?.uri || rest.uri}`; const ResponseError = getErrorByCode(statusCode); if (ResponseError) return new ResponseError(errorMessage, rest); return new HttpError(statusCode, errorMessage, rest); } function hasResponseRetryCode(retryCodes, response) { return [503, ...retryCodes].includes(response?.status || response?.statusCode); } async function executeHttpClientRequest(fetcher, config) { async function sendRequest() { const response = await fetcher({ ...config, headers: { ...config.headers } }); // validations and error handlings can also be done here return response; } // Attempt to send the request. return sendRequest().catch(error => Promise.reject(error)); } async function executor(request) { const { url, httpClient, ...rest } = request; const data = await executeHttpClientRequest(async options => { const { enableRetry, retryConfig, timeout, getAbortController } = rest; const { retryCodes = [], maxDelay = Infinity, maxRetries = 3, backoff = true, retryDelay = 200, // If set to true reinitialize the abort controller when the timeout is reached and apply the retry config retryOnAbort = true } = retryConfig || {}; let result, data, retryCount = 0, timer; // validate the `retryCodes` option validateRetryCodes(retryCodes); async function execute() { return httpClient(url, { ...options, ...rest, headers: { ...rest.headers }, // for axios ...(rest.body ? { data: rest.body } : {}), withCredentials: options.credentialsMode === 'include' }); } function initializeAbortController() { const abortController = (getAbortController ? getAbortController() : null) || new AbortController(); rest.abortController = abortController; rest.signal = abortController.signal; return abortController; } async function executeWithRetry() { const executeWithTryCatch = async (retryCodes, retryWhenAborted) => { let _response = {}; if (timeout) { let abortController = initializeAbortController(); timer = setTimeout(() => { abortController.abort(); abortController = initializeAbortController(); }, timeout); } try { _response = await execute(); if (_response.status > 399 && hasResponseRetryCode(retryCodes, _response)) { return { _response, shouldRetry: true }; } } catch (e) { // in nodejs v18, the error is AbortError, in nodejs v20, the error is TimeoutError // https://github.com/nodejs/undici/issues/2590 if ((e.name.includes('AbortError') || e.name.includes('TimeoutError')) && retryWhenAborted) { return { _response: e, shouldRetry: true }; } else { throw e; } } finally { clearTimeout(timer); } return { _response, shouldRetry: false }; }; // first attempt let { _response, shouldRetry } = await executeWithTryCatch(retryCodes, retryOnAbort); // retry attempts while (enableRetry && shouldRetry && retryCount < maxRetries) { retryCount++; // delay next retry attempt await sleep(calculateRetryDelay({ retryCount, retryDelay, maxRetries, backoff, maxDelay })); const execution = await executeWithTryCatch(retryCodes, retryOnAbort); _response = execution._response; shouldRetry = execution.shouldRetry; } return _response; } const response = await executeWithRetry(); // check if it's a HEAD request, then return if (rest.method == 'HEAD') { return { data: null, retryCount, statusCode: response.status || response.statusCode || data.statusCode, headers: response.headers }; } try { // try to parse the `fetch` response as text if (response.text && typeof response.text == 'function') { result = await response.text(); data = JSON.parse(result); } else { // axios response data = response.data || response; } } catch (err) { throw err; } return { data, retryCount, statusCode: response.status || response.statusCode || data.statusCode, headers: response.headers }; }, /** * get this object from the * middleware options or from * http client config */ /** * we want to suppress axios internal * error handling behaviour to make it * consistent with native fetch. */ { validateStatus: status => true }); return data; } function generateID() { // @ts-ignore return ([1e6] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c => (parseInt(c) ^ Math.floor(Math.random() * 256) & 15 >> parseInt(c) / 4).toString(16)); } function parse(headers) { return DEFAULT_HEADERS.reduce((result, key) => { let val = headers[key] ? headers[key] : typeof headers.get == 'function' ? headers.get(key) : null; if (val) result[key] = val; return result; }, {}); } function getHeaders(headers) { if (!headers) return null; // node-fetch if (headers.raw && typeof headers.raw == 'function') return headers.raw(); // Tmp fix for Firefox until it supports iterables if (!headers.forEach) return parse(headers); // whatwg-fetch const map = {}; headers.forEach((value, name) => { return map[name] = value; }); return map; } function isBuffer(obj) { return obj != null && obj.constructor != null && typeof obj.constructor.isBuffer === 'function' && obj.constructor.isBuffer(obj); } function maskAuthData(request) { const _request = JSON.parse(JSON.stringify(request)); if (_request?.headers) { if (_request.headers.Authorization) { _request.headers['Authorization'] = 'Bearer ********'; } if (_request.headers.authorization) { _request.headers['authorization'] = 'Bearer ********'; } } return _request; } function mergeAuthHeader(token, req) { return { ...req, headers: { ...req.headers, Authorization: `Bearer ${token}` } }; } var METHODS = ['ACL', 'BIND', 'CHECKOUT', 'CONNECT', 'COPY', 'DELETE', 'GET', 'HEAD', 'LINK', 'LOCK', 'M-SEARCH', 'MERGE', 'MKACTIVITY', 'MKCALENDAR', 'MKCOL', 'MOVE', 'NOTIFY', 'OPTIONS', 'PATCH', 'POST', 'PROPFIND', 'PROPPATCH', 'PURGE', 'PUT', 'REBIND', 'REPORT', 'SEARCH', 'SOURCE', 'SUBSCRIBE', 'TRACE', 'UNBIND', 'UNLINK', 'UNLOCK', 'UNSUBSCRIBE']; function calculateRetryDelay({ retryCount, retryDelay, backoff, maxDelay }) { if (backoff) { return retryCount !== 0 // do not increase if it's the first retry ? Math.min(Math.round((Math.random() + 1) * retryDelay * 2 ** retryCount), maxDelay) : retryDelay; } return retryDelay; } function sleep(ms) { return new Promise(resolve => { setTimeout(resolve, ms); }); } function buildTokenCacheKey(options) { if (!options?.credentials?.clientId || !options.projectKey || !options.host) throw new Error('Missing required options.'); return { clientId: options.credentials.clientId, host: options.host, projectKey: options.projectKey }; } function calculateExpirationTime(expiresIn) { return Date.now() + // Add a gap of 5 minutes before expiration time. expiresIn * 1000 - 5 * 60 * 1000; } function store(initVal) { let value = initVal; return { get: TokenCacheOption => value, set: (val, TokenCacheOption) => { value = val; } }; } function isDefined(value) { return typeof value !== 'undefined' && value !== null; } function clean(value) { if (!isDefined(value)) return ''; if (typeof value == 'string') return value; return Object.fromEntries(Object.entries(value).filter(([_, value]) => ![null, undefined, ''].includes(value))); } function urlParser(url) { const object = {}; const data = new URLSearchParams(url); for (let x of data.keys()) { if (data.getAll(x).length > 1) { object[x] = data.getAll(x); } else { object[x] = data.get(x); } } return object; } function urlStringifier(object) { object = clean(object); if (!object) return ''; const params = new URLSearchParams(object); for (const [key, value] of Object.entries(object)) { if (Array.isArray(value)) { params.delete(key); value.filter(isDefined).forEach(v => params.append(key, v)); } } return params.toString(); } function parseURLString(url, parser = urlParser) { return parser(url); } function stringifyURLString(object, stringifier = urlStringifier) { return stringifier(object); } /* This is the easiest way, for this use case, to detect if we're running in Node.js or in a browser environment. In other cases, this won't be even a problem as Rollup will provide the correct polyfill in the bundle. The main advantage by doing it this way is that it allows to easily test the code running in both environments, by overriding `global.window` in the specific test. */ const isBrowser = () => window.document && window.document.nodeType === 9; function getSystemInfo() { if (isBrowser()) return window.navigator.userAgent; const nodeVersion = process?.version?.slice(1) || 'unknown'; // unknow environment like React Native etc const platformInfo = `(${process?.platform || ''}; ${process?.arch || ''})`; return `node.js/${nodeVersion} ${platformInfo.trim()}`; } function createUserAgent(options) { let libraryInfo = null; let contactInfo = null; if (!options) { throw new Error('Missing required option `name`'); } // Main info const baseInfo = options.version ? `${options.name}/${options.version}` : options.name; // Library info if (options.libraryName && !options.libraryVersion) { libraryInfo = options.libraryName; } else if (options.libraryName && options.libraryVersion) { libraryInfo = `${options.libraryName}/${options.libraryVersion}`; } // Contact info if (options.contactUrl && !options.contactEmail) { contactInfo = `(+${options.contactUrl})`; } else if (!options.contactUrl && options.contactEmail) { contactInfo = `(+${options.contactEmail})`; } else if (options.contactUrl && options.contactEmail) { contactInfo = `(+${options.contactUrl}; +${options.contactEmail})`; } // System info const systemInfo = getSystemInfo(); // customName const customAgent = options.customAgent || ''; return [baseInfo, systemInfo, libraryInfo, contactInfo, customAgent].filter(Boolean).join(' '); } /** * validate some essential http options * @param options */ function validateHttpClientOptions(options) { if (!options.host) throw new Error('Request `host` or `url` is missing or invalid, please pass in a valid host e.g `host: http://a-valid-host-url`'); if (!options.httpClient && typeof options.httpClient !== 'function') throw new Error('An `httpClient` is not available, please pass in a `fetch` or `axios` instance as an option or have them globally available.'); if (options.httpClientOptions && Object.prototype.toString.call(options.httpClientOptions) !== '[object Object]') { throw new Error('`httpClientOptions` must be an object type'); } } /** * * @param retryCodes * @example * const retryCodes = [500, 504, "ETIMEDOUT"] */ function validateRetryCodes(retryCodes) { if (!Array.isArray(retryCodes)) { throw new Error('`retryCodes` option must be an array of retry status (error) codes and/or messages.'); } } /** * @param options */ function validateClient(options) { if (!options) throw new Error('Missing required options'); if (options.middlewares && !Array.isArray(options.middlewares)) throw new Error('Middlewares should be an array'); if (!options.middlewares || !Array.isArray(options.middlewares) || !options.middlewares.length) { throw new Error('You need to provide at least one middleware'); } } /** * @param options */ function validate(funcName, request, options = { allowedMethods: METHODS }) { if (!request) throw new Error(`The "${funcName}" function requires a "Request" object as an argument. See https://commercetools.github.io/nodejs/sdk/Glossary.html#clientrequest`); if (typeof request.uri !== 'string') throw new Error(`The "${funcName}" Request object requires a valid uri. See https://commercetools.github.io/nodejs/sdk/Glossary.html#clientrequest`); if (!options.allowedMethods.includes(request.method)) throw new Error(`The "${funcName}" Request object requires a valid method. See https://commercetools.github.io/nodejs/sdk/Glossary.html#clientrequest`); } /** * @param option */ function validateStringBodyHeaderOptions(stringBodyContentTypes) { if (!Array.isArray(stringBodyContentTypes)) { throw new Error('`stringBodyContentTypes` option must be an array of strings'); } } /** * * @param {AuthMiddlewareOptions} options * @returns { IBuiltRequestParams } * */ function buildRequestForClientCredentialsFlow(options) { // Validate options if (!options) throw new Error('Missing required options'); if (!options.host) throw new Error('Missing required option (host)'); if (!options.projectKey) throw new Error('Missing required option (projectKey)'); if (!options.credentials) throw new Error('Missing required option (credentials)'); const { clientId, clientSecret } = options.credentials || {}; if (!(clientId && clientSecret)) throw new Error('Missing required credentials (clientId, clientSecret)'); const scope = options.scopes ? options.scopes.join(' ') : undefined; const basicAuth = btoa(`${clientId}:${clientSecret}`); // This is mostly useful for internal testing purposes to be able to check // other oauth endpoints. const oauthUri = options.oauthUri || '/oauth/token'; const url = options.host.replace(/\/$/, '') + oauthUri; const body = `grant_type=client_credentials${scope ? `&scope=${scope}` : ''}`; return { url, body, basicAuth }; } /** * * @param {AuthMiddlewareOptions} options * @returns {IBuiltRequestParams} * */ function buildRequestForAnonymousSessionFlow(options) { if (!options) throw new Error('Missing required options'); if (!options.projectKey) throw new Error('Missing required option (projectKey)'); const projectKey = options.projectKey; options.oauthUri = options.oauthUri || `/oauth/${projectKey}/anonymous/token`; const result = buildRequestForClientCredentialsFlow(options); if (options.credentials.anonymousId) result.body += `&anonymous_id=${options.credentials.anonymousId}`; return { ...result }; } /** * * @param {RefreshAuthMiddlewareOptions} options * @returns {IBuiltRequestParams} */ function buildRequestForRefreshTokenFlow(options) { if (!options) throw new Error('Missing required options'); if (!options.host) throw new Error('Missing required option (host)'); if (!options.projectKey) throw new Error('Missing required option (projectKey)'); if (!options.credentials) throw new Error('Missing required option (credentials)'); if (!options.refreshToken) throw new Error('Missing required option (refreshToken)'); const { clientId, clientSecret } = options.credentials; if (!(clientId && clientSecret)) throw new Error('Missing required credentials (clientId, clientSecret)'); const basicAuth = btoa(`${clientId}:${clientSecret}`); // This is mostly useful for internal testing // purposes to be able to check other oauth endpoints. const oauthUri = options.oauthUri || '/oauth/token'; const url = options.host.replace(/\/$/, '') + oauthUri; const body = `grant_type=refresh_token&refresh_token=${encodeURIComponent(options.refreshToken)}`; return { basicAuth, url, body }; } /** * @param {PasswordAuthMiddlewareOptions} options * @returns {IBuiltRequestParams} */ function buildRequestForPasswordFlow(options) { if (!options) throw new Error('Missing required options'); if (!options.host) throw new Error('Missing required option (host)'); if (!options.projectKey) throw new Error('Missing required option (projectKey)'); if (!options.credentials) throw new Error('Missing required option (credentials)'); const { clientId, clientSecret, user } = options.credentials; const projectKey = options.projectKey; if (!(clientId && clientSecret && user)) throw new Error('Missing required credentials (clientId, clientSecret, user)'); const { username, password } = user; if (!(username && password)) throw new Error('Missing required user credentials (username, password)'); const scope = (options.scopes || []).join(' '); const scopeStr = scope ? `&scope=${scope}` : ''; const basicAuth = btoa(`${clientId}:${clientSecret}`); /** * This is mostly useful for internal testing purposes to be able to check * other oauth endpoints. */ const oauthUri = options.oauthUri || `/oauth/${projectKey}/customers/token`; const url = options.host.replace(/\/$/, '') + oauthUri; // encode username and password as requested by the system const body = `grant_type=password&username=${encodeURIComponent(username)}&password=${encodeURIComponent(password)}${scopeStr}`; return { basicAuth, url, body }; } async function executeRequest$1(options) { const { httpClient, httpClientOptions, tokenCache, userOption, tokenCacheObject, tokenCacheKey, request } = options; let url = options.url; let body = options.body; let basicAuth = options.basicAuth; if (!httpClient || typeof httpClient !== 'function') throw new Error('an `httpClient` is not available, please pass in a `fetch` or `axios` instance as an option or have them globally available.'); /** * use refreshToken flow if there is refresh-token * and there's either no token or the token is expired */ if (tokenCacheObject && tokenCacheObject.refreshToken && (!tokenCacheObject.token || tokenCacheObject.token && Date.now() > tokenCacheObject.expirationTime)) { if (!userOption) { throw new Error('Missing required options.'); } const opt = { ...buildRequestForRefreshTokenFlow({ ...userOption, refreshToken: tokenCacheObject.refreshToken }) }; // reassign values url = opt.url; body = opt.body; basicAuth = opt.basicAuth; } // request a new token let response; try { response = await executor({ url, method: 'POST', headers: { ...request.headers, Authorization: `Basic ${basicAuth}`, 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': byteLength(body) }, httpClient, httpClientOptions, body }); if (response.statusCode >= 200 && response.statusCode < 300) { const { access_token: token, expires_in: expiresIn, refresh_token: refreshToken } = response?.data; // calculate token expiration time const expirationTime = calculateExpirationTime(expiresIn); // cache new generated token, refreshToken and expiration time const cache = { token, expirationTime, refreshToken }; await tokenCache.set(cache, tokenCacheKey); return Promise.resolve(true); } // bubble up the error for the catch block throw createError({ code: response.data.error, statusCode: response.data.statusCode, message: response.data.message, error: response.data.errors }); } catch (error) { // throw error and free up the middleware chain throw error; } } async function authProcessor(request, tokenFetchPromise = null, tokenCacheObject, tokenCacheKey, tokenCache, builder, options, next) { // prepare request options const requestOptions = { request, tokenCache, tokenCacheKey, httpClient: options.httpClient || fetch, httpClientOptions: options.httpClientOptions, ...builder(options), userOption: options, next }; /** * check to see if the response for 401 errors * @param r MiddlewareResponse * @returns response */ const checkAndRetryUnauthorizedError = async r => { let _response = Object.assign({}, r); if (_response.statusCode == 401) { tokenFetchPromise = executeRequest$1(requestOptions); await tokenFetchPromise; tokenFetchPromise = null; tokenCacheObject = await tokenCache.get(tokenCacheKey); _response = await next(mergeAuthHeader(tokenCacheObject.token, request)); } return _response; }; if (request.headers && (request.headers.Authorization || request.headers.authorization)) { // move on return checkAndRetryUnauthorizedError(await next(request)); } /** * If there is a token in the tokenCache, and it's not * expired, append the token in the `Authorization` header. */ tokenCacheObject = await tokenCache.get(tokenCacheKey); if (tokenCacheObject && tokenCacheObject.token && Date.now() < tokenCacheObject.expirationTime) { return checkAndRetryUnauthorizedError(await next(mergeAuthHeader(tokenCacheObject.token, request))); } // If a token is already being fetched, wait for it to finish if (tokenFetchPromise) { await tokenFetchPromise; } else { // Otherwise, fetch the token and let others wait for this process to complete tokenFetchPromise = executeRequest$1(requestOptions); await tokenFetchPromise; tokenFetchPromise = null; } // Now the token is present in the tokenCache and can be accessed tokenCacheObject = await tokenCache.get(tokenCacheKey); return next(mergeAuthHeader(tokenCacheObject.token, request)); } function createAuthMiddlewareForAnonymousSessionFlow$1(options) { const tokenCache = options.tokenCache || store({ token: '', expirationTime: -1 }); let tokenCacheObject; let tokenFetchPromise = null; const tokenCacheKey = buildTokenCacheKey(options); return next => { return async request => { return authProcessor(request, tokenFetchPromise, tokenCacheObject, tokenCacheKey, tokenCache, buildRequestForAnonymousSessionFlow, options, next); }; }; } function createAuthMiddlewareForClientCredentialsFlow$1(options) { const tokenCache = options.tokenCache || store({ token: '', expirationTime: -1 }); let tokenCacheObject; let tokenFetchPromise = null; const tokenCacheKey = buildTokenCacheKey(options); return next => { return async request => { return authProcessor(request, tokenFetchPromise, tokenCacheObject, tokenCacheKey, tokenCache, buildRequestForClientCredentialsFlow, options, next); }; }; } function createAuthMiddlewareForExistingTokenFlow$1(authorization, options) { return next => { return async request => { if (typeof authorization !== 'string') throw new Error('authorization must be a string'); const isForce = options?.force === undefined ? true : options.force; /** * The request will not be modified if: * 1. no argument is passed * 2. force is false and authorization header exists */ if (!authorization || request.headers && (request.headers.Authorization || request.headers.authorization) && isForce === false) { return next(request); } const requestWithAuth = { ...request, headers: { ...request.headers, Authorization: authorization } }; return next(requestWithAuth); }; }; } function createAuthMiddlewareForPasswordFlow$1(options) { const tokenCache = options.tokenCache || store({ token: '', expirationTime: -1 }); let tokenCacheObject; let tokenFetchPromise = null; const tokenCacheKey = buildTokenCacheKey(options); return next => { return async request => { return authProcessor(request, tokenFetchPromise, tokenCacheObject, tokenCacheKey, tokenCache, buildRequestForPasswordFlow, options, next); }; }; } function createAuthMiddlewareForRefreshTokenFlow$1(options) { const tokenCache = options.tokenCache || store({ token: '', tokenCacheKey: null }); let tokenCacheObject; let tokenFetchPromise = null; const tokenCacheKey = buildTokenCacheKey(options); return next => { return async request => { return authProcessor(request, tokenFetchPromise, tokenCacheObject, tokenCacheKey, tokenCache, buildRequestForRefreshTokenFlow, options, next); }; }; } function createConcurrentModificationMiddleware$1(modifierFunction) { return next => { return async request => { const response = await next(request); if (response.statusCode == 409) { /** * extract the currentVersion * from the error body and update * request with the currentVersion */ const version = response.error.body?.errors?.[0]?.currentVersion; // update the resource version here if (version) { if (modifierFunction && typeof modifierFunction == 'function') { request.body = await modifierFunction(version, request, response); } else { request.body = typeof request.body == 'string' ? { ...JSON.parse(request.body), version } : { ...request.body, version }; } return next(request); } } return response; }; }; } function createCorrelationIdMiddleware$1(options) { return next => request => { const nextRequest = { ...request, headers: { ...request.headers, 'X-Correlation-ID': options?.generate && typeof options.generate == 'function' ? options.generate() : generateID() } }; return next(nextRequest); }; } function createErrorMiddleware$1(options = {}) { return next => async request => { const response = await next(request); if (response.error) { const { error } = response; if (options.handler && typeof options.handler == 'function') { return options.handler({ error, request, response, next }); } return { ...response, statusCode: error.statusCode || 0, headers: error.headers || getHeaders({}), body: error, error }; } return response; }; } async function executeRequest({ url, httpClient, clientOptions }) { const { request, maskSensitiveHeaderData, includeRequestInErrorResponse, includeResponseHeaders } = clientOptions; try { const response = await executor({ url, ...clientOptions, httpClient, method: clientOptions.method, ...(clientOptions.body ? { body: clientOptions.body } : {}) }); if (!includeResponseHeaders) { response.headers = null; } if (response.statusCode >= 200 && response.statusCode < 300) { if (clientOptions.method == 'HEAD') { return { body: null, statusCode: response.statusCode, retryCount: response.retryCount, headers: getHeaders(response.headers) }; } return { body: response.data, statusCode: response.statusCode, retryCount: response.retryCount, headers: getHeaders(response.headers) }; } const error = createError({ code: response.data?.errors?.[0].code, message: response?.data?.message || response?.message, statusCode: response.statusCode || response?.data?.statusCode, method: clientOptions.method, headers: getHeaders(response.headers), /** * @deprecated use `error` instead */ body: response.data, error: response.data, retryCount: response.retryCount, ...(includeRequestInErrorResponse ? { originalRequest: maskSensitiveHeaderData ? maskAuthData(request) : request } : { uri: request.uri }) }); return { ...error, error }; } catch (e) { // We know that this is a network error const headers = includeResponseHeaders ? getHeaders(e.response?.headers) : null; const statusCode = e.response?.status || e.response?.statusCode || e.response?.data.statusCode || 0; const message = e.response?.data?.message; const error = createError({ statusCode, code: 'NetworkError', status: statusCode, message: message || e.message, headers, /** * @deprecated use `error` instead */ body: e.response?.data || e, error: e.response?.data || e, ...(includeRequestInErrorResponse ? { originalRequest: maskSensitiveHeaderData ? maskAuthData(request) : request } : { uri: request.uri }) }); throw { // because body and error should be mutually exclusive body: null, error }; } } function createHttpMiddleware$1(options) { // validate options validateHttpClientOptions(options); const { host, credentialsMode, httpClient, timeout, enableRetry, retryConfig, getAbortController, includeOriginalRequest, includeRequestInErrorResponse = true, includeResponseHeaders = true, maskSensitiveHeaderData, httpClientOptions, stringBodyContentTypes = [] } = options; return next => { return async request => { const url = host.replace(/\/$/, '') + request.uri; const requestHeader = { ...request.headers }; // validate custom header validateStringBodyHeaderOptions(stringBodyContentTypes); // validate header if (!(Object.prototype.hasOwnProperty.call(requestHeader, 'Content-Type') || Object.prototype.hasOwnProperty.call(requestHeader, 'content-type'))) { requestHeader['Content-Type'] = 'application/json'; } // Unset the content-type header if explicitly asked to (passing `null` as value). if (requestHeader['Content-Type'] === null) { delete requestHeader['Content-Type']; } // Ensure body is a string if content type is application/{json|graphql} const body = [...HEADERS_CONTENT_TYPES, ...stringBodyContentTypes].indexOf(requestHeader['Content-Type']) > -1 && typeof request.body === 'string' || isBuffer(request.body) ? request.body : JSON.stringify(request.body || undefined); if (body && (typeof body === 'string' || isBuffer(body))) { requestHeader['Content-Length'] = byteLength(body); } const clientOptions = { enableRetry, retryConfig, request: request, method: request.method, headers: requestHeader, includeRequestInErrorResponse, maskSensitiveHeaderData, includeResponseHeaders, ...httpClientOptions }; if (credentialsMode) { clientOptions.credentialsMode = credentialsMode; } if (timeout) { clientOptions.timeout = timeout; clientOptions.getAbortController = getAbortController; } if (body) { clientOptions.body = body; } // get result from executed request const response = await executeRequest({ url, clientOptions, httpClient }); const responseWithRequest = { ...request, includeOriginalRequest, maskSensitiveHeaderData, response }; return next(responseWithRequest); }; }; } function createLoggerMiddleware$1(options) { return next => { return async request => { let response = await next(request); const originalResponse = Object.assign({}, response); const { loggerFn = console.log } = options || {}; if (loggerFn && typeof loggerFn == 'function') { loggerFn(response); } return originalResponse; }; }; } function createQueueMiddleware$1({ concurrency = 20 }) { let runningCount = 0; const queue = []; const waitForSlot = () => { if (0 >= concurrency) return Promise.resolve(); return new Promise(resolve => { const tryNext = () => { if (runningCount < concurrency) { runningCount++; resolve(); } else { queue.push(tryNext); } }; tryNext(); }); }; // Function to free up a slot after a request is completed const freeSlot = () => { runningCount--; if (queue.length > 0) { const nextInQueue = queue.shift(); if (nextInQueue) { nextInQueue(); } } }; return next => request => { return waitForSlot().then(() => { const patchedRequest = { ...request, resolve(data) { request.resolve(data); freeSlot(); }, reject(error) { request.reject(error); freeSlot(); } }; // Process the next middleware return next(patchedRequest).finally(() => { freeSlot(); }); }); }; } var packageJson = { name: "@commercetools/ts-client", version: "4.7.0", engines: { node: ">=18" }, description: "commercetools Composable Commerce TypeScript SDK client.", keywords: [ "commercetools", "composable commerce", "sdk", "typescript", "client", "middleware", "http", "oauth", "auth" ], homepage: "https://github.com/commercetools/commercetools-sdk-typescript", license: "MIT", directories: { lib: "lib", test: "test" }, publishConfig: { access: "public" }, repository: { type: "git", url: "https://github.com/commercetools/commercetools-sdk-typescript" }, bugs: { url: "https://github.com/commercetools/commercetools-sdk-typescript/issues" }, dependencies: { buffer: "^6.0.3" }, files: [ "dist", "CHANGELOG.md" ], author: "Chukwuemeka Ajima <meeky.ae@gmail.com>", main: "dist/commercetools-ts-client.cjs.js", module: "dist/commercetools-ts-client.esm.js", browser: { "./dist/commercetools-ts-client.cjs.js": "./dist/commercetools-ts-client.browser.cjs.js", "./dist/commercetools-ts-client.esm.js": "./dist/commercetools-ts-client.browser.esm.js" }, devDependencies: { "common-tags": "1.8.2", dotenv: "17.2.2", jest: "29.7.0", nock: "14.0.10", "organize-imports-cli": "0.10.0" }, scripts: { organize_imports: "find src -type f -name '*.ts' | xargs organize-imports-cli", postbuild: "yarn organize_imports", post_process_generate: "yarn organize_imports", docs: "typedoc --out docs" } }; function createUserAgentMiddleware$1(options) { return next => async request => { const userAgent = createUserAgent({ ...options, name: `${options.name ? options.name + '/' : ''}commercetools-sdk-javascript-v3/${packageJson.version}` }); const requestWithUserAgent = { ...request, headers: { ...request.headers, 'User-Agent': userAgent } }; return next(requestWithUserAgent); }; } var middleware = /*#__PURE__*/Object.freeze({ __proto__: null, createAuthMiddlewareForAnonymousSessionFlow: createAuthMiddlewareForAnonymousSessionFlow$1, createAuthMiddlewareForClientCredentialsFlow: createAuthMiddlewareForClientCredentialsFlow$1, createAuthMiddlewareForExistingTokenFlow: createAuthMiddlewareForExistingTokenFlow$1, createAuthMiddlewareForPasswordFlow: createAuthMiddlewareForPasswordFlow$1, createAuthMiddlewareForRefreshTokenFlow: createAuthMiddlewareForRefreshTokenFlow$1, createConcurrentModificationMiddleware: createConcurrentModificationMiddleware$1, createCorrelationIdMiddleware: createCorrelationIdMiddleware$1, createErrorMiddleware: createErrorMiddleware$1, createHttpMiddleware: createHttpMiddleware$1, createLoggerMiddleware: createLoggerMiddleware$1, createQueueMiddleware: createQueueMiddleware$1, createUserAgentMiddleware: createUserAgentMiddleware$1 }); function compose({ middlewares }) { if (middlewares.length === 1) return middlewares[0]; const _middlewares = middlewares.slice(); return _middlewares.reduce((ac, cv) => (...args) => ac(cv.apply(null, args))); } // process batch requests let _options; function process$1(request, fn, processOpt) { validate('process', request, { allowedMethods: ['GET'] }); if (typeof fn !== 'function') throw new Error('The "process" function accepts a "Function" as a second argument that returns a Promise. See https://commercetools.github.io/nodejs/sdk/api/sdkClient.html#processrequest-processfn-options'); // Set default process options const opt = { total: Number.POSITIVE_INFINITY, accumulate: true, ...processOpt }; return new Promise((resolve, reject) => { let _path, _queryString = ''; if (request && request.uri) { const [path, queryString] = request.uri.split('?'); _path = path; _queryString = queryString; } const requestQuery = { ...parseURLString(_queryString) }; const query = { // defaults limit: 20, // merge given query params ...requestQuery }; let itemsToGet = opt.total; let hasFirstPageBeenProcessed = false; const processPage = async (lastId, acc = []) => { // Use the lesser value between limit and itemsToGet in query const limit = query.limit < itemsToGet ? query.limit : itemsToGet; const originalQueryString = stringifyURLString({ ...query, limit }); const enhancedQuery = { sort: 'id asc', withTotal: false, ...(lastId ? { where: `id > "${lastId}"` } : {}) }; const enhancedQueryString = stringifyURLString(enhancedQuery); const enhancedRequest = { ...request, uri: `${_path}?${enhancedQueryString}&${originalQueryString}` }; try { const payload = await createClient(_options).execute(enhancedRequest); const { results, count: resultsLength } = payload?.body || {}; if (!resultsLength && hasFirstPageBeenProcessed) { return resolve(acc || []); } const result = await Promise.resolve(fn(payload)); let accumulated; hasFirstPageBeenProcessed = true; if (opt.accumulate) accumulated = acc.concat(result || []); itemsToGet -= resultsLength; // If there are no more items to get, it means the total number // of items in the original request have been fetched so we // resolve the promise. // Also, if we get less results in a page then the limit set it // means that there are no more pages and that we can finally // resolve the promise. if (resultsLength < query.limit || !itemsToGet) { return resolve(accumulated || []); } const last = results[resultsLength - 1]; const newLastId = last && last.id; processPage(newLastId, accumulated); } catch (error) { reject(error); } }; // Start iterating through pages processPage(); }); } function createClient(middlewares) { _options = middlewares; validateClient(middlewares); let _maskSensitiveHeaderData = false; const resolver = { async resolve(rs) { const { response, includeOriginalRequest, maskSensitiveHeaderData, ...request } = rs; const { retryCount, ...rest } = response; _maskSensitiveHeaderData = maskSensitiveHeaderData; const res = { body: null, error: null, reject: rs.reject, resolve: rs.resolve, ...rest, ...(includeOriginalRequest ? { originalRequest: request } : {}), ...(response?.retryCount ? { retryCount: response.retryCount } : {}) }; return res; } }; const dispatch = compose(middlewares)(resolver.resolve); return { process: process$1, execute(request) { validate('exec', request); return new Promise(async (resolve, reject) => { try { const response = await dispatch({ reject, resolve, ...request }); if (response.error) return reject(response.error); if (response.originalRequest && _maskSensitiveHeaderData) { response.originalRequest = maskAuthData(response.originalRequest); } resolve(response); } catch (err) { reject(err); } }); } }; } const { createAuthMiddlewareForPasswordFlow, createAuthMiddlewareForAnonymousSessionFlow, createAuthMiddlewareForClientCredentialsFlow, createAuthMiddlewareForRefreshTokenFlow, createAuthMiddlewareForExistingTokenFlow, createCorrelationIdMiddleware, createHttpMiddleware, createLoggerMiddleware, createQueueMiddleware, createUserAgentMiddleware, createConcurrentModificationMiddleware, createErrorMiddleware } = middleware; class ClientBuilder { constructor() { _defineProperty(this, "projectKey", void 0); _defineProperty(this, "authMiddleware", void 0); _defineProperty(this, "httpMiddleware", void 0); _defineProperty(this, "userAgentMiddleware", void 0); _defineProperty(this, "correlationIdMiddleware", void 0); _defineProperty(this, "loggerMiddleware", void 0); _defineProperty(this, "queueMiddleware", void 0); _defineProperty(this, "concurrentMiddleware", void 0); _defineProperty(this, "telemetryMiddleware", void 0); _defineProperty(this, "beforeMiddleware", void 0); _defineProperty(this, "afterMiddleware", void 0); _defineProperty(this, "errorMiddleware", void 0); _defineProperty(this, "middlewares", []); this.userAgentMiddleware = createUserAgentMiddleware({}); } withProjectKey(key) { this.projectKey = key; return this; } defaultClient(baseUri, credentials, oauthUri, projectKey, scopes, httpClient) { return this.withClientCredentialsFlow({ host: oauthUri, projectKey: projectKey || this.projectKey, credentials, httpClient: httpClient || fetch, scopes }).withHttpMiddleware({ host: baseUri, httpClient: httpClient || fetch }); } withAuthMiddleware(authMiddleware) { this.authMiddleware = authMiddleware; return this; } withMiddleware(middleware) { this.middlewares.push(middleware); return this; } withErrorMiddleware(options) { this.errorMiddleware = createErrorMiddleware(options); return this; } withClientCredentialsFlow(options) { return this.withAuthMiddleware(createAuthMiddlewareForClientCredentialsFlow({ host: options.host || CTP_AUTH_URL, projectKey: options.projectKey || this.projectKey, credentials: { clientId: options.credentials.clientId || null, clientSecret: options.credentials.clientSecret || null }, oauthUri: options.oauthUri || null, scopes: options.scopes, httpClient: options.httpClient || fetch, ...options })); } withPasswordFlow(options) { return this.withAuthMiddleware(createAuthMiddlewareForPasswordFlow({ host: options.host || CTP_AUTH_URL, projectKey: options.projectKey || this.projectKey, credentials: { clientId: options.credentials.clientId || null, clientSecret: options.credentials.clientSecret || null, user: { username: options.credentials.user.username || null, password: options.credentials.user.password || null } }, httpClient: options.httpClient || fetch, ...options })); } withAnonymousSessionFlow(options) { return this.withAuthMiddleware(createAuthMiddlewareForAnonymousSessionFlow({ host: options.host || CTP_AUTH_URL, projectKey: this.projectKey || options.projectKey, credentials: { clientId: options.credentials.clientId || null, clientSecret: options.credentials.clientSecret || null, anonymousId: options.credentials.anonymousId || null }, httpClient: options.httpClient || fetch, ...options })); } withRefreshTokenFlow(options) { return this.withAuthMiddleware(createAuthMiddlewareForRefreshTokenFlow({ host: options.host || CTP_AUTH_URL, projectKey: this.projectKey || options.projectKey, credentials: { clientId: options.credentials.clientId || null, clientSecret: options.credentials.clientSecret || null }, httpClient: options.httpClient || fetch, refreshToken: options.refreshToken || null, ...options })); } withExistingTokenFlow(authorization, options) { return this.withAuthMiddleware(createAuthMiddlewareForExistingTokenFlow(authorization, { force: options.force || true, ...options })); } withHttpMiddleware(options) { this.httpMiddleware = createHttpMiddleware({ host: options.host || CTP_API_URL, httpClient: options.httpClient || fetch, ...options }); return this; } withUserAgentMiddleware(options) { this.userAgentMiddleware = createUserAgentMiddleware(options); return this; } withQueueMiddleware(options) { this.queueMiddleware = createQueueMiddleware({ concurrency: options.concurrency || CONCURRENCT_REQUEST, ...options }); return this; } withLoggerMiddleware(options) { this.loggerMiddleware = createLoggerMiddleware(options); return this; } withCorrelationIdMiddleware(options) { this.correlationIdMiddleware = createCorrelationIdMiddleware({ generate: options?.generate, ...options }); return this; } withConcurrentModificationMiddleware(options) { this.concurrentMiddleware = createConcurrentModificationMiddleware(options?.concurrentModificationHandlerFn); return this; } withTelemetryMiddleware(options) { const { createTelemetryMiddleware, ...rest } = options; this.withUserAgentMiddleware({ customAgent: rest?.userAgent || 'typescript-sdk-apm-middleware' }); this.telemetryMiddleware = createTelemetryMiddleware(rest); return this; } withBeforeExecutionMiddleware(options) { const { middleware, ...rest } = options || {}; this.beforeMiddleware = options.middleware(rest); return this; } withAfterExecutionMiddleware(