UNPKG

@sitecore-jss/sitecore-jss

Version:

This module is provided as a part of Sitecore JavaScript Rendering SDK. It contains the core JSS APIs (layout service) and utilities.

145 lines (144 loc) 7.62 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { GraphQLClient as Client } from 'graphql-request'; import parse from 'url-parse'; import debuggers from './debug'; import TimeoutPromise from './utils/timeout-promise'; /** * Represents a default retry strategy for handling retry attempts in case of specific HTTP status codes. * This class implements the RetryStrategy interface and provides methods to determine whether a request * should be retried and calculates the delay before the next retry attempt. */ export class DefaultRetryStrategy { /** * @param {object} options Configurable options for retry mechanism. * @param {number[]} [options.statusCodes] HTTP status codes to trigger retries on. Default is [429]. * @param {string[]} [options.errorCodes] Node error codes to trigger retries. Default is ['ECONNRESET', 'ETIMEDOUT', 'EPROTO']. * @param {number} [options.factor] Factor by which the delay increases with each retry attempt. Default is 2. */ constructor(options = {}) { this.statusCodes = options.statusCodes || [429]; this.errorCodes = options.errorCodes || ['ECONNRESET', 'ETIMEDOUT', 'EPROTO']; this.factor = options.factor || 2; } shouldRetry(error, attempt, retries) { var _a; const isStatusCodeError = ((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) !== undefined && this.statusCodes.includes(error.response.status); const isNodeErrorCode = error.code !== undefined && this.errorCodes.includes(error.code); return retries > 0 && attempt <= retries && (isStatusCodeError || isNodeErrorCode); } getDelay(error, attempt) { var _a; const rawHeaders = (_a = error.response) === null || _a === void 0 ? void 0 : _a.headers; const retryAfterHeader = rawHeaders === null || rawHeaders === void 0 ? void 0 : rawHeaders.get('Retry-After'); if (retryAfterHeader !== null && retryAfterHeader !== undefined && retryAfterHeader.trim() !== '') { const delaySeconds = Number.parseFloat(retryAfterHeader); return delaySeconds * 1000; } else { return Math.pow(this.factor, attempt - 1) * 1000; } } } /** * A GraphQL client for Sitecore APIs that uses the 'graphql-request' library. * https://github.com/prisma-labs/graphql-request */ export class GraphQLRequestClient { /** * Provides ability to execute graphql query using given `endpoint` * @param {string} endpoint The Graphql endpoint * @param {GraphQLRequestClientConfig} [clientConfig] GraphQL request client configuration. */ constructor(endpoint, clientConfig = {}) { var _a; this.endpoint = endpoint; this.headers = {}; if (clientConfig.apiKey) { this.headers.sc_apikey = clientConfig.apiKey; } if (clientConfig.headers) { this.headers = Object.assign(Object.assign({}, this.headers), clientConfig.headers); } if (!endpoint || !parse(endpoint).hostname) { throw new Error(`Invalid GraphQL endpoint '${endpoint}'. Verify that 'layoutServiceHost' property in 'scjssconfig.json' file or appropriate environment variable is set`); } this.timeout = clientConfig.timeout; this.retries = (_a = clientConfig.retries) !== null && _a !== void 0 ? _a : 3; this.retryStrategy = clientConfig.retryStrategy || new DefaultRetryStrategy({ statusCodes: [429, 502, 503, 504, 520, 521, 522, 523, 524] }); this.client = new Client(endpoint, { headers: this.headers, fetch: clientConfig.fetch, }); this.debug = clientConfig.debugger || debuggers.http; } /** * Factory method for creating a GraphQLRequestClientFactory. * @param {object} config - client configuration options. * @param {string} config.endpoint - endpoint * @param {string} [config.apiKey] - apikey */ static createClientFactory({ endpoint, apiKey, }) { return (config = {}) => new GraphQLRequestClient(endpoint, Object.assign(Object.assign({}, config), { apiKey })); } /** * Execute graphql request * @param {string | DocumentNode} query graphql query * @param {object} [variables] graphql variables * @param {RequestOptions} [options] Options for configuring a GraphQL request. */ request(query, variables, options) { return __awaiter(this, void 0, void 0, function* () { let attempt = 1; const retryer = () => __awaiter(this, void 0, void 0, function* () { // Note we don't have access to raw request/response with graphql-request // but we should log whatever we have. this.debug('request: %o', { url: this.endpoint, headers: Object.assign(Object.assign({}, this.headers), options === null || options === void 0 ? void 0 : options.headers), query, variables, }); const startTimestamp = Date.now(); const fetchWithOptionalTimeout = [this.client.request(query, variables, options === null || options === void 0 ? void 0 : options.headers)]; if (this.timeout) { this.abortTimeout = new TimeoutPromise(this.timeout); fetchWithOptionalTimeout.push(this.abortTimeout.start); } return Promise.race(fetchWithOptionalTimeout).then((data) => { var _a; (_a = this.abortTimeout) === null || _a === void 0 ? void 0 : _a.clear(); this.debug('response in %dms: %o', Date.now() - startTimestamp, data); return Promise.resolve(data); }, (error) => __awaiter(this, void 0, void 0, function* () { var _a, _b; (_a = this.abortTimeout) === null || _a === void 0 ? void 0 : _a.clear(); this.debug('response error: %o', error.response || error.message || error); const status = ((_b = error.response) === null || _b === void 0 ? void 0 : _b.status) || error.code; const shouldRetry = this.retryStrategy.shouldRetry(error, attempt, this.retries); if (shouldRetry) { const delayMs = this.retryStrategy.getDelay(error, attempt); this.debug('Error: %s. Retrying in %dms (attempt %d).', status, delayMs, attempt); attempt++; return new Promise((resolve) => setTimeout(resolve, delayMs)).then(retryer); } else { return Promise.reject(error); } })); }); return retryer(); }); } }