From fdd5dd13ff7cb08b2c4b7bf833e90ae583b656fb Mon Sep 17 00:00:00 2001 From: illiakovalenko Date: Thu, 26 Oct 2023 12:16:18 +0300 Subject: [PATCH 1/9] [Next.js] JSS Edge Proxy and Context Support --- .../scripts/config/plugins/multisite.ts | 15 ++--- .../lib/page-props-factory/plugins/site.ts | 2 +- .../plugins/graphql-sitemap-service.ts | 4 +- .../src/components/CdpPageView.tsx | 2 +- .../src/lib/middleware/plugins/personalize.ts | 7 +-- .../src/lib/site-resolver/plugins/default.ts | 2 +- .../scripts/disconnected-mode-proxy.ts | 2 +- .../src/lib/middleware/plugins/redirects.ts | 6 +- .../templates/nextjs-sxa/src/pages/404.tsx | 6 +- .../templates/nextjs-sxa/src/pages/500.tsx | 6 +- .../src/templates/nextjs/.env | 22 ++++++- .../templates/nextjs/scripts/config/index.ts | 4 +- .../nextjs/scripts/config/plugins/fallback.ts | 9 +++ .../scripts/config/plugins/package-json.ts | 2 +- .../nextjs/scripts/generate-config.ts | 4 +- .../src/templates/nextjs/src/byoc/index.ts | 9 +++ .../src/lib/dictionary-service-factory.ts | 4 +- .../nextjs/src/lib/graphql-client-factory.ts | 21 +++++++ .../nextjs/src/lib/layout-service-factory.ts | 4 +- .../lib/page-props-factory/plugins/site.ts | 2 +- .../src/lib/site-resolver/plugins/default.ts | 2 +- .../plugins/graphql-sitemap-service.ts | 6 +- packages/sitecore-jss-nextjs/src/index.ts | 4 +- .../services/base-graphql-sitemap-service.ts | 28 ++++++++- .../src/graphql-request-client.test.ts | 15 +++++ .../src/graphql-request-client.ts | 26 ++++++++ .../src/graphql/graphql-edge-proxy.test.ts | 18 ++++++ .../src/graphql/graphql-edge-proxy.ts | 8 +++ packages/sitecore-jss/src/graphql/index.ts | 3 + .../i18n/graphql-dictionary-service.test.ts | 21 +++++++ .../src/i18n/graphql-dictionary-service.ts | 24 +++++++- packages/sitecore-jss/src/index.ts | 2 + .../src/layout/graphql-layout-service.test.ts | 60 +++++++++++++++++++ .../src/layout/graphql-layout-service.ts | 23 ++++++- .../graphql-personalize-service.test.ts | 19 ++++++ .../graphql-personalize-service.ts | 29 ++++++++- .../site/graphql-error-pages-service.test.ts | 22 +++++++ .../src/site/graphql-error-pages-service.ts | 23 ++++++- .../site/graphql-redirects-service.test.ts | 17 ++++++ .../src/site/graphql-redirects-service.ts | 23 ++++++- .../src/site/graphql-robots-service.test.ts | 17 ++++++ .../src/site/graphql-robots-service.ts | 22 ++++++- .../src/site/graphql-siteinfo-service.test.ts | 39 +++++++++++- .../src/site/graphql-siteinfo-service.ts | 22 ++++++- .../src/site/graphql-sitemap-service.test.ts | 18 ++++++ .../src/site/graphql-sitemap-service.ts | 22 ++++++- 46 files changed, 582 insertions(+), 64 deletions(-) create mode 100644 packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory.ts create mode 100644 packages/sitecore-jss/src/graphql/graphql-edge-proxy.test.ts create mode 100644 packages/sitecore-jss/src/graphql/graphql-edge-proxy.ts diff --git a/packages/create-sitecore-jss/src/templates/nextjs-multisite/scripts/config/plugins/multisite.ts b/packages/create-sitecore-jss/src/templates/nextjs-multisite/scripts/config/plugins/multisite.ts index fbe10ee36f..e5a76e39d3 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs-multisite/scripts/config/plugins/multisite.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs-multisite/scripts/config/plugins/multisite.ts @@ -1,6 +1,7 @@ import chalk from 'chalk'; -import { ConfigPlugin, JssConfig } from '..'; import { GraphQLSiteInfoService, SiteInfo } from '@sitecore-jss/sitecore-jss-nextjs'; +import { ConfigPlugin, JssConfig } from '..'; +import { graphQLClientFactory } from 'lib/graphql-client-factory'; /** * This plugin will set the "sites" config prop. @@ -13,19 +14,15 @@ class MultisitePlugin implements ConfigPlugin { async exec(config: JssConfig) { let sites: SiteInfo[] = []; - const endpoint = config.graphQLEndpoint; - const apiKey = config.sitecoreApiKey; + const endpoint = config.sitecoreEdgeContextId ? config.sitecoreEdgeUrl : config.graphQLEndpoint; - if (!endpoint || !apiKey) { - console.warn( - chalk.yellow('Skipping site information fetch (missing GraphQL endpoint or API key).') - ); + if (!endpoint) { + console.warn(chalk.yellow('Skipping site information fetch (missing GraphQL endpoint).')); } else { console.log(`Fetching site information from ${endpoint}`); try { const siteInfoService = new GraphQLSiteInfoService({ - endpoint, - apiKey, + clientFactory: graphQLClientFactory, }); sites = await siteInfoService.fetchSiteInfo(); } catch (error) { diff --git a/packages/create-sitecore-jss/src/templates/nextjs-multisite/src/lib/page-props-factory/plugins/site.ts b/packages/create-sitecore-jss/src/templates/nextjs-multisite/src/lib/page-props-factory/plugins/site.ts index 81702d8d0e..e21b029050 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs-multisite/src/lib/page-props-factory/plugins/site.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs-multisite/src/lib/page-props-factory/plugins/site.ts @@ -19,7 +19,7 @@ class SitePlugin implements Plugin { : context.params.path ?? '/'; // Get site name (from path) - const siteData = getSiteRewriteData(path, config.jssAppName); + const siteData = getSiteRewriteData(path, config.siteName); // Resolve site by name props.site = siteResolver.getByName(siteData.siteName); diff --git a/packages/create-sitecore-jss/src/templates/nextjs-multisite/src/lib/sitemap-fetcher/plugins/graphql-sitemap-service.ts b/packages/create-sitecore-jss/src/templates/nextjs-multisite/src/lib/sitemap-fetcher/plugins/graphql-sitemap-service.ts index 2c258b0e50..c610bc6d56 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs-multisite/src/lib/sitemap-fetcher/plugins/graphql-sitemap-service.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs-multisite/src/lib/sitemap-fetcher/plugins/graphql-sitemap-service.ts @@ -8,14 +8,14 @@ import config from 'temp/config'; import { SitemapFetcherPlugin } from '..'; import { GetStaticPathsContext } from 'next'; import { siteResolver } from 'lib/site-resolver'; +import { graphQLClientFactory } from 'lib/graphql-client'; class GraphqlSitemapServicePlugin implements SitemapFetcherPlugin { _graphqlSitemapService: MultisiteGraphQLSitemapService; constructor() { this._graphqlSitemapService = new MultisiteGraphQLSitemapService({ - endpoint: config.graphQLEndpoint, - apiKey: config.sitecoreApiKey, + clientFactory: graphQLClientFactory, sites: [...new Set(siteResolver.sites.map((site: SiteInfo) => site.name))], }); } diff --git a/packages/create-sitecore-jss/src/templates/nextjs-personalize/src/components/CdpPageView.tsx b/packages/create-sitecore-jss/src/templates/nextjs-personalize/src/components/CdpPageView.tsx index 28f7c8d3fc..190c0af1d7 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs-personalize/src/components/CdpPageView.tsx +++ b/packages/create-sitecore-jss/src/templates/nextjs-personalize/src/components/CdpPageView.tsx @@ -68,7 +68,7 @@ const CdpPageView = (): JSX.Element => { return; } - const siteInfo = siteResolver.getByName(site?.name || config.jssAppName); + const siteInfo = siteResolver.getByName(site?.name || config.siteName); const language = route.itemLanguage || config.defaultLanguage; const scope = process.env.NEXT_PUBLIC_PERSONALIZE_SCOPE; diff --git a/packages/create-sitecore-jss/src/templates/nextjs-personalize/src/lib/middleware/plugins/personalize.ts b/packages/create-sitecore-jss/src/templates/nextjs-personalize/src/lib/middleware/plugins/personalize.ts index 2234ee2901..01da7037c3 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs-personalize/src/lib/middleware/plugins/personalize.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs-personalize/src/lib/middleware/plugins/personalize.ts @@ -1,7 +1,7 @@ import { NextRequest, NextResponse } from 'next/server'; import { PersonalizeMiddleware } from '@sitecore-jss/sitecore-jss-nextjs/middleware'; import { MiddlewarePlugin } from '..'; -import config from 'temp/config'; +import { graphQLClientFactory } from 'lib/graphql-client-factory'; import { siteResolver } from 'lib/site-resolver'; /** @@ -24,13 +24,12 @@ class PersonalizePlugin implements MiddlewarePlugin { this.personalizeMiddleware = new PersonalizeMiddleware({ // Configuration for your Sitecore Experience Edge endpoint edgeConfig: { - endpoint: config.graphQLEndpoint, - apiKey: config.sitecoreApiKey, timeout: (process.env.PERSONALIZE_MIDDLEWARE_EDGE_TIMEOUT && parseInt(process.env.PERSONALIZE_MIDDLEWARE_EDGE_TIMEOUT)) || 400, scope: process.env.NEXT_PUBLIC_PERSONALIZE_SCOPE, + clientFactory: graphQLClientFactory, }, // Configuration for your Sitecore CDP endpoint cdpConfig: { @@ -52,7 +51,7 @@ class PersonalizePlugin implements MiddlewarePlugin { // Site resolver implementation siteResolver, // Personalize middleware will use PosResolver.resolve(site, language) (same as CdpPageView) by default to get point of sale. - // You can also pass a custom point of sale resolver into middleware to override it like so: + // You can also pass a custom point of sale resolver into middleware to override it like so: // getPointOfSale: (site, language) => { ... } }); } diff --git a/packages/create-sitecore-jss/src/templates/nextjs-personalize/src/lib/site-resolver/plugins/default.ts b/packages/create-sitecore-jss/src/templates/nextjs-personalize/src/lib/site-resolver/plugins/default.ts index 6fcb0af050..5244f8e268 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs-personalize/src/lib/site-resolver/plugins/default.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs-personalize/src/lib/site-resolver/plugins/default.ts @@ -13,7 +13,7 @@ class DefaultPlugin implements SiteResolverPlugin { exec(sites: SiteInfo[]): SiteInfo[] { // Add default/configured site sites.unshift({ - name: config.jssAppName, + name: config.siteName, language: config.defaultLanguage, hostName: '*', pointOfSale, diff --git a/packages/create-sitecore-jss/src/templates/nextjs-styleguide/scripts/disconnected-mode-proxy.ts b/packages/create-sitecore-jss/src/templates/nextjs-styleguide/scripts/disconnected-mode-proxy.ts index 240b8b2ca2..3f509d5301 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs-styleguide/scripts/disconnected-mode-proxy.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs-styleguide/scripts/disconnected-mode-proxy.ts @@ -18,7 +18,7 @@ const touchToReloadFilePath = 'src/temp/config.js'; const serverOptions = { appRoot: path.join(__dirname, '..'), - appName: config.jssAppName, + appName: config.siteName, // Prevent require of ./sitecore/definitions/config.js, because ts-node is running requireArg: null, watchPaths: ['./data'], diff --git a/packages/create-sitecore-jss/src/templates/nextjs-sxa/src/lib/middleware/plugins/redirects.ts b/packages/create-sitecore-jss/src/templates/nextjs-sxa/src/lib/middleware/plugins/redirects.ts index c1333ea780..2347e555ba 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs-sxa/src/lib/middleware/plugins/redirects.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs-sxa/src/lib/middleware/plugins/redirects.ts @@ -1,8 +1,8 @@ import { NextRequest, NextResponse } from 'next/server'; import { RedirectsMiddleware } from '@sitecore-jss/sitecore-jss-nextjs/middleware'; -import config from 'temp/config'; import { MiddlewarePlugin } from '..'; import { siteResolver } from 'lib/site-resolver'; +import { graphQLClientFactory } from 'lib/graphql-client-factory'; class RedirectsPlugin implements MiddlewarePlugin { private redirectsMiddleware: RedirectsMiddleware; @@ -10,8 +10,8 @@ class RedirectsPlugin implements MiddlewarePlugin { constructor() { this.redirectsMiddleware = new RedirectsMiddleware({ - endpoint: config.graphQLEndpoint, - apiKey: config.sitecoreApiKey, + // Client factory implementation + clientFactory: graphQLClientFactory, // These are all the locales you support in your application. // These should match those in your next.config.js (i18n.locales). locales: ['en'], diff --git a/packages/create-sitecore-jss/src/templates/nextjs-sxa/src/pages/404.tsx b/packages/create-sitecore-jss/src/templates/nextjs-sxa/src/pages/404.tsx index aef20616ea..413cb614d0 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs-sxa/src/pages/404.tsx +++ b/packages/create-sitecore-jss/src/templates/nextjs-sxa/src/pages/404.tsx @@ -10,6 +10,7 @@ import { componentBuilder } from 'temp/componentBuilder'; import Layout from 'src/Layout'; import { GetStaticProps } from 'next'; import { siteResolver } from 'lib/site-resolver'; +import { graphQLClientFactory } from 'lib/graphql-client-factory'; const Custom404 = (props: SitecorePageProps): JSX.Element => { if (!(props && props.layoutData)) { @@ -27,10 +28,9 @@ const Custom404 = (props: SitecorePageProps): JSX.Element => { }; export const getStaticProps: GetStaticProps = async (context) => { - const site = siteResolver.getByName(config.jssAppName); + const site = siteResolver.getByName(config.siteName); const errorPagesService = new GraphQLErrorPagesService({ - endpoint: config.graphQLEndpoint, - apiKey: config.sitecoreApiKey, + clientFactory: graphQLClientFactory, siteName: site.name, language: context.locale || config.defaultLanguage, retries: diff --git a/packages/create-sitecore-jss/src/templates/nextjs-sxa/src/pages/500.tsx b/packages/create-sitecore-jss/src/templates/nextjs-sxa/src/pages/500.tsx index 15d33bc326..edf954903e 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs-sxa/src/pages/500.tsx +++ b/packages/create-sitecore-jss/src/templates/nextjs-sxa/src/pages/500.tsx @@ -10,6 +10,7 @@ import { componentBuilder } from 'temp/componentBuilder'; import { GetStaticProps } from 'next'; import config from 'temp/config'; import { siteResolver } from 'lib/site-resolver'; +import { graphQLClientFactory } from 'lib/graphql-client-factory'; /** * Rendered in case if we have 500 error @@ -43,10 +44,9 @@ const Custom500 = (props: SitecorePageProps): JSX.Element => { }; export const getStaticProps: GetStaticProps = async (context) => { - const site = siteResolver.getByName(config.jssAppName); + const site = siteResolver.getByName(config.siteName); const errorPagesService = new GraphQLErrorPagesService({ - endpoint: config.graphQLEndpoint, - apiKey: config.sitecoreApiKey, + clientFactory: graphQLClientFactory, siteName: site.name, language: context.locale || context.defaultLocale || config.defaultLanguage, retries: diff --git a/packages/create-sitecore-jss/src/templates/nextjs/.env b/packages/create-sitecore-jss/src/templates/nextjs/.env index d4dba6806e..a3317dd586 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs/.env +++ b/packages/create-sitecore-jss/src/templates/nextjs/.env @@ -17,6 +17,20 @@ PUBLIC_URL=http://localhost:3000 # We recommend an alphanumeric value of at least 16 characters. JSS_EDITING_SECRET= +# ================================ + +# ===== Sitecore Edge Platform ====== +# (Preview / Delivery Edge environment variables should be set empty) + +# Your Sitecore Edge Platform URL. +SITECORE_EDGE_URL=https://edge-platform.sitecorecloud.io + +# Your unified Sitecore Edge Context Id is needed to build the app. +SITECORE_EDGE_CONTEXT_ID= + +# ====== Sitecore Preview / Delivery Edge ====== +# (Sitecore Edge Proxy environment variables should be set empty, otherwise they will be prioritized and applied) + # Your Sitecore API key is needed to build the app. Typically, the API key is # defined in `scjssconfig.json` (as `sitecore.apiKey`). This file may not exist # when building locally (if you've never run `jss setup`), or when building in a @@ -36,8 +50,12 @@ SITECORE_API_HOST= # the resolved Sitecore API hostname + the `graphQLEndpointPath` defined in your `package.json`. GRAPH_QL_ENDPOINT= -# Your JSS app name (also used as the default site name). Overrides 'config.appName' defined in a package.json -JSS_APP_NAME= +# ================================ + +# Your Sitecore site name. +# Uses your `package.json` config `appName` if empty. +# When using the Next.js Multisite add-on, the value of the variable represents the default/configured site. +SITE_NAME= # Your default app language. DEFAULT_LANGUAGE= diff --git a/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/index.ts b/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/index.ts index 0212c23393..cb89cdf390 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/index.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/index.ts @@ -7,7 +7,9 @@ const plugins = require('scripts/temp/config-plugins'); export interface JssConfig extends Record { sitecoreApiKey?: string; sitecoreApiHost?: string; - jssAppName?: string; + sitecoreEdgeUrl?: string; + sitecoreEdgeContextId?: string; + siteName?: string; graphQLEndpointPath?: string; defaultLanguage?: string; graphQLEndpoint?: string; diff --git a/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/plugins/fallback.ts b/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/plugins/fallback.ts index 8385943fc9..bb3033849f 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/plugins/fallback.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/plugins/fallback.ts @@ -1,3 +1,4 @@ +import chalk from 'chalk'; import { ConfigPlugin, JssConfig } from '..'; /** @@ -9,6 +10,14 @@ class FallbackPlugin implements ConfigPlugin { order = 100; async exec(config: JssConfig) { + if (config.sitecoreApiKey && config.sitecoreEdgeContextId) { + console.log( + chalk.yellow( + "You have configured both 'sitecoreApiKey' and 'sitecoreEdgeContextId' values. The 'sitecoreEdgeContextId' is used instead." + ) + ); + } + return Object.assign({}, config, { defaultLanguage: config.defaultLanguage || 'en', sitecoreApiKey: config.sitecoreApiKey || 'no-api-key-set', diff --git a/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/plugins/package-json.ts b/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/plugins/package-json.ts index ce49ee56d5..97c3b66e14 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/plugins/package-json.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/plugins/package-json.ts @@ -11,7 +11,7 @@ class PackageJsonPlugin implements ConfigPlugin { if (!packageConfig.config) return config; return Object.assign({}, config, { - jssAppName: config.jssAppName || packageConfig.config.appName, + siteName: config.siteName || packageConfig.config.appName, graphQLEndpointPath: config.graphQLEndpointPath || packageConfig.config.graphQLEndpointPath, defaultLanguage: config.defaultLanguage || packageConfig.config.language, }); diff --git a/packages/create-sitecore-jss/src/templates/nextjs/scripts/generate-config.ts b/packages/create-sitecore-jss/src/templates/nextjs/scripts/generate-config.ts index e8c18ad937..e78f03249a 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs/scripts/generate-config.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs/scripts/generate-config.ts @@ -13,7 +13,9 @@ import { JssConfig, jssConfigFactory } from './config'; const defaultConfig: JssConfig = { sitecoreApiKey: process.env[`${constantCase('sitecoreApiKey')}`], sitecoreApiHost: process.env[`${constantCase('sitecoreApiHost')}`], - jssAppName: process.env[`${constantCase('jssAppName')}`], + sitecoreEdgeUrl: process.env[`${constantCase('sitecoreEdgeUrl')}`], + sitecoreEdgeContextId: process.env[`${constantCase('sitecoreEdgeContextId')}`], + siteName: process.env[`${constantCase('siteName')}`], graphQLEndpointPath: process.env[`${constantCase('graphQLEndpointPath')}`], defaultLanguage: process.env[`${constantCase('defaultLanguage')}`], graphQLEndpoint: process.env[`${constantCase('graphQLEndpoint')}`], diff --git a/packages/create-sitecore-jss/src/templates/nextjs/src/byoc/index.ts b/packages/create-sitecore-jss/src/templates/nextjs/src/byoc/index.ts index 606450504f..c8ca68ea58 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs/src/byoc/index.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs/src/byoc/index.ts @@ -4,6 +4,15 @@ // Import your client-only components via client-bundle. Nextjs's dynamic() call will ensure they are only rendered client-side import dynamic from 'next/dynamic'; +import * as FEAAS from '@sitecore-feaas/clientside/react'; +import config from 'temp/config'; + +// Setting up Edge Proxy settings to be available withing FEAAS components +FEAAS.setContextProperties({ + sitecoreEdgeUrl: config.sitecoreEdgeUrl, + sitecoreEdgeContextId: config.sitecoreEdgeContextId, +}); + const ClientBundle = dynamic(() => import('./index.client'), { ssr: false, }); diff --git a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/dictionary-service-factory.ts b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/dictionary-service-factory.ts index 9f187dfecb..774a258e1e 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/dictionary-service-factory.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/dictionary-service-factory.ts @@ -5,6 +5,7 @@ import { constants, } from '@sitecore-jss/sitecore-jss-nextjs'; import config from 'temp/config'; +import { graphQLClientFactory } from 'lib/graphql-client-factory'; /** * Factory responsible for creating a DictionaryService instance @@ -17,9 +18,8 @@ export class DictionaryServiceFactory { create(siteName: string): DictionaryService { return process.env.FETCH_WITH === constants.FETCH_WITH.GRAPHQL ? new GraphQLDictionaryService({ - endpoint: config.graphQLEndpoint, - apiKey: config.sitecoreApiKey, siteName, + clientFactory: graphQLClientFactory, /* The Dictionary Service needs a root item ID in order to fetch dictionary phrases for the current app. When not provided, the service will attempt to figure out the root item for the current JSS App using GraphQL and app name. diff --git a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory.ts b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory.ts new file mode 100644 index 0000000000..768de3a8bf --- /dev/null +++ b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory.ts @@ -0,0 +1,21 @@ +import { + getEdgeProxyContentUrl, + GraphQLRequestClient, + GraphQLRequestClientFactoryConfig, +} from '@sitecore-jss/sitecore-jss-nextjs'; +import config from 'temp/config'; + +let clientConfig: GraphQLRequestClientFactoryConfig; + +if (config.sitecoreEdgeContextId) { + clientConfig = { + endpoint: getEdgeProxyContentUrl(config.sitecoreEdgeUrl, config.sitecoreEdgeContextId), + }; +} else { + clientConfig = { + endpoint: config.graphQLEndpoint, + apiKey: config.sitecoreApiKey, + }; +} + +export const graphQLClientFactory = GraphQLRequestClient.createClientFactory(clientConfig); diff --git a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/layout-service-factory.ts b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/layout-service-factory.ts index 2c6e49b1d5..87701f75bd 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/layout-service-factory.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/layout-service-factory.ts @@ -5,6 +5,7 @@ import { constants, } from '@sitecore-jss/sitecore-jss-nextjs'; import config from 'temp/config'; +import { graphQLClientFactory } from 'lib/graphql-client-factory'; /** * Factory responsible for creating a LayoutService instance @@ -17,9 +18,8 @@ export class LayoutServiceFactory { create(siteName: string): LayoutService { return process.env.FETCH_WITH === constants.FETCH_WITH.GRAPHQL ? new GraphQLLayoutService({ - endpoint: config.graphQLEndpoint, - apiKey: config.sitecoreApiKey, siteName, + clientFactory: graphQLClientFactory, /* GraphQL endpoint may reach its rate limit with the amount of Layout and Dictionary requests it receives and throw a rate limit error. GraphQL Dictionary and Layout Services can handle rate limit errors from server and attempt a retry on requests. diff --git a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/page-props-factory/plugins/site.ts b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/page-props-factory/plugins/site.ts index 9cc0582c3a..5a0f13299a 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/page-props-factory/plugins/site.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/page-props-factory/plugins/site.ts @@ -11,7 +11,7 @@ class SitePlugin implements Plugin { if (context.preview) return props; // Resolve site by name - props.site = siteResolver.getByName(config.jssAppName); + props.site = siteResolver.getByName(config.siteName); return props; } diff --git a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/site-resolver/plugins/default.ts b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/site-resolver/plugins/default.ts index 45cd4e034c..7f6bb697e6 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/site-resolver/plugins/default.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/site-resolver/plugins/default.ts @@ -6,7 +6,7 @@ class DefaultPlugin implements SiteResolverPlugin { exec(sites: SiteInfo[]): SiteInfo[] { // Add default/configured site sites.unshift({ - name: config.jssAppName, + name: config.siteName, language: config.defaultLanguage, hostName: '*', }); diff --git a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/sitemap-fetcher/plugins/graphql-sitemap-service.ts b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/sitemap-fetcher/plugins/graphql-sitemap-service.ts index ea50d1c09e..3e9c07e35a 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/sitemap-fetcher/plugins/graphql-sitemap-service.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/sitemap-fetcher/plugins/graphql-sitemap-service.ts @@ -6,15 +6,15 @@ import config from 'temp/config'; import { SitemapFetcherPlugin } from '..'; import { GetStaticPathsContext } from 'next'; +import { graphQLClientFactory } from 'lib/graphql-client-factory'; class GraphqlSitemapServicePlugin implements SitemapFetcherPlugin { _graphqlSitemapService: GraphQLSitemapService; constructor() { this._graphqlSitemapService = new GraphQLSitemapService({ - endpoint: config.graphQLEndpoint, - apiKey: config.sitecoreApiKey, - siteName: config.jssAppName, + siteName: config.siteName, + clientFactory: graphQLClientFactory, }); } diff --git a/packages/sitecore-jss-nextjs/src/index.ts b/packages/sitecore-jss-nextjs/src/index.ts index 44a5ac7ef1..d72b6ba2ad 100644 --- a/packages/sitecore-jss-nextjs/src/index.ts +++ b/packages/sitecore-jss-nextjs/src/index.ts @@ -8,6 +8,8 @@ export { AxiosDataFetcherConfig, NativeDataFetcher, NativeDataFetcherConfig, + GraphQLRequestClient, + GraphQLRequestClientFactoryConfig, HTMLLink, enableDebug, debug, @@ -69,6 +71,7 @@ export { EDITING_COMPONENT_PLACEHOLDER, EDITING_COMPONENT_ID, } from '@sitecore-jss/sitecore-jss/layout'; +export { getEdgeProxyContentUrl } from '@sitecore-jss/sitecore-jss/graphql'; export { mediaApi } from '@sitecore-jss/sitecore-jss/media'; export { trackingApi, @@ -95,7 +98,6 @@ export { CdpHelper, PosResolver, } from '@sitecore-jss/sitecore-jss/personalize'; -export { GraphQLRequestClient } from '@sitecore-jss/sitecore-jss'; export { ComponentPropsCollection, diff --git a/packages/sitecore-jss-nextjs/src/services/base-graphql-sitemap-service.ts b/packages/sitecore-jss-nextjs/src/services/base-graphql-sitemap-service.ts index 089b901f7c..8d8c13ab98 100644 --- a/packages/sitecore-jss-nextjs/src/services/base-graphql-sitemap-service.ts +++ b/packages/sitecore-jss-nextjs/src/services/base-graphql-sitemap-service.ts @@ -1,4 +1,9 @@ -import { GraphQLClient, GraphQLRequestClient, PageInfo } from '@sitecore-jss/sitecore-jss/graphql'; +import { + GraphQLClient, + GraphQLRequestClient, + GraphQLRequestClientFactory, + PageInfo, +} from '@sitecore-jss/sitecore-jss/graphql'; import { debug } from '@sitecore-jss/sitecore-jss'; import { getPersonalizedRewrite } from '@sitecore-jss/sitecore-jss/personalize'; @@ -128,19 +133,26 @@ export interface BaseGraphQLSitemapServiceConfig extends Omit { /** * Your Graphql endpoint + * @deprecated use @param clientFactory property instead */ - endpoint: string; + endpoint?: string; /** * The API key to use for authentication. + * @deprecated use @param clientFactory property instead */ - apiKey: string; + apiKey?: string; /** * A flag for whether to include personalized routes in service output - only works on XM Cloud * turned off by default */ includePersonalizedRoutes?: boolean; + /** + * A GraphQL Request Client Factory is a function that accepts configuration and returns an instance of a GraphQLRequestClient. + * This factory function is used to create and configure GraphQL clients for making GraphQL API requests. + */ + clientFactory?: GraphQLRequestClientFactory; } /** @@ -314,6 +326,16 @@ export abstract class BaseGraphQLSitemapService { * @returns {GraphQLClient} implementation */ protected getGraphQLClient(): GraphQLClient { + if (!this.options.endpoint) { + if (!this.options.clientFactory) { + throw new Error('You should provide either an endpoint and apiKey, or a clientFactory.'); + } + + return this.options.clientFactory({ + debugger: debug.sitemap, + }); + } + return new GraphQLRequestClient(this.options.endpoint, { apiKey: this.options.apiKey, debugger: debug.sitemap, diff --git a/packages/sitecore-jss/src/graphql-request-client.test.ts b/packages/sitecore-jss/src/graphql-request-client.test.ts index 89d76fddb9..ccb785a0ab 100644 --- a/packages/sitecore-jss/src/graphql-request-client.test.ts +++ b/packages/sitecore-jss/src/graphql-request-client.test.ts @@ -230,4 +230,19 @@ describe('GraphQLRequestClient', () => { expect(error.name).to.equal('AbortError'); }); }); + + describe('createClientFactory', () => { + it('should create a graphql request factory', () => { + const clientFactory = GraphQLRequestClient.createClientFactory({ + endpoint: 'https://foo.com', + apiKey: 'bar', + }); + + const client = clientFactory({ retries: 5, timeout: 300 }); + + expect(client instanceof GraphQLRequestClient).to.equal(true); + expect(client['retries']).to.equal(5); + expect(client['timeout']).to.equal(300); + }); + }); }); diff --git a/packages/sitecore-jss/src/graphql-request-client.ts b/packages/sitecore-jss/src/graphql-request-client.ts index 3e9dac24d7..0b2e74ad3a 100644 --- a/packages/sitecore-jss/src/graphql-request-client.ts +++ b/packages/sitecore-jss/src/graphql-request-client.ts @@ -42,6 +42,21 @@ export type GraphQLRequestClientConfig = { retries?: number; }; +/** + * A GraphQL Client Factory is a function that accepts configuration and returns an instance of a GraphQLRequestClient. + * This factory function is used to create and configure GraphQL clients for making GraphQL API requests. + * @param config - The configuration object that specifies how the GraphQL client should be set up. + * @returns An instance of a GraphQL Request Client ready to send GraphQL requests. + */ +export type GraphQLRequestClientFactory = ( + config: Omit +) => GraphQLRequestClient; + +/** + * Configuration type for @type GraphQLRequestClientFactory + */ +export type GraphQLRequestClientFactoryConfig = { endpoint: string; apiKey?: string }; + /** * A GraphQL client for Sitecore APIs that uses the 'graphql-request' library. * https://github.com/prisma-labs/graphql-request @@ -79,6 +94,17 @@ export class GraphQLRequestClient implements GraphQLClient { this.debug = clientConfig.debugger || debuggers.http; } + /** + * Factory method for creating a GraphQLRequestClient. + * @param {Object} config - client configuration options. + * @param {string} config.endpoint - endpoint + * @param {string} [config.apiKey] - apikey + */ + static createClientFactory({ endpoint, apiKey }: GraphQLRequestClientFactoryConfig) { + return (config: Omit = {}) => + new GraphQLRequestClient(endpoint, { ...config, apiKey }); + } + /** * Execute graphql request * @param {string | DocumentNode} query graphql query diff --git a/packages/sitecore-jss/src/graphql/graphql-edge-proxy.test.ts b/packages/sitecore-jss/src/graphql/graphql-edge-proxy.test.ts new file mode 100644 index 0000000000..8a051f594e --- /dev/null +++ b/packages/sitecore-jss/src/graphql/graphql-edge-proxy.test.ts @@ -0,0 +1,18 @@ +/* eslint-disable no-unused-expressions */ +import { expect } from 'chai'; +import { getEdgeProxyContentUrl } from './graphql-edge-proxy'; + +describe('graphql-edge-proxy', () => { + describe('getEdgeProxyContentUrl', () => { + it('should return url', () => { + const endpoint = 'https://edge.staging'; + const contextId = '0730fc5a-3333-5555-5555-08db6d7ddb49'; + + const url = getEdgeProxyContentUrl(endpoint, contextId); + + expect(url).to.equal( + 'https://edge.staging/content/api/graphql/v1?sitecoreContextId=0730fc5a-3333-5555-5555-08db6d7ddb49' + ); + }); + }); +}); diff --git a/packages/sitecore-jss/src/graphql/graphql-edge-proxy.ts b/packages/sitecore-jss/src/graphql/graphql-edge-proxy.ts new file mode 100644 index 0000000000..c16f82783f --- /dev/null +++ b/packages/sitecore-jss/src/graphql/graphql-edge-proxy.ts @@ -0,0 +1,8 @@ +/** + * Generates a URL for accessing Sitecore Edge Platform Content using the provided endpoint and context ID. + * @param {string} endpoint - The base endpoint URL for the Edge Platform. + * @param {string} contextId - The unique context ID. + * @returns {string} The complete URL for accessing content through the Edge Platform. + */ +export const getEdgeProxyContentUrl = (endpoint: string, contextId: string) => + `${endpoint}/content/api/graphql/v1?sitecoreContextId=${contextId}`; diff --git a/packages/sitecore-jss/src/graphql/index.ts b/packages/sitecore-jss/src/graphql/index.ts index cf3f74b6da..9476d2f47d 100644 --- a/packages/sitecore-jss/src/graphql/index.ts +++ b/packages/sitecore-jss/src/graphql/index.ts @@ -3,6 +3,8 @@ export { GraphQLClient, GraphQLRequestClient, GraphQLRequestClientConfig, + GraphQLRequestClientFactory, + GraphQLRequestClientFactoryConfig, } from './../graphql-request-client'; export { SearchQueryResult, @@ -11,3 +13,4 @@ export { SearchQueryService, PageInfo, } from './search-service'; +export { getEdgeProxyContentUrl } from './graphql-edge-proxy'; diff --git a/packages/sitecore-jss/src/i18n/graphql-dictionary-service.test.ts b/packages/sitecore-jss/src/i18n/graphql-dictionary-service.test.ts index c7a8b93669..7cd8260b4a 100644 --- a/packages/sitecore-jss/src/i18n/graphql-dictionary-service.test.ts +++ b/packages/sitecore-jss/src/i18n/graphql-dictionary-service.test.ts @@ -42,6 +42,27 @@ describe('GraphQLDictionaryService', () => { expect(result.bar).to.equal('bar'); }); + it('should fetch dictionary phrases using clientFactory', async () => { + nock(endpoint, { reqheaders: { sc_apikey: apiKey } }) + .post('/', /DictionarySearch/gi) + .reply(200, dictionaryQueryResponse); + + const clientFactory = GraphQLRequestClient.createClientFactory({ + endpoint, + apiKey, + }); + + const service = new GraphQLDictionaryService({ + siteName, + rootItemId, + cacheEnabled: false, + clientFactory, + }); + const result = await service.fetchDictionaryData('en'); + expect(result.foo).to.equal('foo'); + expect(result.bar).to.equal('bar'); + }); + it('should attempt to fetch the rootItemId, if rootItemId not provided', async () => { nock(endpoint) .post('/', /AppRootQuery/) diff --git a/packages/sitecore-jss/src/i18n/graphql-dictionary-service.ts b/packages/sitecore-jss/src/i18n/graphql-dictionary-service.ts index e378bdfcc5..88a865d26c 100644 --- a/packages/sitecore-jss/src/i18n/graphql-dictionary-service.ts +++ b/packages/sitecore-jss/src/i18n/graphql-dictionary-service.ts @@ -2,6 +2,7 @@ import { GraphQLClient, GraphQLRequestClient, GraphQLRequestClientConfig, + GraphQLRequestClientFactory, } from '../graphql-request-client'; import { SitecoreTemplateId } from '../constants'; import { DictionaryPhrases, DictionaryServiceBase } from './dictionary-service'; @@ -58,13 +59,21 @@ export interface GraphQLDictionaryServiceConfig Pick { /** * The URL of the graphQL endpoint. + * @deprecated use @param clientFactory property instead */ - endpoint: string; + endpoint?: string; /** * The API key to use for authentication. + * @deprecated use @param clientFactory property instead */ - apiKey: string; + apiKey?: string; + + /** + * A GraphQL Request Client Factory is a function that accepts configuration and returns an instance of a GraphQLRequestClient. + * This factory function is used to create and configure GraphQL clients for making GraphQL API requests. + */ + clientFactory?: GraphQLRequestClientFactory; /** * Optional. The template ID to use when searching for dictionary entries. @@ -161,6 +170,17 @@ export class GraphQLDictionaryService extends DictionaryServiceBase { * @returns {GraphQLClient} implementation */ protected getGraphQLClient(): GraphQLClient { + if (!this.options.endpoint) { + if (!this.options.clientFactory) { + throw new Error('You should provide either an endpoint and apiKey, or a clientFactory.'); + } + + return this.options.clientFactory({ + debugger: debug.dictionary, + retries: this.options.retries, + }); + } + return new GraphQLRequestClient(this.options.endpoint, { apiKey: this.options.apiKey, debugger: debug.dictionary, diff --git a/packages/sitecore-jss/src/index.ts b/packages/sitecore-jss/src/index.ts index c5997bdb56..bf1a5a8791 100644 --- a/packages/sitecore-jss/src/index.ts +++ b/packages/sitecore-jss/src/index.ts @@ -8,6 +8,8 @@ export { GraphQLClient, GraphQLRequestClient, GraphQLRequestClientConfig, + GraphQLRequestClientFactory, + GraphQLRequestClientFactoryConfig, } from './graphql-request-client'; export { AxiosDataFetcher, AxiosDataFetcherConfig } from './axios-fetcher'; export { AxiosResponse } from 'axios'; diff --git a/packages/sitecore-jss/src/layout/graphql-layout-service.test.ts b/packages/sitecore-jss/src/layout/graphql-layout-service.test.ts index a6a0861fc1..a85623f0fb 100644 --- a/packages/sitecore-jss/src/layout/graphql-layout-service.test.ts +++ b/packages/sitecore-jss/src/layout/graphql-layout-service.test.ts @@ -2,6 +2,7 @@ import { expect, use } from 'chai'; import spies from 'chai-spies'; import nock from 'nock'; import { GraphQLLayoutService } from './graphql-layout-service'; +import { GraphQLRequestClient, GraphQLRequestClientFactory } from '../graphql-request-client'; use(spies); @@ -67,6 +68,65 @@ describe('GraphQLLayoutService', () => { }); }); + it('should fetch layout data using clientFactory', async () => { + nock('https://bar.com', { + reqheaders: { + sc_apikey: apiKey, + }, + }) + .post('/graphql', (body) => { + return ( + body.query.replace(/\n|\s/g, '') === + 'query{layout(site:"supersite",routePath:"/styleguide",language:"da-DK"){item{rendered}}}' + ); + }) + .reply(200, { + data: { + layout: { + item: { + rendered: { + sitecore: { + context: { + pageEditing: false, + site: { name: 'JssNextWeb' }, + }, + route: { + name: 'styleguide', + layoutId: 'xxx', + }, + }, + }, + }, + }, + }, + }); + + const clientFactory: GraphQLRequestClientFactory = GraphQLRequestClient.createClientFactory({ + apiKey, + endpoint: 'https://bar.com/graphql', + }); + + const service = new GraphQLLayoutService({ + siteName: 'supersite', + clientFactory, + }); + + const data = await service.fetchLayoutData('/styleguide', 'da-DK'); + + expect(data).to.deep.equal({ + sitecore: { + context: { + pageEditing: false, + site: { name: 'JssNextWeb' }, + }, + route: { + name: 'styleguide', + layoutId: 'xxx', + }, + }, + }); + }); + it('should fetch layout data if locale is not provided', async () => { nock('http://sctest', { reqheaders: { diff --git a/packages/sitecore-jss/src/layout/graphql-layout-service.ts b/packages/sitecore-jss/src/layout/graphql-layout-service.ts index 5f7c59b9a2..7ad50aa41a 100644 --- a/packages/sitecore-jss/src/layout/graphql-layout-service.ts +++ b/packages/sitecore-jss/src/layout/graphql-layout-service.ts @@ -2,6 +2,7 @@ import { LayoutServiceBase } from './layout-service'; import { LayoutServiceData } from './models'; import { GraphQLClient, + GraphQLRequestClientFactory, GraphQLRequestClient, GraphQLRequestClientConfig, } from '../graphql-request-client'; @@ -10,16 +11,23 @@ import debug from '../debug'; export interface GraphQLLayoutServiceConfig extends Pick { /** * Your Graphql endpoint + * @deprecated use @param clientFactory property instead */ - endpoint: string; + endpoint?: string; /** * The JSS application name */ siteName: string; /** * The API key to use for authentication + * @deprecated use @param clientFactory property instead */ - apiKey: string; + apiKey?: string; + /** + * A GraphQL Request Client Factory is a function that accepts configuration and returns an instance of a GraphQLRequestClient. + * This factory function is used to create and configure GraphQL clients for making GraphQL API requests. + */ + clientFactory?: GraphQLRequestClientFactory; /** * Override default layout query * @param {string} siteName @@ -85,6 +93,17 @@ export class GraphQLLayoutService extends LayoutServiceBase { * @returns {GraphQLClient} implementation */ protected getGraphQLClient(): GraphQLClient { + if (!this.serviceConfig.endpoint) { + if (!this.serviceConfig.clientFactory) { + throw new Error('You should provide either an endpoint and apiKey, or a clientFactory.'); + } + + return this.serviceConfig.clientFactory({ + debugger: debug.layout, + retries: this.serviceConfig.retries, + }); + } + return new GraphQLRequestClient(this.serviceConfig.endpoint, { apiKey: this.serviceConfig.apiKey, debugger: debug.layout, diff --git a/packages/sitecore-jss/src/personalize/graphql-personalize-service.test.ts b/packages/sitecore-jss/src/personalize/graphql-personalize-service.test.ts index e16b6b95c8..c55b2fd8dc 100644 --- a/packages/sitecore-jss/src/personalize/graphql-personalize-service.test.ts +++ b/packages/sitecore-jss/src/personalize/graphql-personalize-service.test.ts @@ -3,6 +3,7 @@ import { expect, use } from 'chai'; import spies from 'chai-spies'; import nock from 'nock'; import { GraphQLPersonalizeService } from './graphql-personalize-service'; +import { GraphQLRequestClient } from '../graphql-request-client'; use(spies); @@ -75,6 +76,24 @@ describe('GraphQLPersonalizeService', () => { }); }); + it('should return personalize info for a route using clientFactory', async () => { + mockNonEmptyResponse(); + + const clientFactory = GraphQLRequestClient.createClientFactory(config); + + const service = new GraphQLPersonalizeService({ clientFactory }); + const personalizeData = await service.getPersonalizeInfo( + '/sitecore/content/home', + 'en', + siteName + ); + + expect(personalizeData).to.deep.equal({ + contentId: `embedded_${id}_en`.toLowerCase(), + variantIds, + }); + }); + it('should return personalize info for a route when scope is provided', async () => { mockNonEmptyResponse(); diff --git a/packages/sitecore-jss/src/personalize/graphql-personalize-service.ts b/packages/sitecore-jss/src/personalize/graphql-personalize-service.ts index 9e10163e2b..cc4f99d485 100644 --- a/packages/sitecore-jss/src/personalize/graphql-personalize-service.ts +++ b/packages/sitecore-jss/src/personalize/graphql-personalize-service.ts @@ -1,4 +1,8 @@ -import { GraphQLClient, GraphQLRequestClient } from '../graphql-request-client'; +import { + GraphQLClient, + GraphQLRequestClient, + GraphQLRequestClientFactory, +} from '../graphql-request-client'; import debug from '../debug'; import { isTimeoutError } from '../utils'; import { CdpHelper } from './utils'; @@ -7,12 +11,14 @@ import { CacheClient, CacheOptions, MemoryCacheClient } from '../cache-client'; export type GraphQLPersonalizeServiceConfig = CacheOptions & { /** * Your Graphql endpoint + * @deprecated use @param clientFactory property instead */ - endpoint: string; + endpoint?: string; /** * The API key to use for authentication + * @deprecated use @param clientFactory property instead */ - apiKey: string; + apiKey?: string; /** * Timeout (ms) for the Personalize request. Default is 400. */ @@ -25,6 +31,11 @@ export type GraphQLPersonalizeServiceConfig = CacheOptions & { * Override fetch method. Uses 'GraphQLRequestClient' default otherwise. */ fetch?: typeof fetch; + /** + * A GraphQL Request Client Factory is a function that accepts configuration and returns an instance of a GraphQLRequestClient. + * This factory function is used to create and configure GraphQL clients for making GraphQL API requests. + */ + clientFactory?: GraphQLRequestClientFactory; }; /** @@ -138,6 +149,18 @@ export class GraphQLPersonalizeService { * @returns {GraphQLClient} implementation */ protected getGraphQLClient(): GraphQLClient { + if (!this.config.endpoint) { + if (!this.config.clientFactory) { + throw new Error('You should provide either an endpoint and apiKey, or a clientFactory.'); + } + + return this.config.clientFactory({ + debugger: debug.personalize, + fetch: this.config.fetch, + timeout: this.config.timeout, + }); + } + return new GraphQLRequestClient(this.config.endpoint, { apiKey: this.config.apiKey, debugger: debug.personalize, diff --git a/packages/sitecore-jss/src/site/graphql-error-pages-service.test.ts b/packages/sitecore-jss/src/site/graphql-error-pages-service.test.ts index d92a7523fd..c8c79c1498 100644 --- a/packages/sitecore-jss/src/site/graphql-error-pages-service.test.ts +++ b/packages/sitecore-jss/src/site/graphql-error-pages-service.test.ts @@ -3,6 +3,7 @@ import nock from 'nock'; import { ErrorPages, GraphQLErrorPagesService } from './graphql-error-pages-service'; import { siteNameError } from '../constants'; import { LayoutServiceData } from '../layout'; +import { GraphQLRequestClient } from '../graphql-request-client'; const errorQueryResultNull = { site: { @@ -80,6 +81,27 @@ describe('GraphQLErrorPagesService', () => { return expect(nock.isDone()).to.be.true; }); + it('should fetch error pages using clientFactory', async () => { + mockErrorPagesRequest(mockErrorPages); + + const clientFactory = GraphQLRequestClient.createClientFactory({ + endpoint, + apiKey, + }); + + const service = new GraphQLErrorPagesService({ + siteName, + language, + clientFactory, + }); + + const errorPages = await service.fetchErrorPages(); + + expect(errorPages).to.deep.equal(mockErrorPages); + + return expect(nock.isDone()).to.be.true; + }); + it('should get null if error not exists', async () => { mockErrorPagesRequest(); diff --git a/packages/sitecore-jss/src/site/graphql-error-pages-service.ts b/packages/sitecore-jss/src/site/graphql-error-pages-service.ts index f7a123cf47..32f704dd45 100644 --- a/packages/sitecore-jss/src/site/graphql-error-pages-service.ts +++ b/packages/sitecore-jss/src/site/graphql-error-pages-service.ts @@ -2,6 +2,7 @@ import { GraphQLClient, GraphQLRequestClient, GraphQLRequestClientConfig } from import { siteNameError } from '../constants'; import debug from '../debug'; import { LayoutServiceData } from '../layout'; +import { GraphQLRequestClientFactory } from '../graphql-request-client'; // The default query for request error handling const defaultQuery = /* GraphQL */ ` @@ -27,12 +28,14 @@ export interface GraphQLErrorPagesServiceConfig extends Pick { /** * Your Graphql endpoint + * @deprecated use @param clientFactory property instead */ - endpoint: string; + endpoint?: string; /** * The API key to use for authentication + * @deprecated use @param clientFactory property instead */ - apiKey: string; + apiKey?: string; /** * The JSS application name */ @@ -41,6 +44,11 @@ export interface GraphQLErrorPagesServiceConfig * The language */ language: string; + /** + * A GraphQL Request Client Factory is a function that accepts configuration and returns an instance of a GraphQLRequestClient. + * This factory function is used to create and configure GraphQL clients for making GraphQL API requests. + */ + clientFactory?: GraphQLRequestClientFactory; } /** @@ -108,6 +116,17 @@ export class GraphQLErrorPagesService { * @returns {GraphQLClient} implementation */ protected getGraphQLClient(): GraphQLClient { + if (!this.options.endpoint) { + if (!this.options.clientFactory) { + throw new Error('You should provide either an endpoint and apiKey, or a clientFactory.'); + } + + return this.options.clientFactory({ + debugger: debug.errorpages, + retries: this.options.retries, + }); + } + return new GraphQLRequestClient(this.options.endpoint, { apiKey: this.options.apiKey, debugger: debug.errorpages, diff --git a/packages/sitecore-jss/src/site/graphql-redirects-service.test.ts b/packages/sitecore-jss/src/site/graphql-redirects-service.test.ts index 2ced61e857..942e485ab6 100644 --- a/packages/sitecore-jss/src/site/graphql-redirects-service.test.ts +++ b/packages/sitecore-jss/src/site/graphql-redirects-service.test.ts @@ -4,6 +4,7 @@ import spies from 'chai-spies'; import nock from 'nock'; import { GraphQLRedirectsService, RedirectsQueryResult } from './graphql-redirects-service'; import { siteNameError } from '../constants'; +import { GraphQLRequestClient } from '../graphql-request-client'; use(spies); @@ -82,6 +83,22 @@ describe('GraphQLRedirectsService', () => { return expect(nock.isDone()).to.be.true; }); + it('should get redirects using clientFactory', async () => { + mockRedirectsRequest(siteName); + + const clientFactory = GraphQLRequestClient.createClientFactory({ + endpoint, + apiKey, + }); + + const service = new GraphQLRedirectsService({ clientFactory }); + const result = await service.fetchRedirects(siteName); + + expect(result).to.deep.equal(redirectsQueryResult.site?.siteInfo?.redirects); + + return expect(nock.isDone()).to.be.true; + }); + it('should get no redirects', async () => { mockRedirectsRequest(); diff --git a/packages/sitecore-jss/src/site/graphql-redirects-service.ts b/packages/sitecore-jss/src/site/graphql-redirects-service.ts index 11adc17757..87be5a21c4 100644 --- a/packages/sitecore-jss/src/site/graphql-redirects-service.ts +++ b/packages/sitecore-jss/src/site/graphql-redirects-service.ts @@ -2,6 +2,7 @@ import { GraphQLClient, GraphQLRequestClient } from '../graphql'; import { siteNameError } from '../constants'; import debug from '../debug'; import { MemoryCacheClient, CacheOptions, CacheClient } from '../cache-client'; +import { GraphQLRequestClientFactory } from '../graphql-request-client'; export const REDIRECT_TYPE_301 = 'REDIRECT_301'; export const REDIRECT_TYPE_302 = 'REDIRECT_302'; @@ -35,16 +36,23 @@ const defaultQuery = /* GraphQL */ ` export type GraphQLRedirectsServiceConfig = CacheOptions & { /** * Your Graphql endpoint + * @deprecated use @param clientFactory property instead */ - endpoint: string; + endpoint?: string; /** * The API key to use for authentication + * @deprecated use @param clientFactory property instead */ - apiKey: string; + apiKey?: string; /** * Override fetch method. Uses 'GraphQLRequestClient' default otherwise. */ fetch?: typeof fetch; + /** + * A GraphQL Request Client Factory is a function that accepts configuration and returns an instance of a GraphQLRequestClient. + * This factory function is used to create and configure GraphQL clients for making GraphQL API requests. + */ + clientFactory?: GraphQLRequestClientFactory; }; /** @@ -105,6 +113,17 @@ export class GraphQLRedirectsService { * @returns {GraphQLClient} implementation */ protected getGraphQLClient(): GraphQLClient { + if (!this.options.endpoint) { + if (!this.options.clientFactory) { + throw new Error('You should provide either an endpoint and apiKey, or a clientFactory.'); + } + + return this.options.clientFactory({ + debugger: debug.redirects, + fetch: this.options.fetch, + }); + } + return new GraphQLRequestClient(this.options.endpoint, { apiKey: this.options.apiKey, debugger: debug.redirects, diff --git a/packages/sitecore-jss/src/site/graphql-robots-service.test.ts b/packages/sitecore-jss/src/site/graphql-robots-service.test.ts index 26c46bf10c..f9d7e3f266 100644 --- a/packages/sitecore-jss/src/site/graphql-robots-service.test.ts +++ b/packages/sitecore-jss/src/site/graphql-robots-service.test.ts @@ -2,6 +2,7 @@ import { expect } from 'chai'; import nock from 'nock'; import { GraphQLRobotsService } from './graphql-robots-service'; import { siteNameError } from '../constants'; +import { GraphQLRequestClient } from '../graphql-request-client'; const robotsQueryResultNull = { site: { @@ -60,5 +61,21 @@ describe('GraphQLRobotsService', () => { return expect(nock.isDone()).to.be.true; }); + + it('should get robots.txt using clientFactory', async () => { + mockRobotsRequest(siteName); + + const clientFactory = GraphQLRequestClient.createClientFactory({ + endpoint, + apiKey, + }); + + const service = new GraphQLRobotsService({ siteName, clientFactory }); + + const robots = await service.fetchRobots(); + expect(robots).to.equal(siteName); + + return expect(nock.isDone()).to.be.true; + }); }); }); diff --git a/packages/sitecore-jss/src/site/graphql-robots-service.ts b/packages/sitecore-jss/src/site/graphql-robots-service.ts index e1541b6c9f..f0dbafd456 100644 --- a/packages/sitecore-jss/src/site/graphql-robots-service.ts +++ b/packages/sitecore-jss/src/site/graphql-robots-service.ts @@ -1,6 +1,7 @@ import { GraphQLClient, GraphQLRequestClient } from '../graphql'; import { siteNameError } from '../constants'; import debug from '../debug'; +import { GraphQLRequestClientFactory } from '../graphql-request-client'; // The default query for request robots.txt const defaultQuery = /* GraphQL */ ` @@ -16,16 +17,23 @@ const defaultQuery = /* GraphQL */ ` export type GraphQLRobotsServiceConfig = { /** * Your Graphql endpoint + * @deprecated use @param clientFactory property instead */ - endpoint: string; + endpoint?: string; /** * The API key to use for authentication + * @deprecated use @param clientFactory property instead */ - apiKey: string; + apiKey?: string; /** * The JSS application name */ siteName: string; + /** + * A GraphQL Request Client Factory is a function that accepts configuration and returns an instance of a GraphQLRequestClient. + * This factory function is used to create and configure GraphQL clients for making GraphQL API requests. + */ + clientFactory?: GraphQLRequestClientFactory; }; /** @@ -82,6 +90,16 @@ export class GraphQLRobotsService { * @returns {GraphQLClient} implementation */ protected getGraphQLClient(): GraphQLClient { + if (!this.options.endpoint) { + if (!this.options.clientFactory) { + throw new Error('You should provide either an endpoint and apiKey, or a clientFactory.'); + } + + return this.options.clientFactory({ + debugger: debug.robots, + }); + } + return new GraphQLRequestClient(this.options.endpoint, { apiKey: this.options.apiKey, debugger: debug.robots, diff --git a/packages/sitecore-jss/src/site/graphql-siteinfo-service.test.ts b/packages/sitecore-jss/src/site/graphql-siteinfo-service.test.ts index 9b986eddd4..d9c95db7d4 100644 --- a/packages/sitecore-jss/src/site/graphql-siteinfo-service.test.ts +++ b/packages/sitecore-jss/src/site/graphql-siteinfo-service.test.ts @@ -2,7 +2,7 @@ import { expect } from 'chai'; import nock from 'nock'; import { GraphQLSiteInfoService, GraphQLSiteInfoResult } from './graphql-siteinfo-service'; -import { PageInfo } from '../graphql'; +import { GraphQLRequestClient, PageInfo } from '../graphql'; describe('GraphQLSiteInfoService', () => { const endpoint = 'http://site'; @@ -106,6 +106,43 @@ describe('GraphQLSiteInfoService', () => { ]); }); + it('should return correct result using clientFactory', async () => { + mockSiteInfoRequest( + nonEmptyResponse({ + sites: [ + site({ + name: 'public 0', + hostName: 'pr.showercurtains.org', + language: '', + pointOfSale: '', + }), + ], + }) + ); + const clientFactory = GraphQLRequestClient.createClientFactory({ + endpoint, + apiKey, + }); + const service = new GraphQLSiteInfoService({ clientFactory }); + const result = await service.fetchSiteInfo(); + expect(result).to.be.deep.equal([ + { + name: 'site 0', + hostName: 'restricted.gov', + language: 'en', + pointOfSale: { + en: 'en-pos', + }, + }, + { + name: 'public 0', + hostName: 'pr.showercurtains.org', + language: '', + pointOfSale: undefined, + }, + ]); + }); + it('should return correct result using custom pageSize', async () => { mockSiteInfoRequest(nonEmptyResponse({ count: 2, pageInfo: { hasNext: true, endCursor: '' } })); mockSiteInfoRequest( diff --git a/packages/sitecore-jss/src/site/graphql-siteinfo-service.ts b/packages/sitecore-jss/src/site/graphql-siteinfo-service.ts index 88625de9fc..8c57ca256c 100644 --- a/packages/sitecore-jss/src/site/graphql-siteinfo-service.ts +++ b/packages/sitecore-jss/src/site/graphql-siteinfo-service.ts @@ -2,6 +2,7 @@ import { URLSearchParams } from 'url'; import { GraphQLClient, GraphQLRequestClient, PageInfo } from '../graphql'; import debug from '../debug'; import { CacheClient, CacheOptions, MemoryCacheClient } from '../cache-client'; +import { GraphQLRequestClientFactory } from '../graphql-request-client'; const headlessSiteGroupingTemplate = 'E46F3AF2-39FA-4866-A157-7017C4B2A40C'; const sitecoreContentRootItem = '0DE95AE4-41AB-4D01-9EB0-67441B7C2450'; @@ -68,18 +69,25 @@ export type SiteInfo = { export type GraphQLSiteInfoServiceConfig = CacheOptions & { /** * Your Graphql endpoint + * @deprecated use @param clientFactory property instead */ - endpoint: string; + endpoint?: string; /** * The API key to use for authentication + * @deprecated use @param clientFactory property instead */ - apiKey: string; + apiKey?: string; /** common variable for all GraphQL queries * it will be used for every type of query to regulate result batch size * Optional. How many result items to fetch in each GraphQL call. This is needed for pagination. * @default 10 */ pageSize?: number; + /** + * A GraphQL Request Client Factory is a function that accepts configuration and returns an instance of a GraphQLRequestClient. + * This factory function is used to create and configure GraphQL clients for making GraphQL API requests. + */ + clientFactory?: GraphQLRequestClientFactory; }; type GraphQLSiteInfoResponse = { @@ -176,6 +184,16 @@ export class GraphQLSiteInfoService { * @returns {GraphQLClient} implementation */ protected getGraphQLClient(): GraphQLClient { + if (!this.config.endpoint) { + if (!this.config.clientFactory) { + throw new Error('You should provide either an endpoint and apiKey, or a clientFactory.'); + } + + return this.config.clientFactory({ + debugger: debug.multisite, + }); + } + return new GraphQLRequestClient(this.config.endpoint, { apiKey: this.config.apiKey, debugger: debug.multisite, diff --git a/packages/sitecore-jss/src/site/graphql-sitemap-service.test.ts b/packages/sitecore-jss/src/site/graphql-sitemap-service.test.ts index 953640a7b8..967ca4f508 100644 --- a/packages/sitecore-jss/src/site/graphql-sitemap-service.test.ts +++ b/packages/sitecore-jss/src/site/graphql-sitemap-service.test.ts @@ -2,6 +2,7 @@ import { expect } from 'chai'; import nock from 'nock'; import { GraphQLSitemapXmlService } from './graphql-sitemap-service'; import { siteNameError } from '../constants'; +import { GraphQLRequestClient } from '../graphql-request-client'; const sitemapQueryResultNull = { site: { @@ -65,6 +66,23 @@ describe('GraphQLSitemapXmlService', () => { return expect(nock.isDone()).to.be.true; }); + it('should fetch sitemap using clientFactory', async () => { + mockSitemapRequest(mockSitemap); + + const clientFactory = GraphQLRequestClient.createClientFactory({ + endpoint, + apiKey, + }); + + const service = new GraphQLSitemapXmlService({ siteName, clientFactory }); + const sitemaps = await service.fetchSitemaps(); + + expect(sitemaps.length).to.equal(1); + expect(sitemaps).to.deep.equal(mockSitemap); + + return expect(nock.isDone()).to.be.true; + }); + it('should fetch sitemaps', async () => { mockSitemapRequest(mockSitemaps); diff --git a/packages/sitecore-jss/src/site/graphql-sitemap-service.ts b/packages/sitecore-jss/src/site/graphql-sitemap-service.ts index 4309c381e1..3faf5bb5a7 100644 --- a/packages/sitecore-jss/src/site/graphql-sitemap-service.ts +++ b/packages/sitecore-jss/src/site/graphql-sitemap-service.ts @@ -1,6 +1,7 @@ import { GraphQLClient, GraphQLRequestClient } from '../graphql'; import { siteNameError } from '../constants'; import debug from '../debug'; +import { GraphQLRequestClientFactory } from '../graphql-request-client'; const PREFIX_NAME_SITEMAP = 'sitemap'; @@ -18,16 +19,23 @@ const defaultQuery = /* GraphQL */ ` export type GraphQLSitemapXmlServiceConfig = { /** * Your Graphql endpoint + * @deprecated use @param clientFactory property instead */ - endpoint: string; + endpoint?: string; /** * The API key to use for authentication + * @deprecated use @param clientFactory property instead */ - apiKey: string; + apiKey?: string; /** * The JSS application name */ siteName: string; + /** + * A GraphQL Request Client Factory is a function that accepts configuration and returns an instance of a GraphQLRequestClient. + * This factory function is used to create and configure GraphQL clients for making GraphQL API requests. + */ + clientFactory?: GraphQLRequestClientFactory; }; /** @@ -94,6 +102,16 @@ export class GraphQLSitemapXmlService { * @returns {GraphQLClient} implementation */ protected getGraphQLClient(): GraphQLClient { + if (!this.options.endpoint) { + if (!this.options.clientFactory) { + throw new Error('You should provide either an endpoint and apiKey, or a clientFactory.'); + } + + return this.options.clientFactory({ + debugger: debug.sitemap, + }); + } + return new GraphQLRequestClient(this.options.endpoint, { apiKey: this.options.apiKey, debugger: debug.sitemap, From db63525ae15f71e25d609a73c814ece733b6e223 Mon Sep 17 00:00:00 2001 From: illiakovalenko Date: Thu, 26 Oct 2023 12:26:04 +0300 Subject: [PATCH 2/9] Updated .env --- packages/create-sitecore-jss/src/templates/nextjs/.env | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/create-sitecore-jss/src/templates/nextjs/.env b/packages/create-sitecore-jss/src/templates/nextjs/.env index a3317dd586..85ca7d99a5 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs/.env +++ b/packages/create-sitecore-jss/src/templates/nextjs/.env @@ -20,7 +20,6 @@ JSS_EDITING_SECRET= # ================================ # ===== Sitecore Edge Platform ====== -# (Preview / Delivery Edge environment variables should be set empty) # Your Sitecore Edge Platform URL. SITECORE_EDGE_URL=https://edge-platform.sitecorecloud.io From 16fef1ad5a6a43975a722efd32439087dcc5035e Mon Sep 17 00:00:00 2001 From: illiakovalenko Date: Thu, 26 Oct 2023 12:56:58 +0300 Subject: [PATCH 3/9] Updated CHANGELOG --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2540d23408..13a3bf901a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,10 @@ Our versioning strategy is as follows: ### 🎉 New Features & Improvements +* `[templates/nextjs]` `[sitecore-jss-nextjs]` `[sitecore-jss]` ([#1640](https://github.com/Sitecore/jss/pull/1640)) Sitecore Edge Platform and Context support: + * Introducing the _clientFactory_ property. This property can be utilized by GraphQL-based services, the previously used _endpoint_ and _apiKey_ properties are deprecated. The _clientFactory_ serves as the central hub for executing GraphQL requests within the application. + * New SITECORE_EDGE_URL, SITECORE_EDGE_CONTEXT_ID environment variables has been added. + * The JSS_APP_NAME environment variable has been updated and is now referred to as SITE_NAME. * `[templates/nextjs]` Enable client-only BYOC component imports. Client-only components can be imported through src/byoc/index.client.ts. Hybrid (server render with client hydration) components can be imported through src/byoc/index.hybrid.ts. BYOC scaffold logic is also moved from nextjs-sxa addon into base template ([#1628](https://github.com/Sitecore/jss/pull/1628)[#1636](https://github.com/Sitecore/jss/pull/1636)) * `[templates/nextjs]` Import SitecoreForm component into sample nextjs app ([#1628](https://github.com/Sitecore/jss/pull/1628)) * `[sitecore-jss-nextjs]` (Vercel/Sitecore) Deployment Protection Bypass support for editing integration. ([#1634](https://github.com/Sitecore/jss/pull/1634)) From 45e3d3083fa7be88c92367f8d33416157ad436ab Mon Sep 17 00:00:00 2001 From: illiakovalenko Date: Thu, 26 Oct 2023 14:30:03 +0300 Subject: [PATCH 4/9] Refactor clientFactory to product new instances on import call --- .../scripts/config/plugins/multisite.ts | 5 +-- .../plugins/graphql-sitemap-service.ts | 4 +-- .../src/lib/middleware/plugins/personalize.ts | 4 +-- .../src/lib/middleware/plugins/redirects.ts | 4 +-- .../templates/nextjs-sxa/src/pages/404.tsx | 4 +-- .../templates/nextjs-sxa/src/pages/500.tsx | 4 +-- .../src/lib/dictionary-service-factory.ts | 4 +-- .../nextjs/src/lib/graphql-client-factory.ts | 21 ------------ .../src/lib/graphql-client-factory/index.ts | 7 ++++ .../src/lib/graphql-client-factory/utils.ts | 32 +++++++++++++++++++ .../nextjs/src/lib/layout-service-factory.ts | 4 +-- .../plugins/graphql-sitemap-service.ts | 4 +-- 12 files changed, 58 insertions(+), 39 deletions(-) delete mode 100644 packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory.ts create mode 100644 packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory/index.ts create mode 100644 packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory/utils.ts diff --git a/packages/create-sitecore-jss/src/templates/nextjs-multisite/scripts/config/plugins/multisite.ts b/packages/create-sitecore-jss/src/templates/nextjs-multisite/scripts/config/plugins/multisite.ts index e5a76e39d3..64a684d4d2 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs-multisite/scripts/config/plugins/multisite.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs-multisite/scripts/config/plugins/multisite.ts @@ -1,7 +1,7 @@ import chalk from 'chalk'; import { GraphQLSiteInfoService, SiteInfo } from '@sitecore-jss/sitecore-jss-nextjs'; +import { createGraphQLClientFactory } from 'lib/graphql-client-factory/utils'; import { ConfigPlugin, JssConfig } from '..'; -import { graphQLClientFactory } from 'lib/graphql-client-factory'; /** * This plugin will set the "sites" config prop. @@ -20,9 +20,10 @@ class MultisitePlugin implements ConfigPlugin { console.warn(chalk.yellow('Skipping site information fetch (missing GraphQL endpoint).')); } else { console.log(`Fetching site information from ${endpoint}`); + try { const siteInfoService = new GraphQLSiteInfoService({ - clientFactory: graphQLClientFactory, + clientFactory: createGraphQLClientFactory(config), }); sites = await siteInfoService.fetchSiteInfo(); } catch (error) { diff --git a/packages/create-sitecore-jss/src/templates/nextjs-multisite/src/lib/sitemap-fetcher/plugins/graphql-sitemap-service.ts b/packages/create-sitecore-jss/src/templates/nextjs-multisite/src/lib/sitemap-fetcher/plugins/graphql-sitemap-service.ts index c610bc6d56..e9f900a493 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs-multisite/src/lib/sitemap-fetcher/plugins/graphql-sitemap-service.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs-multisite/src/lib/sitemap-fetcher/plugins/graphql-sitemap-service.ts @@ -8,14 +8,14 @@ import config from 'temp/config'; import { SitemapFetcherPlugin } from '..'; import { GetStaticPathsContext } from 'next'; import { siteResolver } from 'lib/site-resolver'; -import { graphQLClientFactory } from 'lib/graphql-client'; +import clientFactory from 'lib/graphql-client-factory'; class GraphqlSitemapServicePlugin implements SitemapFetcherPlugin { _graphqlSitemapService: MultisiteGraphQLSitemapService; constructor() { this._graphqlSitemapService = new MultisiteGraphQLSitemapService({ - clientFactory: graphQLClientFactory, + clientFactory, sites: [...new Set(siteResolver.sites.map((site: SiteInfo) => site.name))], }); } diff --git a/packages/create-sitecore-jss/src/templates/nextjs-personalize/src/lib/middleware/plugins/personalize.ts b/packages/create-sitecore-jss/src/templates/nextjs-personalize/src/lib/middleware/plugins/personalize.ts index 01da7037c3..4721453d2e 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs-personalize/src/lib/middleware/plugins/personalize.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs-personalize/src/lib/middleware/plugins/personalize.ts @@ -1,7 +1,7 @@ import { NextRequest, NextResponse } from 'next/server'; import { PersonalizeMiddleware } from '@sitecore-jss/sitecore-jss-nextjs/middleware'; import { MiddlewarePlugin } from '..'; -import { graphQLClientFactory } from 'lib/graphql-client-factory'; +import clientFactory from 'lib/graphql-client-factory'; import { siteResolver } from 'lib/site-resolver'; /** @@ -29,7 +29,7 @@ class PersonalizePlugin implements MiddlewarePlugin { parseInt(process.env.PERSONALIZE_MIDDLEWARE_EDGE_TIMEOUT)) || 400, scope: process.env.NEXT_PUBLIC_PERSONALIZE_SCOPE, - clientFactory: graphQLClientFactory, + clientFactory, }, // Configuration for your Sitecore CDP endpoint cdpConfig: { diff --git a/packages/create-sitecore-jss/src/templates/nextjs-sxa/src/lib/middleware/plugins/redirects.ts b/packages/create-sitecore-jss/src/templates/nextjs-sxa/src/lib/middleware/plugins/redirects.ts index 2347e555ba..58df7acf58 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs-sxa/src/lib/middleware/plugins/redirects.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs-sxa/src/lib/middleware/plugins/redirects.ts @@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'; import { RedirectsMiddleware } from '@sitecore-jss/sitecore-jss-nextjs/middleware'; import { MiddlewarePlugin } from '..'; import { siteResolver } from 'lib/site-resolver'; -import { graphQLClientFactory } from 'lib/graphql-client-factory'; +import clientFactory from 'lib/graphql-client-factory'; class RedirectsPlugin implements MiddlewarePlugin { private redirectsMiddleware: RedirectsMiddleware; @@ -11,7 +11,7 @@ class RedirectsPlugin implements MiddlewarePlugin { constructor() { this.redirectsMiddleware = new RedirectsMiddleware({ // Client factory implementation - clientFactory: graphQLClientFactory, + clientFactory, // These are all the locales you support in your application. // These should match those in your next.config.js (i18n.locales). locales: ['en'], diff --git a/packages/create-sitecore-jss/src/templates/nextjs-sxa/src/pages/404.tsx b/packages/create-sitecore-jss/src/templates/nextjs-sxa/src/pages/404.tsx index 413cb614d0..cf1fef2292 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs-sxa/src/pages/404.tsx +++ b/packages/create-sitecore-jss/src/templates/nextjs-sxa/src/pages/404.tsx @@ -10,7 +10,7 @@ import { componentBuilder } from 'temp/componentBuilder'; import Layout from 'src/Layout'; import { GetStaticProps } from 'next'; import { siteResolver } from 'lib/site-resolver'; -import { graphQLClientFactory } from 'lib/graphql-client-factory'; +import clientFactory from 'lib/graphql-client-factory'; const Custom404 = (props: SitecorePageProps): JSX.Element => { if (!(props && props.layoutData)) { @@ -30,7 +30,7 @@ const Custom404 = (props: SitecorePageProps): JSX.Element => { export const getStaticProps: GetStaticProps = async (context) => { const site = siteResolver.getByName(config.siteName); const errorPagesService = new GraphQLErrorPagesService({ - clientFactory: graphQLClientFactory, + clientFactory, siteName: site.name, language: context.locale || config.defaultLanguage, retries: diff --git a/packages/create-sitecore-jss/src/templates/nextjs-sxa/src/pages/500.tsx b/packages/create-sitecore-jss/src/templates/nextjs-sxa/src/pages/500.tsx index edf954903e..dc0ac5adf4 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs-sxa/src/pages/500.tsx +++ b/packages/create-sitecore-jss/src/templates/nextjs-sxa/src/pages/500.tsx @@ -10,7 +10,7 @@ import { componentBuilder } from 'temp/componentBuilder'; import { GetStaticProps } from 'next'; import config from 'temp/config'; import { siteResolver } from 'lib/site-resolver'; -import { graphQLClientFactory } from 'lib/graphql-client-factory'; +import clientFactory from 'lib/graphql-client-factory'; /** * Rendered in case if we have 500 error @@ -46,7 +46,7 @@ const Custom500 = (props: SitecorePageProps): JSX.Element => { export const getStaticProps: GetStaticProps = async (context) => { const site = siteResolver.getByName(config.siteName); const errorPagesService = new GraphQLErrorPagesService({ - clientFactory: graphQLClientFactory, + clientFactory, siteName: site.name, language: context.locale || context.defaultLocale || config.defaultLanguage, retries: diff --git a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/dictionary-service-factory.ts b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/dictionary-service-factory.ts index 774a258e1e..963f814194 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/dictionary-service-factory.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/dictionary-service-factory.ts @@ -5,7 +5,7 @@ import { constants, } from '@sitecore-jss/sitecore-jss-nextjs'; import config from 'temp/config'; -import { graphQLClientFactory } from 'lib/graphql-client-factory'; +import clientFactory from 'lib/graphql-client-factory'; /** * Factory responsible for creating a DictionaryService instance @@ -19,7 +19,7 @@ export class DictionaryServiceFactory { return process.env.FETCH_WITH === constants.FETCH_WITH.GRAPHQL ? new GraphQLDictionaryService({ siteName, - clientFactory: graphQLClientFactory, + clientFactory, /* The Dictionary Service needs a root item ID in order to fetch dictionary phrases for the current app. When not provided, the service will attempt to figure out the root item for the current JSS App using GraphQL and app name. diff --git a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory.ts b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory.ts deleted file mode 100644 index 768de3a8bf..0000000000 --- a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { - getEdgeProxyContentUrl, - GraphQLRequestClient, - GraphQLRequestClientFactoryConfig, -} from '@sitecore-jss/sitecore-jss-nextjs'; -import config from 'temp/config'; - -let clientConfig: GraphQLRequestClientFactoryConfig; - -if (config.sitecoreEdgeContextId) { - clientConfig = { - endpoint: getEdgeProxyContentUrl(config.sitecoreEdgeUrl, config.sitecoreEdgeContextId), - }; -} else { - clientConfig = { - endpoint: config.graphQLEndpoint, - apiKey: config.sitecoreApiKey, - }; -} - -export const graphQLClientFactory = GraphQLRequestClient.createClientFactory(clientConfig); diff --git a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory/index.ts b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory/index.ts new file mode 100644 index 0000000000..fd62a606c2 --- /dev/null +++ b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory/index.ts @@ -0,0 +1,7 @@ +import config from 'temp/config'; +import { createGraphQLClientFactory } from './utils'; + +/** + * Create a new GraphQLClientFactory instance on each import call + */ +export default createGraphQLClientFactory(config); diff --git a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory/utils.ts b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory/utils.ts new file mode 100644 index 0000000000..71be69ef5e --- /dev/null +++ b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory/utils.ts @@ -0,0 +1,32 @@ +import { + GraphQLRequestClientFactoryConfig, + getEdgeProxyContentUrl, + GraphQLRequestClient, +} from '@sitecore-jss/sitecore-jss-nextjs'; +import { JssConfig } from 'scripts/config'; + +/** + * The clientFactory serves as the central hub for executing GraphQL requests within the application + * @param config jss config + * @returns GraphQLRequestClientFactory instance + */ +export const createGraphQLClientFactory = (config: JssConfig) => { + let clientConfig: GraphQLRequestClientFactoryConfig; + + if (config.sitecoreEdgeContextId && config.sitecoreEdgeUrl) { + clientConfig = { + endpoint: getEdgeProxyContentUrl(config.sitecoreEdgeUrl, config.sitecoreEdgeContextId), + }; + } else if (config.graphQLEndpoint && config.sitecoreApiKey) { + clientConfig = { + endpoint: config.graphQLEndpoint, + apiKey: config.sitecoreApiKey, + }; + } else { + throw new Error( + 'Please configure either your sitecoreEdgeUrl and sitecoreEdgeContextId, or your graphQLEndpoint and sitecoreApiKey' + ); + } + + return GraphQLRequestClient.createClientFactory(clientConfig); +}; diff --git a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/layout-service-factory.ts b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/layout-service-factory.ts index 87701f75bd..c175488bd7 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/layout-service-factory.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/layout-service-factory.ts @@ -5,7 +5,7 @@ import { constants, } from '@sitecore-jss/sitecore-jss-nextjs'; import config from 'temp/config'; -import { graphQLClientFactory } from 'lib/graphql-client-factory'; +import clientFactory from 'lib/graphql-client-factory'; /** * Factory responsible for creating a LayoutService instance @@ -19,7 +19,7 @@ export class LayoutServiceFactory { return process.env.FETCH_WITH === constants.FETCH_WITH.GRAPHQL ? new GraphQLLayoutService({ siteName, - clientFactory: graphQLClientFactory, + clientFactory, /* GraphQL endpoint may reach its rate limit with the amount of Layout and Dictionary requests it receives and throw a rate limit error. GraphQL Dictionary and Layout Services can handle rate limit errors from server and attempt a retry on requests. diff --git a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/sitemap-fetcher/plugins/graphql-sitemap-service.ts b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/sitemap-fetcher/plugins/graphql-sitemap-service.ts index 3e9c07e35a..9b41675535 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/sitemap-fetcher/plugins/graphql-sitemap-service.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/sitemap-fetcher/plugins/graphql-sitemap-service.ts @@ -6,7 +6,7 @@ import config from 'temp/config'; import { SitemapFetcherPlugin } from '..'; import { GetStaticPathsContext } from 'next'; -import { graphQLClientFactory } from 'lib/graphql-client-factory'; +import clientFactory from 'lib/graphql-client-factory'; class GraphqlSitemapServicePlugin implements SitemapFetcherPlugin { _graphqlSitemapService: GraphQLSitemapService; @@ -14,7 +14,7 @@ class GraphqlSitemapServicePlugin implements SitemapFetcherPlugin { constructor() { this._graphqlSitemapService = new GraphQLSitemapService({ siteName: config.siteName, - clientFactory: graphQLClientFactory, + clientFactory, }); } From fd43e2deb694cb3eb55678a0629a9fb827393a9e Mon Sep 17 00:00:00 2001 From: illiakovalenko Date: Thu, 26 Oct 2023 14:33:35 +0300 Subject: [PATCH 5/9] Update --- .../src/lib/middleware/plugins/personalize.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/create-sitecore-jss/src/templates/nextjs-personalize/src/lib/middleware/plugins/personalize.ts b/packages/create-sitecore-jss/src/templates/nextjs-personalize/src/lib/middleware/plugins/personalize.ts index 4721453d2e..44387365a1 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs-personalize/src/lib/middleware/plugins/personalize.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs-personalize/src/lib/middleware/plugins/personalize.ts @@ -24,12 +24,12 @@ class PersonalizePlugin implements MiddlewarePlugin { this.personalizeMiddleware = new PersonalizeMiddleware({ // Configuration for your Sitecore Experience Edge endpoint edgeConfig: { + clientFactory, timeout: (process.env.PERSONALIZE_MIDDLEWARE_EDGE_TIMEOUT && parseInt(process.env.PERSONALIZE_MIDDLEWARE_EDGE_TIMEOUT)) || 400, scope: process.env.NEXT_PUBLIC_PERSONALIZE_SCOPE, - clientFactory, }, // Configuration for your Sitecore CDP endpoint cdpConfig: { From 9c70c6004e7f98acec54a7283646771e0506e2db Mon Sep 17 00:00:00 2001 From: illiakovalenko Date: Thu, 26 Oct 2023 14:37:06 +0300 Subject: [PATCH 6/9] Update --- .../nextjs/src/lib/graphql-client-factory/index.ts | 6 +++--- .../nextjs/src/lib/graphql-client-factory/utils.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory/index.ts b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory/index.ts index fd62a606c2..04b7a2d866 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory/index.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory/index.ts @@ -1,7 +1,7 @@ import config from 'temp/config'; import { createGraphQLClientFactory } from './utils'; -/** - * Create a new GraphQLClientFactory instance on each import call - */ +// The GraphQLRequestClientFactory serves as the central hub for executing GraphQL requests within the application + +// Create a new instance on each import call export default createGraphQLClientFactory(config); diff --git a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory/utils.ts b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory/utils.ts index 71be69ef5e..2e57e43c9c 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory/utils.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory/utils.ts @@ -6,7 +6,7 @@ import { import { JssConfig } from 'scripts/config'; /** - * The clientFactory serves as the central hub for executing GraphQL requests within the application + * Creates a new GraphQLRequestClientFactory instance * @param config jss config * @returns GraphQLRequestClientFactory instance */ From b6f959bd767567a6a2d5952b9a8e2fa749b3c3ed Mon Sep 17 00:00:00 2001 From: illiakovalenko Date: Fri, 27 Oct 2023 11:06:13 +0300 Subject: [PATCH 7/9] Addressed comments --- .../scripts/config/plugins/multisite.ts | 3 ++- .../scripts/config/plugins/disconnected.ts | 3 ++- .../nextjs-sxa/scripts/config/plugins/sxa.ts | 5 +++-- .../src/templates/nextjs/.env | 5 +---- .../templates/nextjs/scripts/config/index.ts | 16 +--------------- .../nextjs/scripts/config/plugins/computed.ts | 3 ++- .../nextjs/scripts/config/plugins/fallback.ts | 4 +++- .../scripts/config/plugins/package-json.ts | 3 ++- .../scripts/config/plugins/scjssconfig.ts | 3 ++- .../nextjs/scripts/generate-config.ts | 6 ++++-- .../src/templates/nextjs/src/byoc/index.ts | 2 +- .../src/templates/nextjs/src/lib/config.ts | 14 ++++++++++++++ .../src/lib/graphql-client-factory/utils.ts | 8 ++++---- packages/sitecore-jss-nextjs/src/index.ts | 1 + .../sitecore-jss/src/graphql-request-client.ts | 7 +++++-- .../src/graphql/graphql-edge-proxy.test.ts | 18 ++++++++++++++---- .../src/graphql/graphql-edge-proxy.ts | 10 ++++++---- 17 files changed, 67 insertions(+), 44 deletions(-) create mode 100644 packages/create-sitecore-jss/src/templates/nextjs/src/lib/config.ts diff --git a/packages/create-sitecore-jss/src/templates/nextjs-multisite/scripts/config/plugins/multisite.ts b/packages/create-sitecore-jss/src/templates/nextjs-multisite/scripts/config/plugins/multisite.ts index 64a684d4d2..ac3aa03cf4 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs-multisite/scripts/config/plugins/multisite.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs-multisite/scripts/config/plugins/multisite.ts @@ -1,7 +1,8 @@ import chalk from 'chalk'; import { GraphQLSiteInfoService, SiteInfo } from '@sitecore-jss/sitecore-jss-nextjs'; import { createGraphQLClientFactory } from 'lib/graphql-client-factory/utils'; -import { ConfigPlugin, JssConfig } from '..'; +import { JssConfig } from 'lib/config'; +import { ConfigPlugin } from '..'; /** * This plugin will set the "sites" config prop. diff --git a/packages/create-sitecore-jss/src/templates/nextjs-styleguide/scripts/config/plugins/disconnected.ts b/packages/create-sitecore-jss/src/templates/nextjs-styleguide/scripts/config/plugins/disconnected.ts index f540878906..4946f0a8b6 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs-styleguide/scripts/config/plugins/disconnected.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs-styleguide/scripts/config/plugins/disconnected.ts @@ -1,7 +1,8 @@ import 'dotenv/config'; import chalk from 'chalk'; import { constants } from '@sitecore-jss/sitecore-jss-nextjs'; -import { ConfigPlugin, JssConfig } from '..'; +import { JssConfig } from 'lib/config'; +import { ConfigPlugin } from '..'; /** * This plugin will override the "sitecoreApiHost" config prop diff --git a/packages/create-sitecore-jss/src/templates/nextjs-sxa/scripts/config/plugins/sxa.ts b/packages/create-sitecore-jss/src/templates/nextjs-sxa/scripts/config/plugins/sxa.ts index d2fd261159..a1c78d89af 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs-sxa/scripts/config/plugins/sxa.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs-sxa/scripts/config/plugins/sxa.ts @@ -1,7 +1,8 @@ -import { ConfigPlugin, JssConfig } from '..'; +import { JssConfig } from 'lib/config'; +import { ConfigPlugin } from '..'; /** - * This plugin will set configuration specific for SXA. + * This plugin will set configuration specific for SXA. */ class SXAPlugin implements ConfigPlugin { // should come before fallback diff --git a/packages/create-sitecore-jss/src/templates/nextjs/.env b/packages/create-sitecore-jss/src/templates/nextjs/.env index 85ca7d99a5..6f192960c1 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs/.env +++ b/packages/create-sitecore-jss/src/templates/nextjs/.env @@ -21,10 +21,7 @@ JSS_EDITING_SECRET= # ===== Sitecore Edge Platform ====== -# Your Sitecore Edge Platform URL. -SITECORE_EDGE_URL=https://edge-platform.sitecorecloud.io - -# Your unified Sitecore Edge Context Id is needed to build the app. +# Your unified Sitecore Edge Context Id. SITECORE_EDGE_CONTEXT_ID= # ====== Sitecore Preview / Delivery Edge ====== diff --git a/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/index.ts b/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/index.ts index cb89cdf390..683c6df2e1 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/index.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/index.ts @@ -1,20 +1,6 @@ // eslint-disable-next-line @typescript-eslint/no-var-requires const plugins = require('scripts/temp/config-plugins'); - -/** - * JSS configuration object - */ -export interface JssConfig extends Record { - sitecoreApiKey?: string; - sitecoreApiHost?: string; - sitecoreEdgeUrl?: string; - sitecoreEdgeContextId?: string; - siteName?: string; - graphQLEndpointPath?: string; - defaultLanguage?: string; - graphQLEndpoint?: string; - layoutServiceConfigurationName?: string; -} +import { JssConfig } from 'lib/config'; export interface ConfigPlugin { /** diff --git a/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/plugins/computed.ts b/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/plugins/computed.ts index 0669af129e..dd38b780a5 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/plugins/computed.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/plugins/computed.ts @@ -1,4 +1,5 @@ -import { ConfigPlugin, JssConfig } from '..'; +import { JssConfig } from 'lib/config'; +import { ConfigPlugin } from '..'; /** * This plugin will set computed config props. diff --git a/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/plugins/fallback.ts b/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/plugins/fallback.ts index bb3033849f..ba3b08c00a 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/plugins/fallback.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/plugins/fallback.ts @@ -1,5 +1,6 @@ import chalk from 'chalk'; -import { ConfigPlugin, JssConfig } from '..'; +import { JssConfig } from 'lib/config'; +import { ConfigPlugin } from '..'; /** * This config will set fallback values for properties that were left empty @@ -22,6 +23,7 @@ class FallbackPlugin implements ConfigPlugin { defaultLanguage: config.defaultLanguage || 'en', sitecoreApiKey: config.sitecoreApiKey || 'no-api-key-set', layoutServiceConfigurationName: config.layoutServiceConfigurationName || 'default', + sitecoreEdgeUrl: config.sitecoreEdgeUrl || 'https://edge-platform.sitecorecloud.io', }); } } diff --git a/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/plugins/package-json.ts b/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/plugins/package-json.ts index 97c3b66e14..f411d9d283 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/plugins/package-json.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/plugins/package-json.ts @@ -1,4 +1,5 @@ -import { ConfigPlugin, JssConfig } from '..'; +import { JssConfig } from 'lib/config'; +import { ConfigPlugin } from '..'; import packageConfig from 'package.json'; /** diff --git a/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/plugins/scjssconfig.ts b/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/plugins/scjssconfig.ts index e957f2758b..d1368da595 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/plugins/scjssconfig.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs/scripts/config/plugins/scjssconfig.ts @@ -1,4 +1,5 @@ -import { ConfigPlugin, JssConfig } from '..'; +import { JssConfig } from 'lib/config'; +import { ConfigPlugin } from '..'; /** * This plugin will set config props based on scjssconfig.json. diff --git a/packages/create-sitecore-jss/src/templates/nextjs/scripts/generate-config.ts b/packages/create-sitecore-jss/src/templates/nextjs/scripts/generate-config.ts index e78f03249a..67c7c5b0f8 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs/scripts/generate-config.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs/scripts/generate-config.ts @@ -2,7 +2,8 @@ import 'dotenv/config'; import fs from 'fs'; import path from 'path'; import { constantCase } from 'constant-case'; -import { JssConfig, jssConfigFactory } from './config'; +import { JssConfig } from 'lib/config'; +import { jssConfigFactory } from './config'; /* CONFIG GENERATION @@ -15,7 +16,8 @@ const defaultConfig: JssConfig = { sitecoreApiHost: process.env[`${constantCase('sitecoreApiHost')}`], sitecoreEdgeUrl: process.env[`${constantCase('sitecoreEdgeUrl')}`], sitecoreEdgeContextId: process.env[`${constantCase('sitecoreEdgeContextId')}`], - siteName: process.env[`${constantCase('siteName')}`], + siteName: + process.env[`${constantCase('siteName')}`] || process.env[`${constantCase('jssAppName')}`], graphQLEndpointPath: process.env[`${constantCase('graphQLEndpointPath')}`], defaultLanguage: process.env[`${constantCase('defaultLanguage')}`], graphQLEndpoint: process.env[`${constantCase('graphQLEndpoint')}`], diff --git a/packages/create-sitecore-jss/src/templates/nextjs/src/byoc/index.ts b/packages/create-sitecore-jss/src/templates/nextjs/src/byoc/index.ts index c8ca68ea58..c9bf7377bd 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs/src/byoc/index.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs/src/byoc/index.ts @@ -7,7 +7,7 @@ import dynamic from 'next/dynamic'; import * as FEAAS from '@sitecore-feaas/clientside/react'; import config from 'temp/config'; -// Setting up Edge Proxy settings to be available withing FEAAS components +// Set context properties to be available within BYOC components FEAAS.setContextProperties({ sitecoreEdgeUrl: config.sitecoreEdgeUrl, sitecoreEdgeContextId: config.sitecoreEdgeContextId, diff --git a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/config.ts b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/config.ts new file mode 100644 index 0000000000..88ec0aaa07 --- /dev/null +++ b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/config.ts @@ -0,0 +1,14 @@ +/* + * Represents the type of config object available within the generated temp/config.js + */ +export interface JssConfig extends Record { + sitecoreApiKey?: string; + sitecoreApiHost?: string; + sitecoreEdgeUrl?: string; + sitecoreEdgeContextId?: string; + siteName?: string; + graphQLEndpointPath?: string; + defaultLanguage?: string; + graphQLEndpoint?: string; + layoutServiceConfigurationName?: string; +} diff --git a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory/utils.ts b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory/utils.ts index 2e57e43c9c..e2b2918626 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory/utils.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory/utils.ts @@ -3,7 +3,7 @@ import { getEdgeProxyContentUrl, GraphQLRequestClient, } from '@sitecore-jss/sitecore-jss-nextjs'; -import { JssConfig } from 'scripts/config'; +import { JssConfig } from 'lib/config'; /** * Creates a new GraphQLRequestClientFactory instance @@ -13,9 +13,9 @@ import { JssConfig } from 'scripts/config'; export const createGraphQLClientFactory = (config: JssConfig) => { let clientConfig: GraphQLRequestClientFactoryConfig; - if (config.sitecoreEdgeContextId && config.sitecoreEdgeUrl) { + if (config.sitecoreEdgeContextId) { clientConfig = { - endpoint: getEdgeProxyContentUrl(config.sitecoreEdgeUrl, config.sitecoreEdgeContextId), + endpoint: getEdgeProxyContentUrl(config.sitecoreEdgeContextId, config.sitecoreEdgeUrl), }; } else if (config.graphQLEndpoint && config.sitecoreApiKey) { clientConfig = { @@ -24,7 +24,7 @@ export const createGraphQLClientFactory = (config: JssConfig) => { }; } else { throw new Error( - 'Please configure either your sitecoreEdgeUrl and sitecoreEdgeContextId, or your graphQLEndpoint and sitecoreApiKey' + 'Please configure either your sitecoreEdgeContextId, or your graphQLEndpoint and sitecoreApiKey.' ); } diff --git a/packages/sitecore-jss-nextjs/src/index.ts b/packages/sitecore-jss-nextjs/src/index.ts index d72b6ba2ad..e753bbee5d 100644 --- a/packages/sitecore-jss-nextjs/src/index.ts +++ b/packages/sitecore-jss-nextjs/src/index.ts @@ -9,6 +9,7 @@ export { NativeDataFetcher, NativeDataFetcherConfig, GraphQLRequestClient, + GraphQLRequestClientFactory, GraphQLRequestClientFactoryConfig, HTMLLink, enableDebug, diff --git a/packages/sitecore-jss/src/graphql-request-client.ts b/packages/sitecore-jss/src/graphql-request-client.ts index 0b2e74ad3a..80da5232cf 100644 --- a/packages/sitecore-jss/src/graphql-request-client.ts +++ b/packages/sitecore-jss/src/graphql-request-client.ts @@ -95,12 +95,15 @@ export class GraphQLRequestClient implements GraphQLClient { } /** - * Factory method for creating a GraphQLRequestClient. + * 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 }: GraphQLRequestClientFactoryConfig) { + static createClientFactory({ + endpoint, + apiKey, + }: GraphQLRequestClientFactoryConfig): GraphQLRequestClientFactory { return (config: Omit = {}) => new GraphQLRequestClient(endpoint, { ...config, apiKey }); } diff --git a/packages/sitecore-jss/src/graphql/graphql-edge-proxy.test.ts b/packages/sitecore-jss/src/graphql/graphql-edge-proxy.test.ts index 8a051f594e..3310843e76 100644 --- a/packages/sitecore-jss/src/graphql/graphql-edge-proxy.test.ts +++ b/packages/sitecore-jss/src/graphql/graphql-edge-proxy.test.ts @@ -5,13 +5,23 @@ import { getEdgeProxyContentUrl } from './graphql-edge-proxy'; describe('graphql-edge-proxy', () => { describe('getEdgeProxyContentUrl', () => { it('should return url', () => { - const endpoint = 'https://edge.staging'; - const contextId = '0730fc5a-3333-5555-5555-08db6d7ddb49'; + const sitecoreEdgeContextId = '0730fc5a-3333-5555-5555-08db6d7ddb49'; - const url = getEdgeProxyContentUrl(endpoint, contextId); + const url = getEdgeProxyContentUrl(sitecoreEdgeContextId); expect(url).to.equal( - 'https://edge.staging/content/api/graphql/v1?sitecoreContextId=0730fc5a-3333-5555-5555-08db6d7ddb49' + 'https://edge-platform.sitecorecloud.io/content/api/graphql/v1?sitecoreContextId=0730fc5a-3333-5555-5555-08db6d7ddb49' + ); + }); + + it('should return url when custom sitecoreEdgeUrl is provided', () => { + const sitecoreEdgeUrl = 'https://test.com'; + const sitecoreEdgeContextId = '0730fc5a-3333-5555-5555-08db6d7ddb49'; + + const url = getEdgeProxyContentUrl(sitecoreEdgeContextId, sitecoreEdgeUrl); + + expect(url).to.equal( + 'https://test.com/content/api/graphql/v1?sitecoreContextId=0730fc5a-3333-5555-5555-08db6d7ddb49' ); }); }); diff --git a/packages/sitecore-jss/src/graphql/graphql-edge-proxy.ts b/packages/sitecore-jss/src/graphql/graphql-edge-proxy.ts index c16f82783f..4d5d1dbf65 100644 --- a/packages/sitecore-jss/src/graphql/graphql-edge-proxy.ts +++ b/packages/sitecore-jss/src/graphql/graphql-edge-proxy.ts @@ -1,8 +1,10 @@ /** * Generates a URL for accessing Sitecore Edge Platform Content using the provided endpoint and context ID. - * @param {string} endpoint - The base endpoint URL for the Edge Platform. - * @param {string} contextId - The unique context ID. + * @param {string} sitecoreEdgeContextId - The unique context id. + * @param {string} [sitecoreEdgeUrl] - The base endpoint URL for the Edge Platform. Default is https://edge-platform.sitecorecloud.io * @returns {string} The complete URL for accessing content through the Edge Platform. */ -export const getEdgeProxyContentUrl = (endpoint: string, contextId: string) => - `${endpoint}/content/api/graphql/v1?sitecoreContextId=${contextId}`; +export const getEdgeProxyContentUrl = ( + sitecoreEdgeContextId: string, + sitecoreEdgeUrl = 'https://edge-platform.sitecorecloud.io' +) => `${sitecoreEdgeUrl}/content/api/graphql/v1?sitecoreContextId=${sitecoreEdgeContextId}`; From 2a24d160cf65679cd01552cd78ab9ee13b3c2f76 Mon Sep 17 00:00:00 2001 From: illiakovalenko Date: Fri, 27 Oct 2023 11:08:57 +0300 Subject: [PATCH 8/9] Updated CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f68a740552..1d2845403b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ Our versioning strategy is as follows: * `[templates/nextjs]` `[sitecore-jss-nextjs]` `[sitecore-jss]` ([#1640](https://github.com/Sitecore/jss/pull/1640)) Sitecore Edge Platform and Context support: * Introducing the _clientFactory_ property. This property can be utilized by GraphQL-based services, the previously used _endpoint_ and _apiKey_ properties are deprecated. The _clientFactory_ serves as the central hub for executing GraphQL requests within the application. - * New SITECORE_EDGE_URL, SITECORE_EDGE_CONTEXT_ID environment variables has been added. + * New SITECORE_EDGE_CONTEXT_ID environment variable has been added. * The JSS_APP_NAME environment variable has been updated and is now referred to as SITE_NAME. * `[templates/nextjs]` Enable client-only BYOC component imports. Client-only components can be imported through src/byoc/index.client.ts. Hybrid (server render with client hydration) components can be imported through src/byoc/index.hybrid.ts. BYOC scaffold logic is also moved from nextjs-sxa addon into base template ([#1628](https://github.com/Sitecore/jss/pull/1628)[#1636](https://github.com/Sitecore/jss/pull/1636)) * `[templates/nextjs]` Import SitecoreForm component into sample nextjs app ([#1628](https://github.com/Sitecore/jss/pull/1628)) From 2b24eff7ab6ffe2c373f11958d70345e526f638c Mon Sep 17 00:00:00 2001 From: illiakovalenko Date: Fri, 27 Oct 2023 15:35:25 +0300 Subject: [PATCH 9/9] Updates --- .../nextjs-multisite/scripts/config/plugins/multisite.ts | 2 +- packages/create-sitecore-jss/src/templates/nextjs/.env | 4 ++-- .../src/lib/graphql-client-factory/{utils.ts => create.ts} | 0 .../templates/nextjs/src/lib/graphql-client-factory/index.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory/{utils.ts => create.ts} (100%) diff --git a/packages/create-sitecore-jss/src/templates/nextjs-multisite/scripts/config/plugins/multisite.ts b/packages/create-sitecore-jss/src/templates/nextjs-multisite/scripts/config/plugins/multisite.ts index ac3aa03cf4..a22b2f849b 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs-multisite/scripts/config/plugins/multisite.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs-multisite/scripts/config/plugins/multisite.ts @@ -1,6 +1,6 @@ import chalk from 'chalk'; import { GraphQLSiteInfoService, SiteInfo } from '@sitecore-jss/sitecore-jss-nextjs'; -import { createGraphQLClientFactory } from 'lib/graphql-client-factory/utils'; +import { createGraphQLClientFactory } from 'lib/graphql-client-factory/create'; import { JssConfig } from 'lib/config'; import { ConfigPlugin } from '..'; diff --git a/packages/create-sitecore-jss/src/templates/nextjs/.env b/packages/create-sitecore-jss/src/templates/nextjs/.env index 6f192960c1..17c41a657f 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs/.env +++ b/packages/create-sitecore-jss/src/templates/nextjs/.env @@ -17,13 +17,13 @@ PUBLIC_URL=http://localhost:3000 # We recommend an alphanumeric value of at least 16 characters. JSS_EDITING_SECRET= -# ================================ - # ===== Sitecore Edge Platform ====== # Your unified Sitecore Edge Context Id. SITECORE_EDGE_CONTEXT_ID= +# ================================ + # ====== Sitecore Preview / Delivery Edge ====== # (Sitecore Edge Proxy environment variables should be set empty, otherwise they will be prioritized and applied) diff --git a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory/utils.ts b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory/create.ts similarity index 100% rename from packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory/utils.ts rename to packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory/create.ts diff --git a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory/index.ts b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory/index.ts index 04b7a2d866..d500587ef8 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory/index.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs/src/lib/graphql-client-factory/index.ts @@ -1,5 +1,5 @@ import config from 'temp/config'; -import { createGraphQLClientFactory } from './utils'; +import { createGraphQLClientFactory } from './create'; // The GraphQLRequestClientFactory serves as the central hub for executing GraphQL requests within the application