diff --git a/CHANGELOG.md b/CHANGELOG.md index 614ceb88ca..6cd9934717 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,11 @@ Our versioning strategy is as follows: ### 🛠 Breaking Changes * `[create-sitecore-jss]` The `nextjs-personalize` initializer add-on template has been removed and is replaced by the `nextjs-xmcloud` initializer template. You can use the interactive prompts or the `--xmcloud` argument to include this template. ([#1653](https://github.com/Sitecore/jss/pull/1653)) +* `[templates/nextjs]` `[sitecore-jss-nextjs]` CloudSDK Integration ([#1652](https://github.com/Sitecore/jss/pull/1652)): + * Removed the following properties from _PersonalizeMiddleware_: _getPointOfSale_, _clientKey_, _endpoint_. You now need to provide _sitecoreEdgeContextId_ as a replacement. + * _PersonalizeMiddleware_ has transitioned to utilizing the _CloudSDK_ package, replacing the previous dependency on _Engage_. + * Introduced _Context_ class, that is used to initialize the application Context and shared Software Development Kits (SDKs). Accessible within the _@sitecore-jss/sitecore-jss-nextjs/context_ submodule. + * Point of Sale resolution is fully removed, now it's handled by Sitecore Edge Proxy ## 21.5.0 diff --git a/packages/create-sitecore-jss/src/templates/nextjs-sxa/src/pages/_app.tsx b/packages/create-sitecore-jss/src/templates/nextjs-sxa/src/pages/_app.tsx index 7bbf99b96c..b4ab44a12f 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs-sxa/src/pages/_app.tsx +++ b/packages/create-sitecore-jss/src/templates/nextjs-sxa/src/pages/_app.tsx @@ -1,6 +1,7 @@ import type { AppProps } from 'next/app'; import { I18nProvider } from 'next-localization'; import { SitecorePageProps } from 'lib/page-props'; +import Bootstrap from 'src/Bootstrap'; import 'assets/main.scss'; @@ -8,12 +9,17 @@ function App({ Component, pageProps }: AppProps): JSX.Element const { dictionary, ...rest } = pageProps; return ( - // Use the next-localization (w/ rosetta) library to provide our translation dictionary to the app. - // Note Next.js does not (currently) provide anything for translation, only i18n routing. - // If your app is not multilingual, next-localization and references to it can be removed. - - - + <> + + {/* + // Use the next-localization (w/ rosetta) library to provide our translation dictionary to the app. + // Note Next.js does not (currently) provide anything for translation, only i18n routing. + // If your app is not multilingual, next-localization and references to it can be removed. + */} + + + + ); } diff --git a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/.env b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/.env index 45ccca7ccc..5292cf2bef 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/.env +++ b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/.env @@ -7,21 +7,11 @@ SITECORE_EDGE_CONTEXT_ID= # ============================================== -# Your Sitecore CDP API target (specific to your data center region) -NEXT_PUBLIC_CDP_TARGET_URL= - -# Your Sitecore CDP client key -NEXT_PUBLIC_CDP_CLIENT_KEY= - # An optional Sitecore Personalize scope identifier. # This can be used to isolate personalization data when multiple XM Cloud Environments share a Personalize tenant. # This should match the PAGES_PERSONALIZE_SCOPE environment variable for your connected XM Cloud Environment. NEXT_PUBLIC_PERSONALIZE_SCOPE= -# Your Sitecore CDP point(s) of sale -# Can be provided as a single value (mypoint.com) or a multi-value JSON with locales ({"en":"en.mypoint.com","fr":"fr.mypoint.com"} etc) -NEXT_PUBLIC_CDP_POINTOFSALE= - # Timeout (ms) for Sitecore CDP requests to respond within. Default is 400. PERSONALIZE_MIDDLEWARE_CDP_TIMEOUT= diff --git a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/package.json b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/package.json index 33d06644d2..4394f65580 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/package.json +++ b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/package.json @@ -1,7 +1,7 @@ { "dependencies": { - "@sitecore/components": "~1.0.19", - "@sitecore/engage": "^1.4.1", + "@sitecore/components": "^1.1.0", + "@sitecore-cloudsdk/events": "^0.1.1", "@sitecore-feaas/clientside": "^0.4.12" } } diff --git a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/scripts/config/plugins/edge-platform.ts b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/scripts/config/plugins/edge-platform.ts index d8779ff0bb..9680df17ca 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/scripts/config/plugins/edge-platform.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/scripts/config/plugins/edge-platform.ts @@ -11,7 +11,9 @@ class EdgePlatformPlugin implements ConfigPlugin { order = 2; async exec(config: JssConfig) { - const sitecoreEdgeUrl = process.env[`${constantCase('sitecoreEdgeUrl')}`] || 'https://edge-platform.sitecorecloud.io'; + const sitecoreEdgeUrl = + process.env[`${constantCase('sitecoreEdgeUrl')}`]?.replace(/\/$/, '') || + 'https://edge-platform.sitecorecloud.io'; const sitecoreEdgeContextId = process.env[`${constantCase('sitecoreEdgeContextId')}`]; if (config.sitecoreApiKey && sitecoreEdgeContextId) { diff --git a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/Bootstrap.tsx b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/Bootstrap.tsx new file mode 100644 index 0000000000..a8f082c8bb --- /dev/null +++ b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/Bootstrap.tsx @@ -0,0 +1,24 @@ +import { SitecorePageProps } from 'lib/page-props'; +import { context } from 'src/lib/context'; +import { siteResolver } from 'lib/site-resolver'; +import config from 'temp/config'; + +/** + * The Bootstrap component is the entry point for performing any initialization logic + * that needs to happen early in the application's lifecycle. + */ +const Bootstrap = (props: SitecorePageProps): JSX.Element | null => { + const site = props.layoutData?.sitecore.context.site; + const siteInfo = siteResolver.getByName(site?.name || config.siteName); + + /** + * Initializes the application Context and associated Software Development Kits (SDKs). + * This function is the entry point for setting up the application's context and any SDKs that are required for its proper functioning. + * It prepares the resources needed to interact with various services and features within the application. + */ + context.init({ siteName: siteInfo.name }); + + return null; +}; + +export default Bootstrap; diff --git a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/byoc/index.ts b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/byoc/index.ts index aeb744d961..3e9486da3f 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/byoc/index.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/byoc/index.ts @@ -1,6 +1,6 @@ import * as FEAAS from '@sitecore-feaas/clientside/react'; import dynamic from 'next/dynamic'; -import config from 'temp/config'; +import { context } from 'lib/context'; /** * This is an out-of-box bundler for External components (BYOC) (see Sitecore documentation for more details) * It enables registering components in client-only or SSR/hybrid contexts @@ -8,10 +8,7 @@ import config from 'temp/config'; */ // Set context properties to be available within BYOC components -FEAAS.setContextProperties({ - sitecoreEdgeUrl: config.sitecoreEdgeUrl, - sitecoreEdgeContextId: config.sitecoreEdgeContextId, -}); +FEAAS.setContextProperties(context); // Import your client-only components via client-bundle. Nextjs's dynamic() call will ensure they are only rendered client-side const ClientBundle = dynamic(() => import('./index.client'), { diff --git a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/components/CdpPageView.tsx b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/components/CdpPageView.tsx index 190c0af1d7..d0d6d53f49 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/components/CdpPageView.tsx +++ b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/components/CdpPageView.tsx @@ -1,54 +1,23 @@ import { CdpHelper, LayoutServicePageState, - SiteInfo, useSitecoreContext, - PosResolver, } from '@sitecore-jss/sitecore-jss-nextjs'; import { useEffect } from 'react'; import config from 'temp/config'; -import { init } from '@sitecore/engage'; -import { siteResolver } from 'lib/site-resolver'; +import { context } from 'lib/context'; /** * This is the CDP page view component. - * It uses the Sitecore Engage SDK to enable page view events on the client-side. + * It uses the Sitecore Cloud SDK to enable page view events on the client-side. * See Sitecore Engage SDK documentation for details. - * https://www.npmjs.com/package/@sitecore/engage + * https://www.npmjs.com/package/@sitecore-cloudsdk/events */ const CdpPageView = (): JSX.Element => { const { sitecoreContext: { pageState, route, variantId, site }, } = useSitecoreContext(); - /** - * Creates a page view event using the Sitecore Engage SDK. - */ - const createPageView = async ( - page: string, - language: string, - site: SiteInfo, - pageVariantId: string - ) => { - const pointOfSale = PosResolver.resolve(site, language); - const engage = await init({ - clientKey: process.env.NEXT_PUBLIC_CDP_CLIENT_KEY || '', - targetURL: process.env.NEXT_PUBLIC_CDP_TARGET_URL || '', - // Replace with the top level cookie domain of the website that is being integrated e.g ".example.com" and not "www.example.com" - cookieDomain: window.location.hostname.replace(/^www\./, ''), - // Cookie may be created in personalize middleware (server), but if not we should create it here - forceServerCookieMode: false, - }); - engage.pageView({ - channel: 'WEB', - currency: 'USD', - pointOfSale, - page, - pageVariantId, - language, - }); - }; - /** * Determines if the page view events should be turned off. * IMPORTANT: You should implement based on your cookie consent management solution of choice. @@ -68,7 +37,6 @@ const CdpPageView = (): JSX.Element => { return; } - const siteInfo = siteResolver.getByName(site?.name || config.siteName); const language = route.itemLanguage || config.defaultLanguage; const scope = process.env.NEXT_PUBLIC_PERSONALIZE_SCOPE; @@ -78,7 +46,16 @@ const CdpPageView = (): JSX.Element => { variantId as string, scope ); - createPageView(route.name, language, siteInfo, pageVariantId); + + context.getSDK('Events')?.then((Events) => + Events.pageView({ + channel: 'WEB', + currency: 'USD', + page: route.name, + pageVariantId, + language, + }) + ); }, [pageState, route, variantId, site]); return <>; diff --git a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/lib/context/index.ts b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/lib/context/index.ts new file mode 100644 index 0000000000..8ba26ce19f --- /dev/null +++ b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/lib/context/index.ts @@ -0,0 +1,22 @@ +import { Context } from '@sitecore-jss/sitecore-jss-nextjs/context'; +import config from 'temp/config'; + +import Events from './sdk/events'; + +/** + * List of SDKs to be initialized. + * Each SDK is defined as a module with the @type {SDK} type. + */ +const sdks = { + Events, +}; + +/** + * Context instance that is used to initialize the application Context and associated Software Development Kits (SDKs). + */ +export const context = new Context({ + sitecoreEdgeUrl: config.sitecoreEdgeUrl, + sitecoreEdgeContextId: config.sitecoreEdgeContextId, + siteName: config.siteName, + sdks, +}); diff --git a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/lib/context/sdk/events.ts b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/lib/context/sdk/events.ts new file mode 100644 index 0000000000..59d8e7629b --- /dev/null +++ b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/lib/context/sdk/events.ts @@ -0,0 +1,22 @@ +import * as Events from '@sitecore-cloudsdk/events/browser'; +import { SDK } from '@sitecore-jss/sitecore-jss-nextjs/context'; + +const sdkModule: SDK = { + sdk: Events, + init: async (props) => { + // Events module can't be initialized on the server side + // We also don't want to initialize it in development mode + if (typeof window === 'undefined' || process.env.NODE_ENV === 'development') return; + + await Events.init({ + siteName: props.siteName, + sitecoreEdgeContextId: props.sitecoreEdgeContextId, + // Replace with the top level cookie domain of the website that is being integrated e.g ".example.com" and not "www.example.com" + cookieDomain: window.location.hostname.replace(/^www\./, ''), + // Cookie may be created in personalize middleware (server), but if not we should create it here + enableBrowserCookie: true, + }); + }, +}; + +export default sdkModule; diff --git a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/lib/middleware/plugins/personalize.ts b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/lib/middleware/plugins/personalize.ts index 44387365a1..6580931843 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/lib/middleware/plugins/personalize.ts +++ b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/lib/middleware/plugins/personalize.ts @@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'; import { PersonalizeMiddleware } from '@sitecore-jss/sitecore-jss-nextjs/middleware'; import { MiddlewarePlugin } from '..'; import clientFactory from 'lib/graphql-client-factory'; +import config from 'temp/config'; import { siteResolver } from 'lib/site-resolver'; /** @@ -33,8 +34,8 @@ class PersonalizePlugin implements MiddlewarePlugin { }, // Configuration for your Sitecore CDP endpoint cdpConfig: { - endpoint: process.env.NEXT_PUBLIC_CDP_TARGET_URL || '', - clientKey: process.env.NEXT_PUBLIC_CDP_CLIENT_KEY || '', + sitecoreEdgeUrl: config.sitecoreEdgeUrl, + sitecoreEdgeContextId: config.sitecoreEdgeContextId, timeout: (process.env.PERSONALIZE_MIDDLEWARE_CDP_TIMEOUT && parseInt(process.env.PERSONALIZE_MIDDLEWARE_CDP_TIMEOUT)) || @@ -50,9 +51,6 @@ class PersonalizePlugin implements MiddlewarePlugin { excludeRoute: () => false, // 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: - // getPointOfSale: (site, language) => { ... } }); } diff --git a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/lib/site-resolver/plugins/default.ts b/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/lib/site-resolver/plugins/default.ts deleted file mode 100644 index 5244f8e268..0000000000 --- a/packages/create-sitecore-jss/src/templates/nextjs-xmcloud/src/lib/site-resolver/plugins/default.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { SiteInfo } from '@sitecore-jss/sitecore-jss-nextjs/site'; -import { tryParseEnvValue } from '@sitecore-jss/sitecore-jss-nextjs/utils'; -import config from 'temp/config'; -import { SiteResolverPlugin } from '..'; - -// Resolving from env variable, but it can be expanded or change in future if needed. -const pointOfSale = tryParseEnvValue>( - process.env.NEXT_PUBLIC_CDP_POINTOFSALE, - { [config.defaultLanguage]: process.env.NEXT_PUBLIC_CDP_POINTOFSALE || '' } -); - -class DefaultPlugin implements SiteResolverPlugin { - exec(sites: SiteInfo[]): SiteInfo[] { - // Add default/configured site - sites.unshift({ - name: config.siteName, - language: config.defaultLanguage, - hostName: '*', - pointOfSale, - }); - - return sites; - } -} - -export const defaultPlugin = new DefaultPlugin(); diff --git a/packages/create-sitecore-jss/src/templates/nextjs/src/Bootstrap.tsx b/packages/create-sitecore-jss/src/templates/nextjs/src/Bootstrap.tsx new file mode 100644 index 0000000000..1bb368b21c --- /dev/null +++ b/packages/create-sitecore-jss/src/templates/nextjs/src/Bootstrap.tsx @@ -0,0 +1,12 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { SitecorePageProps } from 'lib/page-props'; + +/** + * The Bootstrap component is the entry point for performing any initialization logic + * that needs to happen early in the application's lifecycle. + */ +const Bootstrap = (_props: SitecorePageProps): JSX.Element | null => { + return null; +}; + +export default Bootstrap; diff --git a/packages/create-sitecore-jss/src/templates/nextjs/src/pages/_app.tsx b/packages/create-sitecore-jss/src/templates/nextjs/src/pages/_app.tsx index 7fef8a5794..37f4f24162 100644 --- a/packages/create-sitecore-jss/src/templates/nextjs/src/pages/_app.tsx +++ b/packages/create-sitecore-jss/src/templates/nextjs/src/pages/_app.tsx @@ -1,6 +1,7 @@ import type { AppProps } from 'next/app'; import { I18nProvider } from 'next-localization'; import { SitecorePageProps } from 'lib/page-props'; +import Bootstrap from 'src/Bootstrap'; import 'assets/app.css'; @@ -8,12 +9,17 @@ function App({ Component, pageProps }: AppProps): JSX.Element const { dictionary, ...rest } = pageProps; return ( - // Use the next-localization (w/ rosetta) library to provide our translation dictionary to the app. - // Note Next.js does not (currently) provide anything for translation, only i18n routing. - // If your app is not multilingual, next-localization and references to it can be removed. - - - + <> + + {/* + // Use the next-localization (w/ rosetta) library to provide our translation dictionary to the app. + // Note Next.js does not (currently) provide anything for translation, only i18n routing. + // If your app is not multilingual, next-localization and references to it can be removed. + */} + + + + ); } diff --git a/packages/sitecore-jss-nextjs/context.d.ts b/packages/sitecore-jss-nextjs/context.d.ts new file mode 100644 index 0000000000..72e501678d --- /dev/null +++ b/packages/sitecore-jss-nextjs/context.d.ts @@ -0,0 +1 @@ +export * from './types/context/index'; diff --git a/packages/sitecore-jss-nextjs/context.js b/packages/sitecore-jss-nextjs/context.js new file mode 100644 index 0000000000..e9529d5720 --- /dev/null +++ b/packages/sitecore-jss-nextjs/context.js @@ -0,0 +1 @@ +module.exports = require('./dist/cjs/context/index'); diff --git a/packages/sitecore-jss-nextjs/package.json b/packages/sitecore-jss-nextjs/package.json index 040b166c0e..88879dbef9 100644 --- a/packages/sitecore-jss-nextjs/package.json +++ b/packages/sitecore-jss-nextjs/package.json @@ -11,7 +11,7 @@ "test": "mocha --require ./test/setup.js \"./src/**/*.test.ts\" \"./src/**/*.test.tsx\" --exit", "prepublishOnly": "npm run build", "coverage": "nyc npm test", - "generate-docs": "npx typedoc --plugin typedoc-plugin-markdown --readme none --out ../../ref-docs/sitecore-jss-nextjs --entryPoints src/index.ts --entryPoints src/monitoring/index.ts --entryPoints src/editing/index.ts --entryPoints src/middleware/index.ts --githubPages false" + "generate-docs": "npx typedoc --plugin typedoc-plugin-markdown --readme none --out ../../ref-docs/sitecore-jss-nextjs --entryPoints src/index.ts --entryPoints src/monitoring/index.ts --entryPoints src/editing/index.ts --entryPoints src/middleware/index.ts --entryPoints src/context/index.ts --entryPoints src/utils/index.ts --entryPoints src/site/index.ts --entryPoints src/graphql/index.ts --githubPages false" }, "engines": { "node": ">=12", @@ -30,7 +30,7 @@ "url": "https://github.com/sitecore/jss/issues" }, "devDependencies": { - "@sitecore/engage": "^1.4.1", + "@sitecore-cloudsdk/personalize": "^0.1.1", "@types/chai": "^4.3.4", "@types/chai-as-promised": "^7.1.5", "@types/chai-string": "^1.4.2", @@ -66,7 +66,8 @@ "typescript": "~4.9.4" }, "peerDependencies": { - "@sitecore/engage": "^1.4.1", + "@sitecore-cloudsdk/events": "^0.1.1", + "@sitecore-cloudsdk/personalize": "^0.1.1", "next": "^13.4.16", "react": "^18.2.0", "react-dom": "^18.2.0" diff --git a/packages/sitecore-jss-nextjs/src/context/context.test.ts b/packages/sitecore-jss-nextjs/src/context/context.test.ts new file mode 100644 index 0000000000..8d7f528500 --- /dev/null +++ b/packages/sitecore-jss-nextjs/src/context/context.test.ts @@ -0,0 +1,163 @@ +/* eslint-disable no-unused-expressions */ +/* eslint-disable dot-notation */ +import sinon from 'sinon'; +import { expect } from 'chai'; +import { Context } from './'; + +describe('Context', () => { + const sdks = { + Foo: { + sdk: { foo: true }, + init: () => + new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, 300); + }), + }, + Bar: { + sdk: { bar: true }, + init: () => { + return new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, 500); + }); + }, + }, + }; + + const fooInitSpy = sinon.spy(sdks.Foo, 'init'); + const barInitSpy = sinon.spy(sdks.Bar, 'init'); + + const props = { + sitecoreEdgeUrl: 'https://edgeurl', + sitecoreEdgeContextId: 'contextid', + siteName: '', + sdks, + }; + + afterEach(() => { + fooInitSpy.resetHistory(); + barInitSpy.resetHistory(); + }); + + describe('constructor', () => { + it('should create a new context', () => { + const context = new Context(props); + + expect(context.sitecoreEdgeUrl).to.equal(props.sitecoreEdgeUrl); + expect(context.sitecoreEdgeContextId).to.equal(props.sitecoreEdgeContextId); + expect(context.siteName).to.equal(props.siteName); + }); + }); + + describe('init', () => { + it('should initialize the context', (done) => { + const context = new Context(props); + + context.init(); + + expect(context.isInitialized).to.be.true; + expect(context.siteName).to.equal(props.siteName); + + expect(context.sdks.Bar).to.equal(undefined); + expect(context.sdks.Foo).to.equal(undefined); + + Promise.all([ + context.getSDK('Foo')?.then((sdk) => { + expect(fooInitSpy.calledOnce).to.be.true; + expect(sdk).to.deep.equal(sdks.Foo.sdk); + + return; + }), + context.getSDK('Bar')?.then((sdk) => { + expect(barInitSpy.calledOnce).to.be.true; + expect(sdk).to.deep.equal(sdks.Bar.sdk); + + return; + }), + ]).then(() => { + expect(context.sdks.Foo).to.deep.equal(sdks.Foo.sdk); + expect(context.sdks.Bar).to.deep.equal(sdks.Bar.sdk); + + done(); + }); + }); + + it('should initialize the context with a different site name', (done) => { + const context = new Context(props); + + context.init({ siteName: 'website' }); + + expect(context.isInitialized).to.be.true; + expect(context.siteName).to.equal('website'); + + expect(context.sdks.Bar).to.equal(undefined); + expect(context.sdks.Foo).to.equal(undefined); + + Promise.all([ + context.getSDK('Foo')?.then((sdk) => { + expect(fooInitSpy.calledOnce).to.be.true; + expect(sdk).to.deep.equal(sdks.Foo.sdk); + + return; + }), + context.getSDK('Bar')?.then((sdk) => { + expect(barInitSpy.calledOnce).to.be.true; + expect(sdk).to.deep.equal(sdks.Bar.sdk); + + return; + }), + ]).then(() => { + expect(context.sdks.Foo).to.deep.equal(sdks.Foo.sdk); + expect(context.sdks.Bar).to.deep.equal(sdks.Bar.sdk); + + done(); + }); + }); + + it('should not initialize the context if it is already initialized', () => { + const context = new Context(props); + + context.init({ siteName: 'website-1' }); + + expect(context.isInitialized).to.be.true; + + context.init({ siteName: 'website-2' }); + + expect(context.siteName).to.equal('website-1'); + }); + }); + + describe('getSDK', () => { + it('should return the SDKs', (done) => { + const context = new Context(props); + + context.init(); + + expect(context.sdks.Bar).to.equal(undefined); + expect(context.sdks.Foo).to.equal(undefined); + + Promise.all([ + context.getSDK('Foo')?.then((sdk) => { + expect(fooInitSpy.calledOnce).to.be.true; + expect(sdk).to.deep.equal(sdks.Foo.sdk); + + return; + }), + context.getSDK('Bar')?.then((sdk) => { + expect(barInitSpy.calledOnce).to.be.true; + expect(sdk).to.deep.equal(sdks.Bar.sdk); + + return; + }), + ]).then(() => { + expect(context.sdks.Foo).to.deep.equal(sdks.Foo.sdk); + expect(context.sdks.Bar).to.deep.equal(sdks.Bar.sdk); + + done(); + }); + }); + }); +}); diff --git a/packages/sitecore-jss-nextjs/src/context/context.ts b/packages/sitecore-jss-nextjs/src/context/context.ts new file mode 100644 index 0000000000..89fc384544 --- /dev/null +++ b/packages/sitecore-jss-nextjs/src/context/context.ts @@ -0,0 +1,133 @@ +/** + * Software Development Kit (SDK) instance + */ +export type SDK = { + /** + * The Software Development Kit (SDK) library instance + */ + sdk: SDKType; + /** + * Initializes the Software Development Kit (SDK) + */ + init: (props: InitSDKProps) => Promise; +}; + +/** + * Software Development Kits (SDKs) to be initialized + */ +type SDKModulesType = Record; + +/** + * Properties that are passed to the Context. + */ +export interface ContextInitProps { + /** + * Your Sitecore site name + */ + siteName?: string; +} + +/** + * Configuration that is passed to the Context. + */ +export interface ContextConfig { + /** + * Your Sitecore Edge URL + */ + sitecoreEdgeUrl: string; + /** + * Your Sitecore Edge Context ID + */ + sitecoreEdgeContextId: string; + /** + * Your Sitecore site name + */ + siteName: string; + /** + * Software Development Kits (SDKs) to be initialized + */ + sdks: { [module in keyof SDKModules]: SDKModules[module] }; +} + +/** + * Properties that are passed to the Software Development Kit (SDK) initialization function. + */ +type InitSDKProps = Omit, 'sdks'>; + +/** + * Context instance that is used to initialize the application Context and associated Software Development Kits (SDKs). + */ +export class Context { + /** + * Indicates whether the Context and SDK(s) have been initialized + */ + public isInitialized = false; + /** + * The Sitecore Edge URL + */ + public readonly sitecoreEdgeUrl: string; + /** + * The Sitecore Edge Context ID + */ + public readonly sitecoreEdgeContextId: string; + /** + * The Sitecore site name + */ + public siteName: string; + /** + * Software Development Kits (SDKs) to be initialized + */ + public readonly sdks: { [module in keyof SDKModules]?: SDKModules[module]['sdk'] } = {}; + /** + * Promises for the SDKs + */ + protected sdkPromises: { [module in keyof SDKModules]?: Promise } = {}; + + constructor(protected props: ContextConfig) { + this.sitecoreEdgeUrl = props.sitecoreEdgeUrl; + this.sitecoreEdgeContextId = props.sitecoreEdgeContextId; + this.siteName = props.siteName; + } + + public init(props: ContextInitProps = {}) { + // Context and SDKs are initialized only once + if (this.isInitialized) return; + + this.isInitialized = true; + + if (props.siteName) { + this.siteName = props.siteName; + } + + // iterate over the SDKs and initialize them + for (const sdkName of Object.keys(this.props.sdks) as (keyof SDKModules)[]) { + this.initSDK(sdkName); + } + } + + /** + * Retrieves the Software Development Kit (SDK) instance, ensuring it is initialized before returning + * + * @param {string} name SDK name + * @returns initialized SDK + */ + public getSDK(name: T): Promise | undefined { + return this.sdkPromises[name]; + } + + /** + * Initializes the Software Development Kit (SDK) + * + * @param {T} name SDK name + * @returns {void} + */ + protected initSDK(name: T): void { + this.sdkPromises[name] = new Promise((resolve) => { + this.props.sdks[name].init(this).then(() => { + this.sdks[name] = this.props.sdks[name].sdk; + + resolve(this.sdks[name]); + }); + }); + } +} diff --git a/packages/sitecore-jss-nextjs/src/context/index.ts b/packages/sitecore-jss-nextjs/src/context/index.ts new file mode 100644 index 0000000000..b7719c3c1b --- /dev/null +++ b/packages/sitecore-jss-nextjs/src/context/index.ts @@ -0,0 +1 @@ +export { Context, ContextConfig, SDK } from './context'; diff --git a/packages/sitecore-jss-nextjs/src/index.ts b/packages/sitecore-jss-nextjs/src/index.ts index e8f332c66e..7335fa4782 100644 --- a/packages/sitecore-jss-nextjs/src/index.ts +++ b/packages/sitecore-jss-nextjs/src/index.ts @@ -101,7 +101,6 @@ export { getPersonalizedRewriteData, normalizePersonalizedRewrite, CdpHelper, - PosResolver, } from '@sitecore-jss/sitecore-jss/personalize'; export { @@ -166,6 +165,8 @@ export { BYOCWrapper }; export { ComponentBuilder, ComponentBuilderConfig } from './ComponentBuilder'; +export { Context, ContextConfig, SDK } from './context'; + export { ComponentFactory, Image, diff --git a/packages/sitecore-jss-nextjs/src/middleware/personalize-middleware.test.ts b/packages/sitecore-jss-nextjs/src/middleware/personalize-middleware.test.ts index 20a9e6a1b1..7383065f00 100644 --- a/packages/sitecore-jss-nextjs/src/middleware/personalize-middleware.test.ts +++ b/packages/sitecore-jss-nextjs/src/middleware/personalize-middleware.test.ts @@ -8,7 +8,6 @@ import nextjs, { NextRequest, NextResponse } from 'next/server'; import { debug } from '@sitecore-jss/sitecore-jss'; import { SiteResolver } from '@sitecore-jss/sitecore-jss/site'; import { PersonalizeMiddleware } from './personalize-middleware'; -import { ExperienceParams } from '@sitecore-jss/sitecore-jss/personalize'; use(sinonChai); const expect = chai.use(chaiString).expect; @@ -33,17 +32,7 @@ describe('PersonalizeMiddleware', () => { const variantIds = ['variant-1', 'variant-2']; const contentId = `${id}_en_${version}`.toLowerCase(); const defaultLang = 'en'; - const pointOfSale = 'cdp-pos'; const referrer = 'http://localhost:3000'; - const experienceParams: ExperienceParams = { - referrer, - utm: { - campaign: 'utm_campaign', - content: null, - medium: null, - source: null, - }, - }; const createRequest = (props: any = {}) => { const req = { ...props, @@ -164,8 +153,8 @@ describe('PersonalizeMiddleware', () => { } = {} ) => { const cdpConfig = { - clientKey: 'cdp-client-key', - endpoint: 'http://cdp-endpoint', + sitecoreEdgeContextId: '0000-0000-0000', + sitecoreEdgeUrl: 'https://foo.bar', ...(props?.cdpConfig || {}), }; const edgeConfig = { @@ -179,18 +168,12 @@ describe('PersonalizeMiddleware', () => { name: siteName, language: props.language || '', hostName: hostname, - pointOfSale: { - [props.language || defaultLang]: pointOfSale, - }, })); getByHost = sinon.stub().callsFake((hostName: string) => ({ name: siteName, language: props.language || '', hostName, - pointOfSale: { - [props.language || defaultLang]: pointOfSale, - }, })); } @@ -202,24 +185,13 @@ describe('PersonalizeMiddleware', () => { edgeConfig, }); - const engageServer = (middleware['initializeEngageServer'] = sinon.stub().returns({ - handleCookie: async () => { - return props.handleCookieStub || Promise.resolve(); - }, - event: async () => { - return Promise.resolve(null); - }, - personalize: async () => { - return Promise.resolve({ variantId: props.variantId }); - }, - identity: async () => { - return Promise.resolve(null); - }, - pageView: async () => { - return Promise.resolve(null); - }, - version: '1.0', - })); + const initPersonalizeServer = (middleware['initPersonalizeServer'] = sinon.stub()); + + const personalize = (middleware['personalize'] = sinon.stub().returns( + Promise.resolve({ + variantId: props.variantId, + }) + )); const getPersonalizeInfo = (middleware['personalizeService']['getPersonalizeInfo'] = props.getPersonalizeInfoStub || @@ -238,7 +210,8 @@ describe('PersonalizeMiddleware', () => { middleware, getPersonalizeInfo, siteResolver, - engageServer, + initPersonalizeServer, + personalize, }; }; @@ -439,7 +412,12 @@ describe('PersonalizeMiddleware', () => { it('no variant identified', async () => { const req = createRequest(); const res = createResponse(); - const { middleware, getPersonalizeInfo, engageServer } = createMiddleware({ + const { + middleware, + getPersonalizeInfo, + initPersonalizeServer, + personalize, + } = createMiddleware({ variantId: undefined, }); const headers = {}; @@ -452,7 +430,8 @@ describe('PersonalizeMiddleware', () => { headers, }); expect(getPersonalizeInfo.calledWith('/styleguide', 'en')).to.be.true; - expect(engageServer.called).to.be.true; + expect(initPersonalizeServer.called).to.be.true; + expect(personalize.called).to.be.true; validateDebugLog('skipped (no variant identified)'); expect(finalRes).to.deep.equal(res); }); @@ -460,7 +439,12 @@ describe('PersonalizeMiddleware', () => { const req = createRequest(); const res = createResponse(); const handleCookieStub = sinon.stub().resolves(); - const { middleware, getPersonalizeInfo, engageServer } = createMiddleware({ + const { + middleware, + getPersonalizeInfo, + initPersonalizeServer, + personalize, + } = createMiddleware({ variantId: 'invalid-variant', handleCookieStub, }); @@ -474,7 +458,8 @@ describe('PersonalizeMiddleware', () => { headers, }); expect(getPersonalizeInfo.calledWith('/styleguide', 'en')).to.be.true; - expect(engageServer.called).to.be.true; + expect(initPersonalizeServer.called).to.be.true; + expect(personalize.called).to.be.true; validateDebugLog('skipped (invalid variant)'); expect(finalRes).to.deep.equal(res); }); @@ -492,7 +477,13 @@ describe('PersonalizeMiddleware', () => { }); const res = createResponse(); const nextRewriteStub = sinon.stub(nextjs.NextResponse, 'rewrite').returns(res); - const { middleware, getPersonalizeInfo, siteResolver, engageServer } = createMiddleware({ + const { + middleware, + getPersonalizeInfo, + siteResolver, + initPersonalizeServer, + personalize, + } = createMiddleware({ language, variantId: 'variant-2', personalizeInfo: { @@ -511,7 +502,8 @@ describe('PersonalizeMiddleware', () => { language: language, }); expect(getPersonalizeInfo.calledWith('/styleguide', 'da-DK')).to.be.true; - expect(engageServer.calledOnce).to.be.true; + expect(initPersonalizeServer.calledOnce).to.be.true; + expect(personalize.calledOnce).to.be.true; validateEndMessageDebugLog('personalize middleware end in %dms: %o', { rewritePath: '/_variantId_variant-2/styleguide', headers: { @@ -534,7 +526,13 @@ describe('PersonalizeMiddleware', () => { }); const res = createResponse(); const nextRewriteStub = sinon.stub(nextjs.NextResponse, 'rewrite').returns(res); - const { middleware, getPersonalizeInfo, siteResolver, engageServer } = createMiddleware({ + const { + middleware, + getPersonalizeInfo, + siteResolver, + initPersonalizeServer, + personalize, + } = createMiddleware({ variantId: 'variant-2', }); const finalRes = await middleware.getHandler()(req, res); @@ -548,7 +546,8 @@ describe('PersonalizeMiddleware', () => { language: 'en', }); expect(getPersonalizeInfo.calledWith('/styleguide', 'en')).to.be.true; - expect(engageServer.calledOnce).to.be.true; + expect(initPersonalizeServer.calledOnce).to.be.true; + expect(personalize.calledOnce).to.be.true; validateEndMessageDebugLog('personalize middleware end in %dms: %o', { rewritePath: '/_variantId_variant-2/styleguide', headers: { @@ -566,13 +565,20 @@ describe('PersonalizeMiddleware', () => { const req = createRequest(); const res = createResponse(); const nextRewriteStub = sinon.stub(nextjs.NextResponse, 'rewrite').returns(res); - const { middleware, getPersonalizeInfo, siteResolver, engageServer } = createMiddleware({ + const { + middleware, + getPersonalizeInfo, + siteResolver, + initPersonalizeServer, + personalize, + } = createMiddleware({ variantId: 'variant-2', }); const finalRes = await middleware.getHandler()(req); expect(getPersonalizeInfo.calledWith('/styleguide', 'en')).to.be.true; - expect(engageServer.calledOnce).to.be.true; + expect(initPersonalizeServer.calledOnce).to.be.true; + expect(personalize.calledOnce).to.be.true; validateDebugLog('personalize middleware start: %o', { headers: { ...req.headers, @@ -599,7 +605,13 @@ describe('PersonalizeMiddleware', () => { const req = createRequest({ headerValues: { referer: null } }); const res = createResponse(); const nextRewriteStub = sinon.stub(nextjs.NextResponse, 'rewrite').returns(res); - const { middleware, getPersonalizeInfo, siteResolver, engageServer } = createMiddleware({ + const { + middleware, + getPersonalizeInfo, + siteResolver, + initPersonalizeServer, + personalize, + } = createMiddleware({ variantId: 'variant-2', }); const finalRes = await middleware.getHandler()(req, res); @@ -613,7 +625,8 @@ describe('PersonalizeMiddleware', () => { language: 'en', }); expect(getPersonalizeInfo.calledWith('/styleguide', 'en')).to.be.true; - expect(engageServer.calledOnce).to.be.true; + expect(initPersonalizeServer.calledOnce).to.be.true; + expect(personalize.calledOnce).to.be.true; validateEndMessageDebugLog('personalize middleware end in %dms: %o', { rewritePath: '/_variantId_variant-2/styleguide', headers: { @@ -635,7 +648,13 @@ describe('PersonalizeMiddleware', () => { }, }); const nextRewriteStub = sinon.stub(nextjs.NextResponse, 'rewrite').returns(res); - const { middleware, getPersonalizeInfo, engageServer, siteResolver } = createMiddleware({ + const { + middleware, + getPersonalizeInfo, + initPersonalizeServer, + personalize, + siteResolver, + } = createMiddleware({ variantId: 'variant-2', }); const finalRes = await middleware.getHandler()(req, res); @@ -649,7 +668,8 @@ describe('PersonalizeMiddleware', () => { language: 'en', }); expect(getPersonalizeInfo.calledWith('/styleguide', 'en', 'foo')).to.be.true; - expect(engageServer.calledOnce).to.be.true; + expect(initPersonalizeServer.calledOnce).to.be.true; + expect(personalize.calledOnce).to.be.true; validateEndMessageDebugLog('personalize middleware end in %dms: %o', { rewritePath: '/_variantId_variant-2/styleguide', headers: { @@ -672,7 +692,13 @@ describe('PersonalizeMiddleware', () => { }, }); const nextRewriteStub = sinon.stub(nextjs.NextResponse, 'rewrite').returns(res); - const { middleware, getPersonalizeInfo, engageServer, siteResolver } = createMiddleware({ + const { + middleware, + getPersonalizeInfo, + initPersonalizeServer, + personalize, + siteResolver, + } = createMiddleware({ variantId: 'variant-2', }); const finalRes = await middleware.getHandler()(req, res); @@ -686,7 +712,8 @@ describe('PersonalizeMiddleware', () => { language: 'en', }); expect(getPersonalizeInfo.calledWith('/styleguide', 'en', siteName)).to.be.true; - expect(engageServer.calledOnce).to.be.true; + expect(initPersonalizeServer.calledOnce).to.be.true; + expect(personalize.calledOnce).to.be.true; validateEndMessageDebugLog('personalize middleware end in %dms: %o', { rewritePath: '/_variantId_variant-2/_site_nextjs-app/styleguide', headers: { @@ -708,7 +735,13 @@ describe('PersonalizeMiddleware', () => { }); const res = createResponse(); const nextRewriteStub = sinon.stub(nextjs.NextResponse, 'rewrite').returns(res); - const { middleware, getPersonalizeInfo, engageServer, siteResolver } = createMiddleware({ + const { + middleware, + getPersonalizeInfo, + initPersonalizeServer, + personalize, + siteResolver, + } = createMiddleware({ variantId: 'variant-2', }); const finalRes = await middleware.getHandler()(req, res); @@ -722,7 +755,8 @@ describe('PersonalizeMiddleware', () => { language: 'en', }); expect(getPersonalizeInfo.calledWith('/styleguide', 'en', siteName)).to.be.true; - expect(engageServer.calledOnce).to.be.true; + expect(initPersonalizeServer.calledOnce).to.be.true; + expect(personalize.calledOnce).to.be.true; validateEndMessageDebugLog('personalize middleware end in %dms: %o', { rewritePath: '/_variantId_variant-2/styleguide', headers: { @@ -744,12 +778,19 @@ describe('PersonalizeMiddleware', () => { }); const res = createResponse(); const nextRewriteStub = sinon.stub(nextjs.NextResponse, 'rewrite').returns(res); - const { middleware, getPersonalizeInfo, engageServer, siteResolver } = createMiddleware({ + const { + middleware, + getPersonalizeInfo, + initPersonalizeServer, + personalize, + siteResolver, + } = createMiddleware({ variantId: 'variant-2', defaultHostname: 'foobar', }); const finalRes = await middleware.getHandler()(req, res); - expect(engageServer.calledOnce).to.be.true; + expect(initPersonalizeServer.calledOnce).to.be.true; + expect(personalize.calledOnce).to.be.true; validateDebugLog('personalize middleware start: %o', { headers: { ...req.headers }, hostname: 'foobar', @@ -796,13 +837,19 @@ describe('PersonalizeMiddleware', () => { const getPersonalizeInfoWithError = sinon.stub().throws(error); - const { middleware, getPersonalizeInfo, engageServer } = createMiddleware({ + const { + middleware, + getPersonalizeInfo, + initPersonalizeServer, + personalize, + } = createMiddleware({ getPersonalizeInfoStub: getPersonalizeInfoWithError, }); const finalRes = await middleware.getHandler()(req, res); - expect(engageServer.called).to.be.false; + expect(initPersonalizeServer.called).to.be.false; + expect(personalize.called).to.be.false; expect(getPersonalizeInfo.called).to.be.true; expect(errorSpy.getCall(0).calledWith('Personalize middleware failed:')).to.be.true; diff --git a/packages/sitecore-jss-nextjs/src/middleware/personalize-middleware.ts b/packages/sitecore-jss-nextjs/src/middleware/personalize-middleware.ts index 8f43d8e11d..158fff0d04 100644 --- a/packages/sitecore-jss-nextjs/src/middleware/personalize-middleware.ts +++ b/packages/sitecore-jss-nextjs/src/middleware/personalize-middleware.ts @@ -3,22 +3,22 @@ import { GraphQLPersonalizeService, GraphQLPersonalizeServiceConfig, getPersonalizedRewrite, - PosResolver, + PersonalizeInfo, } from '@sitecore-jss/sitecore-jss/personalize'; -import { SiteInfo } from '@sitecore-jss/sitecore-jss/site'; import { debug } from '@sitecore-jss/sitecore-jss'; import { MiddlewareBase, MiddlewareBaseConfig } from './middleware'; -import { initServer, EngageServer } from '@sitecore/engage'; +import { init, personalize } from '@sitecore-cloudsdk/personalize/server'; export type CdpServiceConfig = { /** - * Your Sitecore CDP API endpoint + * Your Sitecore Edge Platform endpoint + * Default is https://edge-platform.sitecorecloud.io */ - endpoint: string; + sitecoreEdgeUrl?: string; /** - * The client key to use for authentication + * Your unified Sitecore Edge Context Id */ - clientKey: string; + sitecoreEdgeContextId: string; /** * The Sitecore CDP channel to use for events. Uses 'WEB' by default. */ @@ -42,13 +42,6 @@ export type PersonalizeMiddlewareConfig = MiddlewareBaseConfig & { * Configuration for your Sitecore CDP endpoint */ cdpConfig: CdpServiceConfig; - /** - * function to resolve point of sale for a site - * @param {Siteinfo} site to get info from - * @param {string} language to get info for - * @returns point of sale value for site/language combination - */ - getPointOfSale?: (site: SiteInfo, language: string) => string; }; /** @@ -101,22 +94,55 @@ export class PersonalizeMiddleware extends MiddlewareBase { }; } - protected initializeEngageServer( - hostName: string, - site: SiteInfo, - language: string - ): EngageServer { - const engageServer = initServer({ - clientKey: this.config.cdpConfig.clientKey, - targetURL: this.config.cdpConfig.endpoint, - pointOfSale: this.config.getPointOfSale - ? this.config.getPointOfSale(site, language) - : PosResolver.resolve(site, language), - cookieDomain: hostName, - forceServerCookieMode: true, - }); + protected async initPersonalizeServer({ + hostname, + siteName, + request, + response, + }: { + hostname: string; + siteName: string; + request: NextRequest; + response: NextResponse; + }): Promise { + await init( + { + sitecoreEdgeUrl: this.config.cdpConfig.sitecoreEdgeUrl, + sitecoreEdgeContextId: this.config.cdpConfig.sitecoreEdgeContextId, + siteName, + cookieDomain: hostname, + enableServerCookie: true, + }, + request, + response + ); + } - return engageServer; + protected async personalize( + { + params, + personalizeInfo, + language, + timeout, + }: { + personalizeInfo: PersonalizeInfo; + params: ExperienceParams; + language: string; + timeout?: number; + }, + request: NextRequest + ) { + const personalizationData = { + channel: this.config.cdpConfig.channel || 'WEB', + currency: this.config.cdpConfig.currency ?? 'USD', + friendlyId: personalizeInfo.contentId, + params, + language, + }; + + return (await personalize(personalizationData, request, timeout)) as { + variantId: string; + }; } protected getExperienceParams(req: NextRequest): ExperienceParams { @@ -194,36 +220,27 @@ export class PersonalizeMiddleware extends MiddlewareBase { return response; } - const engageServer = this.initializeEngageServer(hostname, site, language); + await this.initPersonalizeServer({ + hostname, + siteName: site.name, + request: req, + response, + }); - // creates the browser ID cookie on the server side - // and includes the cookie in the response header - try { - await engageServer.handleCookie(req, response, timeout); - } catch (error) { - debug.personalize('skipped (browser id generation failed)'); - throw error; - } const params = this.getExperienceParams(req); debug.personalize('executing experience for %s %s %o', personalizeInfo.contentId, params); - const personalizationData = { - channel: this.config.cdpConfig.channel || 'WEB', - currency: this.config.cdpConfig.currency ?? 'USA', - friendlyId: personalizeInfo.contentId, - params, - language, - }; - let variantId; - // Execute targeted experience in CDP + // Execute targeted experience in Personalize SDK // eslint-disable-next-line no-useless-catch try { - variantId = ((await engageServer.personalize(personalizationData, req, timeout)) as { - variantId: string; - }).variantId; + const personalization = await this.personalize( + { personalizeInfo, params, language, timeout }, + req + ); + variantId = personalization.variantId; } catch (error) { throw error; } diff --git a/packages/sitecore-jss-nextjs/tsconfig.json b/packages/sitecore-jss-nextjs/tsconfig.json index 348881666f..9d2cf9eaff 100644 --- a/packages/sitecore-jss-nextjs/tsconfig.json +++ b/packages/sitecore-jss-nextjs/tsconfig.json @@ -14,6 +14,7 @@ "types", "typings", "dist", + "context.d.ts", "middleware.d.ts", "editing.d.ts", "monitoring.d.ts", 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 c847d26d6d..b2a53976b4 100644 --- a/packages/sitecore-jss/src/graphql/graphql-edge-proxy.test.ts +++ b/packages/sitecore-jss/src/graphql/graphql-edge-proxy.test.ts @@ -11,7 +11,7 @@ describe('graphql-edge-proxy', () => { const url = getEdgeProxyContentUrl(sitecoreEdgeContextId); expect(url).to.equal( - `${SITECORE_EDGE_URL_DEFAULT}/content/api/graphql/v1?sitecoreContextId=0730fc5a-3333-5555-5555-08db6d7ddb49` + `${SITECORE_EDGE_URL_DEFAULT}/v1/content/api/graphql/v1?sitecoreContextId=0730fc5a-3333-5555-5555-08db6d7ddb49` ); }); @@ -22,7 +22,7 @@ describe('graphql-edge-proxy', () => { const url = getEdgeProxyContentUrl(sitecoreEdgeContextId, sitecoreEdgeUrl); expect(url).to.equal( - 'https://test.com/content/api/graphql/v1?sitecoreContextId=0730fc5a-3333-5555-5555-08db6d7ddb49' + 'https://test.com/v1/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 b869b71944..abb0ce734b 100644 --- a/packages/sitecore-jss/src/graphql/graphql-edge-proxy.ts +++ b/packages/sitecore-jss/src/graphql/graphql-edge-proxy.ts @@ -9,4 +9,4 @@ import { SITECORE_EDGE_URL_DEFAULT } from '../constants'; export const getEdgeProxyContentUrl = ( sitecoreEdgeContextId: string, sitecoreEdgeUrl = SITECORE_EDGE_URL_DEFAULT -) => `${sitecoreEdgeUrl}/content/api/graphql/v1?sitecoreContextId=${sitecoreEdgeContextId}`; +) => `${sitecoreEdgeUrl}/v1/content/api/graphql/v1?sitecoreContextId=${sitecoreEdgeContextId}`; diff --git a/packages/sitecore-jss/src/personalize/index.ts b/packages/sitecore-jss/src/personalize/index.ts index 55363c3c2a..7f51a7fbf2 100644 --- a/packages/sitecore-jss/src/personalize/index.ts +++ b/packages/sitecore-jss/src/personalize/index.ts @@ -1,6 +1,6 @@ export { personalizeLayout } from './layout-personalizer'; -export { PosResolver } from './pos-resolver'; export { + PersonalizeInfo, GraphQLPersonalizeService, GraphQLPersonalizeServiceConfig, } from './graphql-personalize-service'; diff --git a/packages/sitecore-jss/src/personalize/pos-resolver.test.ts b/packages/sitecore-jss/src/personalize/pos-resolver.test.ts deleted file mode 100644 index 25cea8c230..0000000000 --- a/packages/sitecore-jss/src/personalize/pos-resolver.test.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { expect } from 'chai'; -import { SiteInfo } from '../site'; -import { PosResolver } from './pos-resolver'; - -describe('resolvePointOfSale', () => { - it('should return empty when no point of sale present', () => { - const site: SiteInfo = { - name: 'no-pos', - hostName: 'www.nopos.com', - language: 'en', - }; - const language = 'en'; - const result = PosResolver.resolve(site, language); - expect(result).to.equal(''); - }); - - it('should return pos for provided language', () => { - const myPoint = 'apos.com'; - const site: SiteInfo = { - name: 'apos', - hostName: 'www.apos.com', - pointOfSale: { - en: myPoint, - }, - language: 'de-DE', - }; - - const result = PosResolver.resolve(site, 'en'); - expect(result).to.equal(myPoint); - }); - - it('should use fallback wildcard value as first backup', () => { - const site: SiteInfo = { - name: 'apos', - hostName: 'www.apos.com', - pointOfSale: { - 'de-DE': 'depos.com', - 'es-ES': 'espos.com', - '*': 'fallpos.com', - }, - language: 'de-DE', - }; - - const result = PosResolver.resolve(site, 'en'); - expect(result).to.equal('fallpos.com'); - }); - - it('should return pos for site language as second backup', () => { - const site: SiteInfo = { - name: 'apos', - hostName: 'www.apos.com', - pointOfSale: { - 'de-DE': 'depos.com', - 'es-ES': 'espos.com', - }, - language: 'de-DE', - }; - - const result = PosResolver.resolve(site, 'en'); - expect(result).to.equal('depos.com'); - }); - - it('should return pos for site language when provided language is empty', () => { - const site: SiteInfo = { - name: 'apos', - hostName: 'www.apos.com', - pointOfSale: { - 'de-DE': 'depos.com', - 'es-ES': 'espos.com', - }, - language: 'de-DE', - }; - - const result = PosResolver.resolve(site, ''); - expect(result).to.equal('depos.com'); - }); -}); diff --git a/packages/sitecore-jss/src/personalize/pos-resolver.ts b/packages/sitecore-jss/src/personalize/pos-resolver.ts deleted file mode 100644 index 4c0e2e8510..0000000000 --- a/packages/sitecore-jss/src/personalize/pos-resolver.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { SiteInfo } from '../site'; - -export class PosResolver { - static resolve = (site: SiteInfo, language: string) => { - return site.pointOfSale - ? site.pointOfSale[language] || site.pointOfSale['*'] || site.pointOfSale[site.language] - : ''; - }; -} 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 53f61b98a5..e1cdafd299 100644 --- a/packages/sitecore-jss/src/site/graphql-siteinfo-service.test.ts +++ b/packages/sitecore-jss/src/site/graphql-siteinfo-service.test.ts @@ -19,17 +19,14 @@ describe('GraphQLSiteInfoService', () => { name, hostName, language, - pointOfSale = '', }: { name: string; hostName: string; language: string; - pointOfSale: string; }): GraphQLSiteInfoResult => ({ name: { value: name }, hostName: { value: hostName }, language: { value: language }, - pointOfSale: { value: pointOfSale }, }); const nonEmptyResponse = ({ @@ -52,7 +49,6 @@ describe('GraphQLSiteInfoService', () => { name: `site ${start + n}`, hostName: 'restricted.gov', language: 'en', - pointOfSale: 'en=en-pos', }) ), ...sites, @@ -103,7 +99,6 @@ describe('GraphQLSiteInfoService', () => { name: 'public 0', hostName: 'pr.showercurtains.org', language: '', - pointOfSale: '', }), ], }) @@ -115,15 +110,11 @@ describe('GraphQLSiteInfoService', () => { name: 'site 0', hostName: 'restricted.gov', language: 'en', - pointOfSale: { - en: 'en-pos', - }, }, { name: 'public 0', hostName: 'pr.showercurtains.org', language: '', - pointOfSale: undefined, }, ]); }); @@ -136,7 +127,6 @@ describe('GraphQLSiteInfoService', () => { name: 'public 0', hostName: 'pr.showercurtains.org', language: '', - pointOfSale: '', }), ], }) @@ -152,15 +142,11 @@ describe('GraphQLSiteInfoService', () => { name: 'site 0', hostName: 'restricted.gov', language: 'en', - pointOfSale: { - en: 'en-pos', - }, }, { name: 'public 0', hostName: 'pr.showercurtains.org', language: '', - pointOfSale: undefined, }, ]); }); @@ -181,49 +167,31 @@ describe('GraphQLSiteInfoService', () => { name: 'site 0', hostName: 'restricted.gov', language: 'en', - pointOfSale: { - en: 'en-pos', - }, }, { name: 'site 1', hostName: 'restricted.gov', language: 'en', - pointOfSale: { - en: 'en-pos', - }, }, { name: 'site 2', hostName: 'restricted.gov', language: 'en', - pointOfSale: { - en: 'en-pos', - }, }, { name: 'site 3', hostName: 'restricted.gov', language: 'en', - pointOfSale: { - en: 'en-pos', - }, }, { name: 'site 4', hostName: 'restricted.gov', language: 'en', - pointOfSale: { - en: 'en-pos', - }, }, { name: 'site 5', hostName: 'restricted.gov', language: 'en', - pointOfSale: { - en: 'en-pos', - }, }, ]); }); @@ -257,7 +225,6 @@ describe('GraphQLSiteInfoService', () => { name: 'public 0', hostName: 'pr.showercurtains.org', language: '', - pointOfSale: '', }), ], }) @@ -273,15 +240,11 @@ describe('GraphQLSiteInfoService', () => { name: 'site 0', hostName: 'restricted.gov', language: 'en', - pointOfSale: { - en: 'en-pos', - }, }, { name: 'public 0', hostName: 'pr.showercurtains.org', language: '', - pointOfSale: undefined, }, ]); nock.cleanAll(); diff --git a/packages/sitecore-jss/src/site/graphql-siteinfo-service.ts b/packages/sitecore-jss/src/site/graphql-siteinfo-service.ts index c0b87758f4..b917fd6945 100644 --- a/packages/sitecore-jss/src/site/graphql-siteinfo-service.ts +++ b/packages/sitecore-jss/src/site/graphql-siteinfo-service.ts @@ -1,4 +1,3 @@ -import { URLSearchParams } from 'url'; import { GraphQLClient, GraphQLRequestClient, PageInfo } from '../graphql'; import debug from '../debug'; import { CacheClient, CacheOptions, MemoryCacheClient } from '../cache-client'; @@ -34,9 +33,6 @@ const defaultQuery = /* GraphQL */ ` language: field(name: "Language") { value } - pointOfSale: field(name: "POS") { - value - } } } } @@ -60,10 +56,6 @@ export type SiteInfo = { * Site default language */ language: string; - /** - * Site point of sale - */ - pointOfSale?: Record; }; export type GraphQLSiteInfoServiceConfig = CacheOptions & { @@ -107,9 +99,6 @@ export type GraphQLSiteInfoResult = { language: { value: string; }; - pointOfSale?: { - value: string; - }; }; export class GraphQLSiteInfoService { @@ -150,9 +139,6 @@ export class GraphQLSiteInfoService { }); const result = response?.search?.results?.reduce((result, current) => { result.push({ - pointOfSale: current.pointOfSale?.value - ? Object.fromEntries(new URLSearchParams(current.pointOfSale.value)) - : undefined, name: current.name.value, hostName: current.hostName.value, language: current.language.value, diff --git a/yarn.lock b/yarn.lock index 1e6af67912..bcc41b6678 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6433,6 +6433,32 @@ __metadata: languageName: node linkType: hard +"@sitecore-cloudsdk/core@npm:^0.1.0-rc.2": + version: 0.1.0-rc.4 + resolution: "@sitecore-cloudsdk/core@npm:0.1.0-rc.4" + dependencies: + "@sitecore-cloudsdk/utils": ^0.1.0-rc.2 + checksum: 596ebb923e8a7598b15c7762c6a8c3f7e6bc1fb9746e8ae51c656a7c3dd91a39e75b089abb8f4a1784f6dba2cf4115d9947f9fada26d6022191985acb4ee28a5 + languageName: node + linkType: hard + +"@sitecore-cloudsdk/personalize@npm:^0.1.1": + version: 0.1.1 + resolution: "@sitecore-cloudsdk/personalize@npm:0.1.1" + dependencies: + "@sitecore-cloudsdk/core": ^0.1.0-rc.2 + "@sitecore-cloudsdk/utils": ^0.1.0-rc.2 + checksum: e32a4f4b416a39c7657e475c49b6793ecc376a961e318cf3065ef148f78d6c00207ce2364d492a43ec5729322450a5faab0082ce3dafd580af4c26df9212530f + languageName: node + linkType: hard + +"@sitecore-cloudsdk/utils@npm:^0.1.0-rc.2": + version: 0.1.0-rc.4 + resolution: "@sitecore-cloudsdk/utils@npm:0.1.0-rc.4" + checksum: 28bce2648560a6b9e0621228642cc590baddc3ad1c335acec9ba0c45934472c1a3455048f9d9e4a566f933044fb1cc39c8b59f0ec1e60e878e9337f4227b72e5 + languageName: node + linkType: hard + "@sitecore-feaas/clientside@npm:^0.4.12": version: 0.4.12 resolution: "@sitecore-feaas/clientside@npm:0.4.12" @@ -6620,10 +6646,10 @@ __metadata: version: 0.0.0-use.local resolution: "@sitecore-jss/sitecore-jss-nextjs@workspace:packages/sitecore-jss-nextjs" dependencies: + "@sitecore-cloudsdk/personalize": ^0.1.1 "@sitecore-jss/sitecore-jss": 21.7.0-canary.6 "@sitecore-jss/sitecore-jss-dev-tools": 21.7.0-canary.6 "@sitecore-jss/sitecore-jss-react": 21.7.0-canary.6 - "@sitecore/engage": ^1.4.1 "@types/chai": ^4.3.4 "@types/chai-as-promised": ^7.1.5 "@types/chai-string": ^1.4.2 @@ -6663,7 +6689,8 @@ __metadata: ts-node: ^10.9.1 typescript: ~4.9.4 peerDependencies: - "@sitecore/engage": ^1.4.1 + "@sitecore-cloudsdk/events": ^0.1.1 + "@sitecore-cloudsdk/personalize": ^0.1.1 next: ^13.4.16 react: ^18.2.0 react-dom: ^18.2.0 @@ -6914,13 +6941,6 @@ __metadata: languageName: node linkType: hard -"@sitecore/engage@npm:^1.4.1": - version: 1.4.1 - resolution: "@sitecore/engage@npm:1.4.1" - checksum: 582b7a55ba407765def12518114790fb1735c359e60fe4594102a5e643539b148a494b02bf71970bcafc807b92fe19a1ab8ff7c2af3db2c080ab36ec2eb16d57 - languageName: node - linkType: hard - "@socket.io/component-emitter@npm:~3.1.0": version: 3.1.0 resolution: "@socket.io/component-emitter@npm:3.1.0"