UNPKG

@commercetools/sdk-client-v2

Version:
1,374 lines (1,313 loc) 46.4 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var _ = require('buffer/'); 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 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); } 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']; /** * @throws {Error} */ 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`); } let _options; const PAGE_LIMIT = 20; function compose(...funcs) { funcs = funcs.filter(func => typeof func === 'function'); if (funcs.length === 1) return funcs[0]; return funcs.reduce((a, b) => (...args) => a(b(...args))); } 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 = { limit: PAGE_LIMIT, // defaults 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 = { ...qs.parse(_queryString) } // const requestQuery = { ...Object.fromEntries(new URLSearchParams(_queryString)) } const requestQuery = { ...parseURLString(_queryString) }; const query = { // defaults limit: opt.limit, // merge given query params ...requestQuery }; let hasFirstPageBeenProcessed = false; let itemsToGet = opt.total; 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 = qs.stringify({ ...query, limit }, qsOptions) const originalQueryString = stringifyURLString({ ...query, limit }); const enhancedQuery = { sort: opt.sort || 'id asc', withTotal: false, ...(lastId ? { where: `id > "${lastId}"` } : {}) }; // const enhancedQueryString = qs.stringify(enhancedQuery, qsOptions) 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(options) { _options = 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'); return { /** * Given a request object, */ process: process$1, execute(request) { validate('exec', request); return new Promise((resolve, reject) => { const resolver = (rq, rs) => { // Note: pick the promise `resolve` and `reject` function from // the response object. This is not necessary the same function // given from the `new Promise` constructor, as middlewares could // override those functions for custom behaviours. if (rs.error) rs.reject(rs.error);else { const resObj = { body: rs.body || {}, statusCode: rs.statusCode }; if (rs.headers) resObj.headers = rs.headers; if (rs.request) resObj.request = rs.request; rs.resolve(resObj); } }; const dispatch = compose(...options.middlewares)(resolver); dispatch(request, // Initial response shape { resolve, reject, body: undefined, error: undefined }); }); } }; } function buildRequestForClientCredentialsFlow(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 = _.Buffer.from(`${clientId}:${clientSecret}`).toString('base64'); // 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 { basicAuth, url, body }; } 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 pKey = 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 = _.Buffer.from(`${clientId}:${clientSecret}`).toString('base64'); /** * This is mostly useful for internal testing purposes to be able to check * other oauth endpoints. */ const oauthUri = options.oauthUri || `/oauth/${pKey}/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 }; } 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 = _.Buffer.from(`${clientId}:${clientSecret}`).toString('base64'); // 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 }; } function buildRequestForAnonymousSessionFlow(options) { if (!options) throw new Error('Missing required options'); if (!options.projectKey) throw new Error('Missing required option (projectKey)'); const pKey = options.projectKey; options.oauthUri = options.oauthUri || `/oauth/${pKey}/anonymous/token`; const result = buildRequestForClientCredentialsFlow(options); if (options.credentials.anonymousId) result.body += `&anonymous_id=${options.credentials.anonymousId}`; return { ...result }; } function mergeAuthHeader(token, req) { return { ...req, headers: { ...req.headers, Authorization: `Bearer ${token}` } }; } function calculateExpirationTime(expiresIn) { return Date.now() + // Add a gap of 5 minutes before expiration time. expiresIn * 1000 - 5 * 60 * 1000; } async function executeRequest({ fetcher, url, basicAuth, body, tokenCache, requestState, pendingTasks, response, tokenCacheKey }) { try { const _res = await fetcher(url, { method: 'POST', headers: { Authorization: `Basic ${basicAuth}`, 'Content-Length': _.Buffer.byteLength(body).toString(), 'Content-Type': 'application/x-www-form-urlencoded' }, body }); if (_res.ok) { const { access_token: token, expires_in: expiresIn, refresh_token: refreshToken } = await _res.json(); const expirationTime = calculateExpirationTime(expiresIn); // cache new generated token tokenCache.set({ token, expirationTime, refreshToken }, tokenCacheKey); // Dispatch all pending requests requestState.set(false); /** * Freeze and copy pending queue, reset original one for accepting * new pending tasks */ const executionQueue = pendingTasks.slice(); pendingTasks = []; executionQueue.forEach(task => { // Assign the new token in the request header const requestWithAuth = mergeAuthHeader(token, task.request); /** * console.log('test', cache, pendingTasks) * Continue by calling the task's own next function */ task.next(requestWithAuth, task.response); }); return; } // Handle error response let parsed; const text = await _res.text(); try { parsed = JSON.parse(text); } catch (error) { /* noop */ } const error = new Error(parsed ? parsed.message : text); if (parsed) error.body = parsed; /** * to notify that token is either fetched or failed * in the below case token failed to be fetched * and reset requestState to false * so requestState could be shared between multi authMiddlewareBase functions */ requestState.set(false); response.reject(error); } catch (error) { /** * to notify that token is either fetched or failed * in the below case token failed to be fetched * and reset requestState to false * so requestState could be shared between multi authMiddlewareBase functions */ requestState.set(false); if (response && typeof response.reject === 'function') response.reject(error); } } function authMiddlewareBase({ request, response, url, basicAuth, body, pendingTasks, requestState, tokenCache, tokenCacheKey, fetch: fetcher }, next, userOptions) { if (!fetcher && typeof fetch === 'undefined') throw new Error('`fetch` is not available. Please pass in `fetch` as an option or have it globally available.'); if (!fetcher) fetcher = fetch; // Check if there is already a `Authorization` header in the request. // If so, then go directly to the next middleware. if (request.headers && request.headers.authorization || request.headers && request.headers.Authorization) { next(request, response); return; } // If there was a token in the tokenCache, and it's not expired, append // the token in the `Authorization` header. const tokenObj = tokenCache.get(tokenCacheKey); if (tokenObj && tokenObj.token && Date.now() < tokenObj.expirationTime) { const requestWithAuth = mergeAuthHeader(tokenObj.token, request); next(requestWithAuth, response); return; } /** * Keep pending tasks until a token is fetched * Save next function as well, to call it once the token has been fetched, which prevents * unexpected behaviour in a context in which the next function uses global vars * or Promises to capture the token to hand it to other libraries, e.g. Apollo */ pendingTasks.push({ request, response, next }); // If a token is currently being fetched, just wait ;) if (requestState.get()) return; // Mark that a token is being fetched requestState.set(true); /** * If there was a refreshToken in the tokenCache, and there was an expired * token or no token in the tokenCache, use the refreshToken flow */ if (tokenObj && tokenObj.refreshToken && (!tokenObj.token || tokenObj.token && Date.now() > tokenObj.expirationTime)) { if (!userOptions) throw new Error('Missing required options'); executeRequest({ fetcher, ...buildRequestForRefreshTokenFlow({ ...userOptions, refreshToken: tokenObj.refreshToken }), tokenCacheKey, tokenCache, requestState, pendingTasks, response }); return; } // Token and refreshToken are not present or invalid. Request a new token... executeRequest({ fetcher, url, basicAuth, body, tokenCacheKey, tokenCache, requestState, pendingTasks, response }); } function store(initVal) { let value = initVal; return { get: TokenCacheOption => value, set: (val, TokenCacheOption) => { value = val; } }; } function createAuthMiddlewareForAnonymousSessionFlow$1(options) { const tokenCache = options.tokenCache || store({ token: '', expirationTime: -1 }); const pendingTasks = []; const requestState = store(false); return next => (request, response) => { // Check if there is already a `Authorization` header in the request. // If so, then go directly to the next middleware. if (request.headers && request.headers.authorization || request.headers && request.headers.Authorization) { next(request, response); return; } const params = { request, response, ...buildRequestForAnonymousSessionFlow(options), pendingTasks, requestState, tokenCache, fetch: options.fetch }; authMiddlewareBase(params, next, options); }; } function buildTokenCacheKey(options) { return { clientId: options.credentials.clientId, host: options.host, projectKey: options.projectKey }; } function createAuthMiddlewareForClientCredentialsFlow$1(options) { const tokenCache = options.tokenCache || store({ token: '', expirationTime: -1 }); const requestState = store(false); const pendingTasks = []; return next => (request, response) => { // Check if there is already a `Authorization` header in the request. // If so, then go directly to the next middleware. if (request.headers && request.headers.authorization || request.headers && request.headers.Authorization) { next(request, response); return; } const params = { request, response, ...buildRequestForClientCredentialsFlow(options), pendingTasks, requestState, tokenCache, tokenCacheKey: buildTokenCacheKey(options), fetch: options.fetch }; authMiddlewareBase(params, next); }; } function createAuthMiddlewareWithExistingToken$1(authorization = '', options = {}) { return next => (request, response) => { if (typeof authorization !== 'string') throw new Error('authorization must be a string'); const force = 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 && request.headers.Authorization) && force === false) { return next(request, response); } const requestWithAuth = { ...request, headers: { ...request.headers, Authorization: authorization } }; return next(requestWithAuth, response); }; } function createAuthMiddlewareForPasswordFlow$1(options) { const tokenCache = options.tokenCache || store({}); const pendingTasks = []; const requestState = store(false); return next => (request, response) => { // Check if there is already a `Authorization` header in the request. // If so, then go directly to the next middleware. if (request.headers && request.headers.authorization || request.headers && request.headers.Authorization) { next(request, response); return; } const params = { request, response, ...buildRequestForPasswordFlow(options), pendingTasks, requestState, tokenCache, fetch: options.fetch }; authMiddlewareBase(params, next, options); }; } function createAuthMiddlewareForRefreshTokenFlow$1(options) { const tokenCache = options.tokenCache || store({ token: '', expirationTime: -1 }); const pendingTasks = []; const requestState = store(false); return next => (request, response) => { // Check if there is already a `Authorization` header in the request. // If so, then go directly to the next middleware. if (request.headers && request.headers.authorization || request.headers && request.headers.Authorization) { next(request, response); return; } const params = { request, response, ...buildRequestForRefreshTokenFlow(options), pendingTasks, requestState, tokenCache, fetch: options.fetch }; authMiddlewareBase(params, next); }; } var authMiddlewares = /*#__PURE__*/Object.freeze({ __proto__: null, createAuthMiddlewareForAnonymousSessionFlow: createAuthMiddlewareForAnonymousSessionFlow$1, createAuthMiddlewareForClientCredentialsFlow: createAuthMiddlewareForClientCredentialsFlow$1, createAuthMiddlewareWithExistingToken: createAuthMiddlewareWithExistingToken$1, createAuthMiddlewareForPasswordFlow: createAuthMiddlewareForPasswordFlow$1, createAuthMiddlewareForRefreshTokenFlow: createAuthMiddlewareForRefreshTokenFlow$1 }); function createCorrelationIdMiddleware(options) { return next => (request, response) => { const nextRequest = { ...request, headers: { ...request.headers, 'X-Correlation-ID': options.generate() } }; next(nextRequest, response); }; } function defineError(statusCode, message, meta = {}) { this.status = this.statusCode = this.code = 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 /* special code to indicate network errors */, ...args); } function HttpError(...args) { defineError.call(this, /* code will be passed as arg */...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 parseHeaders(headers) { if (headers.raw) // node-fetch return headers.raw(); // Tmp fix for Firefox until it supports iterables if (!headers.forEach) return {}; // whatwg-fetch const map = {}; headers.forEach((value, name) => { map[name] = value; }); return map; } // performs a proper buffer check function isBuffer(obj) { return obj != null && obj.constructor != null && typeof obj.constructor.isBuffer === 'function' && obj.constructor.isBuffer(obj); } 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}`; delete rest.uri; // remove the `uri` property from the response } const ResponseError = getErrorByCode(statusCode); if (ResponseError) return new ResponseError(errorMessage, rest); return new HttpError(statusCode, errorMessage, rest); } // calculates the delay duration exponentially // More info about the algorithm use here https://goo.gl/Xk8h5f function calcDelayDuration(retryCount, retryDelay, maxRetries, 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 maskAuthData(request, maskSensitiveHeaderData) { if (maskSensitiveHeaderData) { if (request && request.headers && request.headers.authorization) request.headers.authorization = 'Bearer ********'; if (request && request.headers && request.headers.Authorization) request.headers.Authorization = 'Bearer ********'; } } function createHttpMiddleware({ host, credentialsMode, includeResponseHeaders, includeOriginalRequest, includeRequestInErrorResponse = true, maskSensitiveHeaderData = true, headersWithStringBody = [], enableRetry, timeout, retryConfig: { // encourage exponential backoff to prevent spamming the server if down maxRetries = 10, backoff = true, retryDelay = 200, maxDelay = Infinity, // If set to true reinitialize the abort controller when the timeout is reached and apply the retry config retryOnAbort = false, retryCodes = [503] } = {}, fetch: fetcher, getAbortController }) { //nodejs v18 has the fetch available and not the version 16 if (!fetcher) throw new Error('`fetch` is not available. Please pass in `fetch` as an option or have it globally available.'); if (timeout && !getAbortController) throw new Error('`AbortController` is not available. Please pass in `getAbortController` as an option or have AbortController globally available when using timeout.'); let fetchFunction; if (fetcher) { fetchFunction = fetcher; } else { // `fetcher` is set here rather than the destructuring to ensure fetch is // declared before referencing it otherwise it would cause a `ReferenceError`. // For reference of this pattern: https://github.com/apollographql/apollo-link/blob/498b413a5b5199b0758ce898b3bb55451f57a2fa/packages/apollo-link-http/src/httpLink.ts#L49 fetchFunction = fetch; } if (!Array.isArray(retryCodes)) { throw new Error('`retryCodes` option must be an array of retry status (error) codes.'); } if (!Array.isArray(headersWithStringBody)) { throw new Error('`headersWithStringBody` option must be an array of strings'); } return next => (request, response) => { const url = host.replace(/\/$/, '') + request.uri; const requestHeader = { ...request.headers }; // If no content-type is provided, defaults to application/json 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 or application/graphql const body = ['application/json', 'application/graphql', ...headersWithStringBody].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'] = _.Buffer.byteLength(body).toString(); } const fetchOptions = { method: request.method, headers: requestHeader }; if (credentialsMode) { fetchOptions.credentialsMode = credentialsMode; } if (body) { fetchOptions.body = body; } let retryCount = 0; // wrap in a fn so we can retry if error occur function executeFetch() { // Kick off timer for abortController directly before fetch. let timer; let abortController; if (timeout) { // Initialize the abort controller in case we do a retry on an aborted request to rest the signal abortController = (getAbortController ? getAbortController() : null) || new AbortController(); fetchOptions.signal = abortController.signal; // Set the timer timer = setTimeout(() => { abortController.abort(); }, timeout); } fetchFunction(url, fetchOptions).then(res => { if (res.ok) { if (fetchOptions.method === 'HEAD') { next(request, { ...response, statusCode: res.status }); return; } res.text().then(result => { // Try to parse the response as JSON let parsed; try { parsed = result.length > 0 ? JSON.parse(result) : {}; } catch (err) { if (enableRetry && retryCount < maxRetries) { setTimeout(executeFetch, calcDelayDuration(retryCount, retryDelay, maxRetries, backoff, maxDelay)); retryCount += 1; return; } parsed = result; } const parsedResponse = { ...response, body: parsed, statusCode: res.status }; if (includeResponseHeaders) parsedResponse.headers = parseHeaders(res.headers); if (includeOriginalRequest) { parsedResponse.request = { ...fetchOptions }; maskAuthData(parsedResponse.request, maskSensitiveHeaderData); } next(request, parsedResponse); }).catch(err => { if (enableRetry && retryCount < maxRetries) { setTimeout(executeFetch, calcDelayDuration(retryCount, retryDelay, maxRetries, backoff, maxDelay)); retryCount += 1; return; } const error = new NetworkError(err.message, { ...(includeRequestInErrorResponse ? { originalRequest: request } : {}), retryCount }); maskAuthData(error.originalRequest, maskSensitiveHeaderData); next(request, { ...response, error, statusCode: 0 }); }); return; } // Server responded with an error. Try to parse it as JSON, then // return a proper error type with all necessary meta information. res.text().then(text => { // Try to parse the error response as JSON let parsed; try { parsed = JSON.parse(text); } catch (error) { parsed = text; } const error = createError({ statusCode: res.status, ...(includeRequestInErrorResponse ? { originalRequest: request } : res.status === 404 ? { uri: request.uri } : {}), retryCount, headers: parseHeaders(res.headers), ...(typeof parsed === 'object' ? { message: parsed.message, body: parsed } : { message: parsed, body: parsed }) }); if (enableRetry && (retryCodes.indexOf(error.statusCode) !== -1 || retryCodes?.indexOf(error.message) !== -1)) { if (retryCount < maxRetries) { setTimeout(executeFetch, calcDelayDuration(retryCount, retryDelay, maxRetries, backoff, maxDelay)); retryCount += 1; return; } } maskAuthData(error.originalRequest, maskSensitiveHeaderData); // Let the final resolver to reject the promise const parsedResponse = { ...response, error, statusCode: res.status }; next(request, parsedResponse); }); }, // We know that this is a "network" error thrown by the `fetch` library e => { // Retry when enabled and either the request was not aborted or retryOnAbort is enabled if (enableRetry && (retryOnAbort || !abortController || !abortController.signal)) { if (retryCount < maxRetries) { setTimeout(executeFetch, calcDelayDuration(retryCount, retryDelay, maxRetries, backoff, maxDelay)); retryCount += 1; return; } } const error = new NetworkError(e.message, { ...(includeRequestInErrorResponse ? { originalRequest: request } : {}), retryCount }); maskAuthData(error.originalRequest, maskSensitiveHeaderData); next(request, { ...response, error, statusCode: 0 }); }).finally(() => { clearTimeout(timer); }); } executeFetch(); }; } function createLoggerMiddleware() { return next => (request, response) => { const { error, body, statusCode } = response; console.log('Request: ', request); console.log('Response: ', { error, body, statusCode }); next(request, response); }; } function createQueueMiddleware({ concurrency = 20 }) { const queue = []; let runningCount = 0; const dequeue = next => { // We assume here that this task has been completed runningCount -= 1; // Check if there are any other pending tasks and execute them if (queue.length && runningCount <= concurrency) { const nextTask = queue.shift(); runningCount += 1; next(nextTask.request, nextTask.response); } }; return next => (request, response) => { // Override response `resolve` and `reject` to know when the request has // been completed and therefore trigger a pending task in the queue. const patchedResponse = { ...response, resolve(data) { // Resolve original promise response.resolve(data); dequeue(next); }, reject(error) { // Reject original promise response.reject(error); dequeue(next); } }; // Add task to the queue queue.push({ request, response: patchedResponse }); // If possible, run the task straight away if (runningCount < concurrency) { const nextTask = queue.shift(); runningCount += 1; next(nextTask.request, nextTask.response); } }; } var packageJson = { name: "@commercetools/sdk-client-v2", version: "3.0.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-sdk-client-v2.cjs.js", module: "dist/commercetools-sdk-client-v2.esm.js", browser: { "./dist/commercetools-sdk-client-v2.cjs.js": "./dist/commercetools-sdk-client-v2.browser.cjs.js", "./dist/commercetools-sdk-client-v2.esm.js": "./dist/commercetools-sdk-client-v2.browser.esm.js" }, devDependencies: { "common-tags": "1.8.2", dotenv: "16.4.5", jest: "29.7.0", nock: "^14.0.0-beta.19", "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" } }; /* 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) || '12'; // temporary fix for rn environment // const platformInfo = `(${process.platform}; ${process.arch})` // return `Node.js/${nodeVersion} ${platformInfo}` return `node.js/${nodeVersion}`; } function createUserAgent(options) { if (!options || Object.keys(options).length === 0 || !{}.hasOwnProperty.call(options, 'name')) throw new Error('Missing required option `name`'); // Main info const baseInfo = options.version ? `${options.name}/${options.version}` : options.name; // Library info let libraryInfo = null; if (options.libraryName && !options.libraryVersion) libraryInfo = options.libraryName;else if (options.libraryName && options.libraryVersion) libraryInfo = `${options.libraryName}/${options.libraryVersion}`; // Contact info let contactInfo = null; 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(' '); } function createUserAgentMiddleware(options) { const userAgent = createUserAgent({ ...options, name: `commercetools-sdk-javascript-v2/${packageJson.version}` }); return next => (request, response) => { const requestWithUserAgent = { ...request, headers: { ...request.headers, 'User-Agent': userAgent } }; next(requestWithUserAgent, response); }; } const { createAuthMiddlewareForPasswordFlow, createAuthMiddlewareForAnonymousSessionFlow, createAuthMiddlewareForClientCredentialsFlow, createAuthMiddlewareForRefreshTokenFlow, createAuthMiddlewareWithExistingToken } = authMiddlewares; 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, "telemetryMiddleware", void 0); _defineProperty(this, "beforeMiddleware", void 0); _defineProperty(this, "afterMiddleware", void 0); _defineProperty(this, "middlewares", []); } withProjectKey(key) { this.projectKey = key; return this; } defaultClient(baseUri, credentials, oauthUri, projectKey) { return this.withClientCredentialsFlow({ host: oauthUri, projectKey: projectKey || this.projectKey, credentials }).withHttpMiddleware({ host: baseUri, fetch: fetch }).withLoggerMiddleware().withUserAgentMiddleware(); } withAuthMiddleware(authMiddleware) { this.authMiddleware = authMiddleware; return this; } withMiddleware(middleware) { this.middlewares.push(middleware); return this; } withClientCredentialsFlow(options) { return this.withAuthMiddleware(createAuthMiddlewareForClientCredentialsFlow({ host: options.host || 'https://auth.europe-west1.gcp.commercetools.com', projectKey: options.projectKey || this.projectKey, credentials: { clientId: options.credentials.clientId || '', clientSecret: options.credentials.clientSecret || '' }, oauthUri: options.oauthUri || '', scopes: options.scopes, fetch: options.fetch || fetch, ...options })); } withPasswordFlow(options) { return this.withAuthMiddleware(createAuthMiddlewareForPasswordFlow({ host: options.host || 'https://auth.europe-west1.gcp.commercetools.com', projectKey: options.projectKey || this.projectKey, credentials: { clientId: options.credentials.clientId || '', clientSecret: options.credentials.clientSecret || '', user: { username: options.credentials.user.username || '', password: options.credentials.user.password || '' } }, fetch: options.fetch || fetch, ...options })); } withAnonymousSessionFlow(options) { return this.withAuthMiddleware(createAuthMiddlewareForAnonymousSessionFlow({ host: options.host || 'https://auth.europe-west1.gcp.commercetools.com', projectKey: this.projectKey || options.projectKey, credentials: { clientId: options.credentials.clientId || '', clientSecret: options.credentials.clientSecret || '', anonymousId: options.credentials.anonymousId || '' }, fetch: options.fetch || fetch, ...options })); } withRefreshTokenFlow(options) { return this.withAuthMiddleware(createAuthMiddlewareForRefreshTokenFlow({ host: options.host || 'https://auth.europe-west1.gcp.commercetools.com', projectKey: this.projectKey || options.projectKey, credentials: { clientId: options.credentials.clientId || '', clientSecret: options.credentials.clientSecret || '' }, fetch: options.fetch || fetch, refreshToken: options.refreshToken || '', ...options })); } withExistingTokenFlow(authorization, options) { return this.withAuthMiddleware(createAuthMiddlewareWithExistingToken(authorization, { force: options.force || true, ...options })); } withHttpMiddleware(options) { this.httpMiddleware = createHttpMiddleware({ host: options.host || 'https://api.europe-west1.gcp.commercetools.com', fetch: options.fetch || fetch, ...options }); return this; } withUserAgentMiddleware(options) { this.userAgentMiddleware = createUserAgentMiddleware(options); return this; } withQueueMiddleware(options) { this.queueMiddleware = createQueueMiddleware({ concurrency: options.concurrency || 20, ...options }); return this; } withLoggerMiddleware(options) { const { logger, ...rest } = options || {}; this.loggerMiddleware = typeof options?.logger == 'function' && options.logger(rest) || createLoggerMiddleware(); return this; } withCorrelationIdMiddleware(options) { this.correlationIdMiddleware = createCorrelationIdMiddleware({ generate: options.generate || null, ...options }); 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.middleware(rest); return this; } build() { const middlewares = this.middlewares.slice(); if (this.telemetryMiddleware) middlewares.push(this.telemetryMiddleware); if (this.correlationIdMiddleware) middlewares.push(this.correlationIdMiddleware); if (this.userAgentMiddleware) middlewares.push(this.userAgentMiddleware); if (this.authMiddleware) middlewares.push(this.authMiddleware); if (this.beforeMiddleware) middlewares.push(this.beforeMiddleware); if (this.queueMiddleware) middlewares.push(this.queueMiddleware); if (this.httpMiddleware) middlewares.push(this.httpMiddleware); if (this.afterMiddleware) middlewares.push(this.afterMiddleware); if (this.loggerMiddleware) middlewares.push(this.loggerMiddleware); return createClient({ middlewares }); } } exports.ClientBuilder = ClientBuilder; exports.Process = process$1; exports.createAuthForAnonymousSessionFlow = createAuthMiddlewareForAnonymousSessionFlow$1; exports.createAuthForClientCredentialsFlow = createAuthMiddlewareForClientCredentialsFlow$1; exports.createAuthForPasswordFlow = createAuthMiddlewareForPasswordFlow$1; exports.createAuthForRefreshTokenFlow = createAuthMiddlewareForRefreshTokenFlow$1; exports.createAuthWithExistingToken = createAuthMiddlewareWithExistingToken$1; exports.createClient = createClient; exports.createCorrelationIdMiddleware = createCorrelationIdMiddleware; exports.createHttpClient = createHttpMiddleware; exports.createLoggerMiddleware = createLoggerMiddleware; exports.createQueueMiddleware = createQueueMiddleware; exports.createUserAgentMiddleware = createUserAgentMiddleware; exports.getErrorByCode = getErrorByCode;