@commercetools/ts-client
Version:
commercetools Composable Commerce TypeScript SDK client.
1,620 lines (1,531 loc) • 52 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
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();
try {
// try to parse the `fetch` response as text
if (response.text && typeof response.text == 'function') {
result = (await response.text()) || JSON.stringify(response[Object.getOwnPropertySymbols(response)[1]]);
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 = () => typeof window !== 'undefined' && 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
} = 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: {
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.2.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: "git+https://github.com/commercetools/commercetools-sdk-typescript.git"
},
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: "16.5.0",
jest: "29.7.0",
nock: "14.0.4",
"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(options) {
const {
middleware,
...rest
} = options || {};
this.afterMiddleware = options.mi