diff --git a/CHANGELOG.md b/CHANGELOG.md index 27244d6513..11cdcef039 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,10 +18,11 @@ Our versioning strategy is as follows: * `[templates/react]` `[sitecore-jss-react]` Replace package 'deep-equal' with 'fast-deep-equal'. No functionality change only performance improvement ([#1719](https://github.com/Sitecore/jss/pull/1719)) ([#1665](https://github.com/Sitecore/jss/pull/1665)) * `[templates/nextjs-xmcloud]` `[sitecore-jss]` `[sitecore-jss-nextjs]` `[sitecore-jss-react]` Add support for loading appropriate stylesheets whenever a theme is applied to BYOC and SXA components by introducing new function getComponentLibraryStylesheetLinks, which replaces getFEAASLibraryStylesheetLinks (which has been marked as deprecated) ([#1722](https://github.com/Sitecore/jss/pull/1722)) * `[templates/nextjs-xmcloud]` `[sitecore-jss-nextjs]` Add protected endpoint which provides configuration information (the sitecore packages used by the app and all registered components) to be used to determine feature compatibility on Pages side. ([#1724](https://github.com/Sitecore/jss/pull/1724)) -* `[templates/nextjs]` `[templates/nextjs-styleguide]` Modify all GraphQLRequestClient import statements so that it gets imported from the /graphql submodule ([#1728](https://github.com/Sitecore/jss/pull/1728)) +* `[sitecore-jss-nextjs]` `[templates/nextjs]` [BYOC] Component Builder integration endpoint ([#1729](https://github.com/Sitecore/jss/pull/1729)) ### 🐛 Bug Fixes +* `[templates/nextjs]` `[templates/nextjs-styleguide]` Modify all GraphQLRequestClient import statements so that it gets imported from the /graphql submodule ([#1728](https://github.com/Sitecore/jss/pull/1728)) * `[sitecore-jss-nextjs]` Internal link in RichText is broken when nested tags are added ([#1718](https://github.com/Sitecore/jss/pull/1718)) * `[templates/node-headless-ssr-proxy]` `[node-headless-ssr-proxy]` Add sc_site qs parameter to Layout Service requests by default ([#1660](https://github.com/Sitecore/jss/pull/1660)) * `[templates/nextjs-sxa]` Fixed Image component when there is using Banner variant which set property background-image when image is empty. ([#1689](https://github.com/Sitecore/jss/pull/1689)) ([#1692](https://github.com/Sitecore/jss/pull/1692)) diff --git a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/lib/next-config/plugins/feaas.js b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/lib/next-config/plugins/feaas.js index d79aeadee4..47d8d6ee82 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/lib/next-config/plugins/feaas.js +++ b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/lib/next-config/plugins/feaas.js @@ -3,6 +3,15 @@ */ const feaasPlugin = (nextConfig = {}) => { return Object.assign({}, nextConfig, { + async rewrites() { + return [ + ...await nextConfig.rewrites(), + { + source: '/feaas-render', + destination: '/api/editing/feaas/render', + }, + ]; + }, webpack: (config, options) => { if (options.isServer) { // Force use of CommonJS on the server for FEAAS SDK since JSS also uses CommonJS entrypoint to FEAAS SDK. diff --git a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/middleware.ts b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/middleware.ts new file mode 100644 index 0000000000..8020e2e530 --- /dev/null +++ b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/middleware.ts @@ -0,0 +1,21 @@ +import type { NextRequest, NextFetchEvent } from 'next/server'; +import middleware from 'lib/middleware'; + +// eslint-disable-next-line +export default async function (req: NextRequest, ev: NextFetchEvent) { + return middleware(req, ev); +} + +export const config = { + /* + * Match all paths except for: + * 1. /api routes + * 2. /_next (Next.js internals) + * 3. /sitecore/api (Sitecore API routes) + * 4. /- (Sitecore media) + * 5. /healthz (Health check) + * 6. /feaas-render (FEaaS render) + * 7. all root files inside /public + */ + matcher: ['/', '/((?!api/|_next/|feaas-render|healthz|sitecore/api/|-/|favicon.ico|sc_logo.svg).*)'], +}; diff --git a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/pages/api/editing/feaas/render.ts b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/pages/api/editing/feaas/render.ts new file mode 100644 index 0000000000..3830d592cb --- /dev/null +++ b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/pages/api/editing/feaas/render.ts @@ -0,0 +1,16 @@ +import { FEAASRenderMiddleware } from '@sitecore-jss/sitecore-jss-nextjs/editing'; + +/** + * This Next.js API route is used to handle GET requests from Sitecore Component Builder. + * + * The `FEAASRenderMiddleware` will: + * 1. Enable Next.js Preview Mode. + * 2. Redirect the request to the /feaas/render page. + * - If "feaasSrc" query parameter is provided, the page will render a FEAAS component. + * - The page provides all the registered FEAAS components. + */ + +// Wire up the FEAASRenderMiddleware handler +const handler = new FEAASRenderMiddleware().getHandler(); + +export default handler; diff --git a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/pages/feaas/render.tsx b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/pages/feaas/render.tsx new file mode 100644 index 0000000000..2f3e2e30ff --- /dev/null +++ b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/pages/feaas/render.tsx @@ -0,0 +1,32 @@ +import { GetServerSideProps } from 'next'; +import BYOC from 'src/byoc'; +import * as FEAAS from '@sitecore-feaas/clientside/react'; + +/** + * The FEAASRender page is responsible for: + * - Rendering the FEAAS component if the "feaasSrc" is provided. + * - Rendering all the registered components. + * - The page is rendered only if it's requested by the api route (/api/editing/feaas/render) using the preview mode. + */ +const FEAASRender = ({ feaasSrc }: { feaasSrc: string }): JSX.Element => { + return ( + <> + {/** Render the component if the "feaasSrc" is provided */} + {feaasSrc && } + {/** Render all the registered components */} + + + ); +}; + +export const getServerSideProps: GetServerSideProps = async (context) => { + return { + props: { + feaasSrc: context.query.feaasSrc || null, + }, + // Don't show the page if it's not requested by the api route using the preview mode + notFound: !context.preview, + }; +}; + +export default FEAASRender; diff --git a/packages/sitecore-jss-nextjs/src/editing/editing-render-middleware.ts b/packages/sitecore-jss-nextjs/src/editing/editing-render-middleware.ts index d3d3bf5f93..789121278f 100644 --- a/packages/sitecore-jss-nextjs/src/editing/editing-render-middleware.ts +++ b/packages/sitecore-jss-nextjs/src/editing/editing-render-middleware.ts @@ -5,12 +5,9 @@ import { EDITING_COMPONENT_ID, RenderingType } from '@sitecore-jss/sitecore-jss/ import { parse } from 'node-html-parser'; import { EditingData } from './editing-data'; import { EditingDataService, editingDataService } from './editing-data-service'; -import { - QUERY_PARAM_EDITING_SECRET, - QUERY_PARAM_PROTECTION_BYPASS_SITECORE, - QUERY_PARAM_PROTECTION_BYPASS_VERCEL, -} from './constants'; +import { QUERY_PARAM_EDITING_SECRET } from './constants'; import { getJssEditingSecret } from '../utils/utils'; +import { RenderMiddlewareBase } from './render-middleware'; export interface EditingRenderMiddlewareConfig { /** @@ -52,7 +49,7 @@ export interface EditingRenderMiddlewareConfig { * Middleware / handler for use in the editing render Next.js API route (e.g. '/api/editing/render') * which is required for Sitecore editing support. */ -export class EditingRenderMiddleware { +export class EditingRenderMiddleware extends RenderMiddlewareBase { private editingDataService: EditingDataService; private dataFetcher: AxiosDataFetcher; private resolvePageUrl: (serverUrl: string, itemPath: string) => string; @@ -62,6 +59,8 @@ export class EditingRenderMiddleware { * @param {EditingRenderMiddlewareConfig} [config] Editing render middleware config */ constructor(config?: EditingRenderMiddlewareConfig) { + super(); + this.editingDataService = config?.editingDataService ?? editingDataService; this.dataFetcher = config?.dataFetcher ?? new AxiosDataFetcher({ debugger: debug.editing }); this.resolvePageUrl = config?.resolvePageUrl ?? this.defaultResolvePageUrl; @@ -76,28 +75,6 @@ export class EditingRenderMiddleware { return this.handler; } - /** - * Gets query parameters that should be passed along to subsequent requests - * @param {Object} query Object of query parameters from incoming URL - * @returns Object of approved query parameters - */ - protected getQueryParamsForPropagation = ( - query: Partial<{ [key: string]: string | string[] }> - ): { [key: string]: string } => { - const params: { [key: string]: string } = {}; - if (query[QUERY_PARAM_PROTECTION_BYPASS_SITECORE]) { - params[QUERY_PARAM_PROTECTION_BYPASS_SITECORE] = query[ - QUERY_PARAM_PROTECTION_BYPASS_SITECORE - ] as string; - } - if (query[QUERY_PARAM_PROTECTION_BYPASS_VERCEL]) { - params[QUERY_PARAM_PROTECTION_BYPASS_VERCEL] = query[ - QUERY_PARAM_PROTECTION_BYPASS_VERCEL - ] as string; - } - return params; - }; - private handler = async (req: NextApiRequest, res: NextApiResponse): Promise => { const { method, query, body, headers } = req; diff --git a/packages/sitecore-jss-nextjs/src/editing/feaas-render-middleware.test.ts b/packages/sitecore-jss-nextjs/src/editing/feaas-render-middleware.test.ts new file mode 100644 index 0000000000..3b98b23ab3 --- /dev/null +++ b/packages/sitecore-jss-nextjs/src/editing/feaas-render-middleware.test.ts @@ -0,0 +1,216 @@ +/* eslint-disable quotes */ +/* eslint-disable no-unused-expressions */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { expect, use } from 'chai'; +import { NextApiRequest, NextApiResponse } from 'next'; +import { + QUERY_PARAM_EDITING_SECRET, + QUERY_PARAM_PROTECTION_BYPASS_SITECORE, + QUERY_PARAM_PROTECTION_BYPASS_VERCEL, +} from './constants'; +import { FEAASRenderMiddleware } from './feaas-render-middleware'; +import { spy } from 'sinon'; +import sinonChai from 'sinon-chai'; + +use(sinonChai); + +type Query = { + [key: string]: string; +}; + +const mockRequest = (query?: Query, method?: string, host?: string) => { + return { + body: {}, + method: method ?? 'GET', + query: query ?? {}, + headers: { host: host ?? 'localhost:3000' }, + } as NextApiRequest; +}; + +const mockResponse = () => { + const res = {} as NextApiResponse; + res.status = spy(() => { + return res; + }); + res.send = spy(() => { + return res; + }); + res.setPreviewData = spy(() => { + return res; + }); + res.setHeader = spy(() => { + return res; + }); + res.redirect = spy(() => { + return res; + }); + + return res; +}; + +describe('FEAASRenderMiddleware', () => { + const secret = 'secret1234'; + + beforeEach(() => { + process.env.JSS_EDITING_SECRET = secret; + }); + + after(() => { + delete process.env.JSS_EDITING_SECRET; + }); + + it('should handle request', async () => { + const query = {} as Query; + query[QUERY_PARAM_EDITING_SECRET] = secret; + + const req = mockRequest(query); + const res = mockResponse(); + + const middleware = new FEAASRenderMiddleware(); + const handler = middleware.getHandler(); + + await handler(req, res); + + expect(res.setPreviewData, 'set preview mode w/ data').to.have.been.calledWith({}); + expect(res.redirect).to.have.been.calledOnce; + expect(res.redirect).to.have.been.calledWith('/feaas/render'); + }); + + it('should handle request when feaasSrc query parameter is present', async () => { + const query = { + feaasSrc: 'https://feaas.blob.core.windows.net/components/xxx/xyz/responsive/staged', + } as Query; + query[QUERY_PARAM_EDITING_SECRET] = secret; + + const req = mockRequest(query); + const res = mockResponse(); + + const middleware = new FEAASRenderMiddleware(); + const handler = middleware.getHandler(); + + await handler(req, res); + + expect(res.setPreviewData, 'set preview mode w/ data').to.have.been.calledWith({}); + expect(res.redirect).to.have.been.calledOnce; + expect(res.redirect).to.have.been.calledWith( + '/feaas/render?feaasSrc=https%3A%2F%2Ffeaas.blob.core.windows.net%2Fcomponents%2Fxxx%2Fxyz%2Fresponsive%2Fstaged' + ); + }); + + it('should throw error', async () => { + const query = {} as Query; + query[QUERY_PARAM_EDITING_SECRET] = secret; + + const req = mockRequest(query); + const res = mockResponse(); + + res.setPreviewData = spy(() => { + throw new Error('Test Error'); + }); + + const middleware = new FEAASRenderMiddleware(); + const handler = middleware.getHandler(); + + await handler(req, res); + + expect(res.setPreviewData, 'set preview mode w/ data').to.have.been.calledWith({}); + expect(res.status).to.have.been.calledOnce; + expect(res.status).to.have.been.calledWith(500); + expect(res.send).to.have.been.calledOnce; + expect(res.send).to.have.been.calledWith('Error: Test Error'); + }); + + it('should respondWith 405 for unsupported method', async () => { + const req = mockRequest({}, 'POST'); + const res = mockResponse(); + + const middleware = new FEAASRenderMiddleware(); + const handler = middleware.getHandler(); + + await handler(req, res); + + expect(res.setHeader).to.have.been.calledWithExactly('Allow', 'GET'); + expect(res.status).to.have.been.calledOnce; + expect(res.status).to.have.been.calledWith(405); + expect(res.send).to.have.been.calledOnce; + expect(res.send).to.have.been.calledWith( + "Invalid request method 'POST'" + ); + }); + + it('should respond with 401 for missing secret', async () => { + const query = {} as Query; + const req = mockRequest(query); + const res = mockResponse(); + + const middleware = new FEAASRenderMiddleware(); + const handler = middleware.getHandler(); + + await handler(req, res); + + expect(res.status).to.have.been.calledOnce; + expect(res.status).to.have.been.calledWith(401); + expect(res.send).to.have.been.calledOnce; + expect(res.send).to.have.been.calledWith('Missing or invalid secret'); + }); + + it('should respond with 401 for invalid secret', async () => { + const query = {} as Query; + query[QUERY_PARAM_EDITING_SECRET] = 'nope'; + const req = mockRequest(query); + const res = mockResponse(); + + const middleware = new FEAASRenderMiddleware(); + const handler = middleware.getHandler(); + + await handler(req, res); + + expect(res.status).to.have.been.calledOnce; + expect(res.status).to.have.been.calledWith(401); + expect(res.send).to.have.been.calledOnce; + expect(res.send).to.have.been.calledWith('Missing or invalid secret'); + }); + + it('should use custom pageUrl', async () => { + const query = {} as Query; + query[QUERY_PARAM_EDITING_SECRET] = secret; + const req = mockRequest(query); + const res = mockResponse(); + + const pageUrl = '/some/path/feaas/render'; + + const middleware = new FEAASRenderMiddleware({ + pageUrl, + }); + const handler = middleware.getHandler(); + + await handler(req, res); + + expect(res.redirect).to.have.been.calledOnce; + expect(res.redirect).to.have.been.calledWith(pageUrl); + }); + + it('should pass along protection bypass query parameters', async () => { + const query = {} as Query; + const bypassTokenSitecore = 'token1234Sitecore'; + const bypassTokenVercel = 'token1234Vercel'; + query[QUERY_PARAM_EDITING_SECRET] = secret; + query[QUERY_PARAM_PROTECTION_BYPASS_SITECORE] = bypassTokenSitecore; + query[QUERY_PARAM_PROTECTION_BYPASS_VERCEL] = bypassTokenVercel; + const previewData = {}; + + const req = mockRequest(query); + const res = mockResponse(); + + const middleware = new FEAASRenderMiddleware(); + const handler = middleware.getHandler(); + + await handler(req, res); + + expect(res.setPreviewData, 'set preview mode w/ data').to.have.been.calledWith(previewData); + expect(res.redirect).to.have.been.calledOnce; + expect(res.redirect).to.have.been.calledWith( + '/feaas/render?x-sitecore-protection-bypass=token1234Sitecore&x-vercel-protection-bypass=token1234Vercel' + ); + }); +}); diff --git a/packages/sitecore-jss-nextjs/src/editing/feaas-render-middleware.ts b/packages/sitecore-jss-nextjs/src/editing/feaas-render-middleware.ts new file mode 100644 index 0000000000..98c2e53a28 --- /dev/null +++ b/packages/sitecore-jss-nextjs/src/editing/feaas-render-middleware.ts @@ -0,0 +1,111 @@ +import { NextApiRequest, NextApiResponse } from 'next'; +import { debug } from '@sitecore-jss/sitecore-jss'; +import { QUERY_PARAM_EDITING_SECRET } from './constants'; +import { getJssEditingSecret } from '../utils/utils'; +import { RenderMiddlewareBase } from './render-middleware'; + +/** + * Configuration for `FEAASRenderMiddleware`. + */ +export interface FEAASRenderMiddlewareConfig { + /** + * Defines FEAAS page route to render. + * This may be necessary for certain custom Next.js routing configurations. + * @default /feaas/render + */ + pageUrl?: string; +} + +/** + * Middleware / handler for use in the feaas render Next.js API route (e.g. '/api/editing/feaas/render') + * which is required for Sitecore editing support. + */ +export class FEAASRenderMiddleware extends RenderMiddlewareBase { + private pageUrl: string; + private defaultPageUrl = '/feaas/render'; + + /** + * @param {EditingRenderMiddlewareConfig} [config] Editing render middleware config + */ + constructor(protected config?: FEAASRenderMiddlewareConfig) { + super(); + + this.pageUrl = config?.pageUrl ?? this.defaultPageUrl; + } + + /** + * Gets the Next.js API route handler + * @returns route handler + */ + public getHandler(): (req: NextApiRequest, res: NextApiResponse) => Promise { + return this.handler; + } + + private handler = async (req: NextApiRequest, res: NextApiResponse): Promise => { + const { method, query, headers } = req; + + const startTimestamp = Date.now(); + + debug.editing('feaas render middleware start: %o', { + method, + query, + headers, + }); + + if (method !== 'GET') { + debug.editing('invalid method - sent %s expected GET', method); + res.setHeader('Allow', 'GET'); + return res.status(405).send(`Invalid request method '${method}'`); + } + + // Validate secret + const secret = query[QUERY_PARAM_EDITING_SECRET]; + if (secret !== getJssEditingSecret()) { + debug.editing( + 'invalid editing secret - sent "%s" expected "%s"', + secret, + getJssEditingSecret() + ); + return res.status(401).send('Missing or invalid secret'); + } + + try { + // Get query string parameters to propagate on subsequent requests (e.g. for deployment protection bypass) + const params = this.getQueryParamsForPropagation(query); + + // Enable Next.js Preview Mode + res.setPreviewData({}); + + const queryParams = new URLSearchParams(); + + for (const key in params) { + if ({}.hasOwnProperty.call(params, key)) { + queryParams.append(key, params[key]); + } + } + + // Pass "feaasSrc" in case a FEAASComponent is being requested + if (query.feaasSrc) { + queryParams.append('feaasSrc', query.feaasSrc as string); + } + + const redirectUrl = + this.pageUrl + (queryParams.toString() ? `?${queryParams.toString()}` : ''); + + debug.editing('redirecting to page route %s', redirectUrl); + + debug.editing('feaas render middleware end in %dms', Date.now() - startTimestamp); + + res.redirect(redirectUrl); + } catch (err) { + const error = err as Record; + + console.info( + // eslint-disable-next-line quotes + "Hint: for non-standard server or Next.js route configurations, you may need to override the 'pageUrl' available on the 'FEAASRenderMiddleware' config." + ); + + res.status(500).send(`${error}`); + } + }; +} diff --git a/packages/sitecore-jss-nextjs/src/editing/index.ts b/packages/sitecore-jss-nextjs/src/editing/index.ts index 0c55468641..98e7ea60ab 100644 --- a/packages/sitecore-jss-nextjs/src/editing/index.ts +++ b/packages/sitecore-jss-nextjs/src/editing/index.ts @@ -15,6 +15,7 @@ export { editingDataService, } from './editing-data-service'; export { VercelEditingDataCache } from './vercel-editing-data-cache'; +export { FEAASRenderMiddleware, FEAASRenderMiddlewareConfig } from './feaas-render-middleware'; export { EditingConfigMiddleware, EditingConfigMiddlewareConfig, diff --git a/packages/sitecore-jss-nextjs/src/editing/render-middleware.test.ts b/packages/sitecore-jss-nextjs/src/editing/render-middleware.test.ts new file mode 100644 index 0000000000..7b56134fe5 --- /dev/null +++ b/packages/sitecore-jss-nextjs/src/editing/render-middleware.test.ts @@ -0,0 +1,38 @@ +/* eslint-disable dot-notation */ +import chai from 'chai'; +import chaiString from 'chai-string'; +import { RenderMiddlewareBase } from './render-middleware'; +import { + QUERY_PARAM_EDITING_SECRET, + QUERY_PARAM_PROTECTION_BYPASS_SITECORE, + QUERY_PARAM_PROTECTION_BYPASS_VERCEL, +} from './constants'; + +const expect = chai.use(chaiString).expect; + +type Query = { + [key: string]: string; +}; + +describe('RenderMiddlewareBase', () => { + class SampleMiddleware extends RenderMiddlewareBase {} + + describe('getQueryParamsForPropagation', () => { + it('should construct query params for protection bypass', () => { + const middleware = new SampleMiddleware(); + + const secret = 'secret1234'; + const query = {} as Query; + const bypassTokenSitecore = 'token1234Sitecore'; + const bypassTokenVercel = 'token1234Vercel'; + query[QUERY_PARAM_EDITING_SECRET] = secret; + query[QUERY_PARAM_PROTECTION_BYPASS_SITECORE] = bypassTokenSitecore; + query[QUERY_PARAM_PROTECTION_BYPASS_VERCEL] = bypassTokenVercel; + + expect(middleware['getQueryParamsForPropagation'](query)).to.deep.equal({ + [QUERY_PARAM_PROTECTION_BYPASS_SITECORE]: bypassTokenSitecore, + [QUERY_PARAM_PROTECTION_BYPASS_VERCEL]: bypassTokenVercel, + }); + }); + }); +}); diff --git a/packages/sitecore-jss-nextjs/src/editing/render-middleware.ts b/packages/sitecore-jss-nextjs/src/editing/render-middleware.ts new file mode 100644 index 0000000000..2d2503efd9 --- /dev/null +++ b/packages/sitecore-jss-nextjs/src/editing/render-middleware.ts @@ -0,0 +1,31 @@ +import { + QUERY_PARAM_PROTECTION_BYPASS_SITECORE, + QUERY_PARAM_PROTECTION_BYPASS_VERCEL, +} from './constants'; + +/** + * Base class for middleware that handles pages and components rendering in Sitecore Editors. + */ +export abstract class RenderMiddlewareBase { + /** + * Gets query parameters that should be passed along to subsequent requests (e.g. for deployment protection bypass) + * @param {Object} query Object of query parameters from incoming URL + * @returns Object of approved query parameters + */ + protected getQueryParamsForPropagation = ( + query: Partial<{ [key: string]: string | string[] }> + ): { [key: string]: string } => { + const params: { [key: string]: string } = {}; + if (query[QUERY_PARAM_PROTECTION_BYPASS_SITECORE]) { + params[QUERY_PARAM_PROTECTION_BYPASS_SITECORE] = query[ + QUERY_PARAM_PROTECTION_BYPASS_SITECORE + ] as string; + } + if (query[QUERY_PARAM_PROTECTION_BYPASS_VERCEL]) { + params[QUERY_PARAM_PROTECTION_BYPASS_VERCEL] = query[ + QUERY_PARAM_PROTECTION_BYPASS_VERCEL + ] as string; + } + return params; + }; +}