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.

203 lines (202 loc) • 9.13 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.mergeURLSearchParams = exports.escapeNonSpecialQuestionMarks = exports.areURLSearchParamsEqual = exports.isRegexOrUrl = exports.enforceCors = exports.getAllowedOriginsFromEnv = exports.isTimeoutError = exports.isAbsoluteUrl = void 0; exports.resolveUrl = resolveUrl; const is_server_1 = __importDefault(require("./is-server")); /** * note: encodeURIComponent is available via browser (window) or natively in node.js * if you use another js engine for server-side rendering you may not have native encodeURIComponent * and would then need to install a package for that functionality * @param {ParsedUrlQueryInput} params query string parameters * @returns {string} query string */ function getQueryString(params) { return Object.keys(params) .map((k) => `${encodeURIComponent(k)}=${encodeURIComponent(String(params[k]))}`) .join('&'); } /** * Resolves a base URL that may contain query string parameters and an additional set of query * string parameters into a unified string representation. * @param {string} urlBase the base URL that may contain query string parameters * @param {ParsedUrlQueryInput} params query string parameters * @returns a URL string * @throws {RangeError} if the provided url is an empty string */ function resolveUrl(urlBase, params = {}) { if (!urlBase) { throw new RangeError('url must be a non-empty string'); } // This is a better way to work with URLs since it handles different user input // edge cases. This works in Node and all browser except IE11. // https://developer.mozilla.org/en-US/docs/Web/API/URL // TODO: Verify our browser support requirements. if ((0, is_server_1.default)()) { const url = new URL(urlBase); for (const key in params) { if ({}.hasOwnProperty.call(params, key)) { url.searchParams.append(key, String(params[key])); } } const result = url.toString(); return result; } const qs = getQueryString(params); const result = urlBase.indexOf('?') !== -1 ? `${urlBase}&${qs}` : `${urlBase}?${qs}`; return result; } const isAbsoluteUrl = (url) => { if (!url) { return false; } if (typeof url !== 'string') { throw new TypeError('Expected a string'); } return /^[a-z][a-z0-9+.-]*:/.test(url); }; exports.isAbsoluteUrl = isAbsoluteUrl; /** * Indicates whether the error is a timeout error * @param {unknown} error error * @returns {boolean} is timeout error */ const isTimeoutError = (error) => { var _a; return ((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 408 || error.name === 'AbortError'; }; exports.isTimeoutError = isTimeoutError; /** * Converts a string value in a regex pattern allowing wildcard matching * @param {string} pattern input with wildcards i.e. site.*.com * @returns {string} modified string that can be used as regexp input */ const convertToWildcardRegex = (pattern) => { return ('^' + pattern .replace(/\//g, '\\/') .replace(/\./g, '\\.') .replace(/\*/g, '.*') + '$'); }; /** * Gets allowed origins from JSS_ALLOWED_ORIGINS env variable * @returns {string[]} list of allowed origins from JSS_ALLOWED_ORIGINS env variable */ const getAllowedOriginsFromEnv = () => process.env.JSS_ALLOWED_ORIGINS ? process.env.JSS_ALLOWED_ORIGINS.replace(' ', '').split(',') : []; exports.getAllowedOriginsFromEnv = getAllowedOriginsFromEnv; /** * Tests origin from incoming request against allowed origins list that can be * set in JSS's JSS_ALLOWED_ORIGINS env variable, passed via allowedOrigins param and/or * be already set in Access-Control-Allow-Origin by other logic. * Applies Access-Control-Allow-Origin and Access-Control-Allow-Methods on match * Also applies Access-Control-Allow-Headers for preflight requests * @param {IncomingMessage} req incoming request * @param {OutgoingMessage} res response to set CORS headers for * @param {string[]} [allowedOrigins] additional list of origins to test against * @returns true if incoming origin matches the allowed lists, false when it does not */ const enforceCors = (req, res, allowedOrigins) => { // origin in not present for non-CORS requests (e.g. server-side) - so we skip the checks if (!req.headers.origin) { return true; } // 3 sources of allowed origins are considered: // the env value const defaultAllowedOrigins = (0, exports.getAllowedOriginsFromEnv)(); // the allowedOrigins prop allowedOrigins = defaultAllowedOrigins.concat(allowedOrigins || []); // and the existing CORS header, if present (i.e. set by nextjs config) const presetCors = res.getHeader('Access-Control-Allow-Origin'); if (presetCors) { allowedOrigins.push(presetCors); } const origin = req.headers.origin; if (origin && allowedOrigins.some((allowedOrigin) => origin === allowedOrigin || new RegExp(convertToWildcardRegex(allowedOrigin)).test(origin))) { res.setHeader('Access-Control-Allow-Origin', origin); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, DELETE, PUT, PATCH'); // set the allowed headers for preflight requests if (req.method === 'OPTIONS') { res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); } return true; } return false; }; exports.enforceCors = enforceCors; /** * Determines whether the given input is a regular expression or resembles a URL. * @param {string} input - The input string to evaluate. * @returns {'regex' | 'url'} - Returns 'url' if the input looks like a URL, otherwise 'regex'. */ const isRegexOrUrl = (input) => { // Remove the trailing slash. input = input.slice(0, -1); // Check if the string resembles a URL. const isUrlLike = /^\/[a-zA-Z0-9\-\/]+(\?([a-zA-Z0-9\-_]+=[a-zA-Z0-9\-_]+)(&[a-zA-Z0-9\-_]+=[a-zA-Z0-9\-_]+)*)?$/.test(input); if (isUrlLike) { return 'url'; } // If it doesn't resemble a URL, it's likely a regular expression. return 'regex'; }; exports.isRegexOrUrl = isRegexOrUrl; /** * Compares two URLSearchParams objects to determine if they are equal. * @param {URLSearchParams} params1 - The first set of URL search parameters. * @param {URLSearchParams} params2 - The second set of URL search parameters. * @returns {boolean} - Returns true if the parameters are equal, otherwise false. */ const areURLSearchParamsEqual = (params1, params2) => { // Generates a sorted string representation of URL search parameters. const getSortedParamsString = (params) => { return [...params.entries()] .sort(([keyA], [keyB]) => keyA.localeCompare(keyB)) .map(([key, value]) => `${key}=${value}`) .join('&'); }; // Compare the sorted strings of both parameter sets. return getSortedParamsString(params1) === getSortedParamsString(params2); }; exports.areURLSearchParamsEqual = areURLSearchParamsEqual; /** * Escapes non-special "?" characters in a string or regex. * - For regular strings, it escapes all unescaped "?" characters by adding a backslash (`\`). * - For regex patterns (strings enclosed in `/.../`), it analyzes each "?" to determine if it has special meaning * (e.g., `?` in `(abc)?`, `.*?`, `(?!...)`) or is just a literal character. Only literal "?" characters are escaped. * @param {string} input - The input string or regex pattern. * @returns {string} - The modified string or regex with non-special "?" characters escaped. */ const escapeNonSpecialQuestionMarks = (input) => { // If the input is already a regex pattern (starts with ^ or ends with $), return it unchanged if (input.startsWith('^') || input.endsWith('$')) { return input; } // For non-regex strings, escape literal "?" characters return input.replace(/\?/g, '\\?'); }; exports.escapeNonSpecialQuestionMarks = escapeNonSpecialQuestionMarks; /** * Merges two URLSearchParams objects. If both objects contain the same key, the value from the second object overrides the first. * @param {URLSearchParams} params1 - The first set of URL search parameters. * @param {URLSearchParams} params2 - The second set of URL search parameters. * @returns {string} - A string representation of the merged URL search parameters. */ const mergeURLSearchParams = (params1, params2) => { const merged = new URLSearchParams(); // Add all keys and values from the first object. for (const [key, value] of params1.entries()) { merged.set(key, value); } // Add all keys and values from the second object, replacing existing ones. for (const [key, value] of params2.entries()) { merged.set(key, value); } return merged.toString(); }; exports.mergeURLSearchParams = mergeURLSearchParams;