create-sitecore-jss 22.3.0-canary.3 → 22.3.0-canary.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,8 +8,8 @@
8
8
  "prepare:proxy-build": "ts-node --project src/tsconfig.webpack-server.json ./scripts/proxy-build.ts"
9
9
  },
10
10
  "dependencies": {
11
- "@sitecore-cloudsdk/core": "^0.4.0-rc.0",
12
- "@sitecore-cloudsdk/events": "^0.4.0-rc.0",
11
+ "@sitecore-cloudsdk/core": "^0.4.0",
12
+ "@sitecore-cloudsdk/events": "^0.4.0",
13
13
  "font-awesome": "^4.7.0",
14
14
  "sass": "^1.52.3",
15
15
  "sass-alias": "^1.0.5"
@@ -5,12 +5,12 @@ import { layoutServiceFactory } from './src/app/lib/layout-service-factory';
5
5
  import { environment } from './src/environments/environment';
6
6
  import { components } from './src/app/components/app-components.module';
7
7
  import metadata from './src/environments/metadata.json';
8
-
9
8
  /**
10
9
  * Define the required configuration values to be exported from the server.bundle.ts.
11
10
  */
12
11
 
13
12
  const defaultLanguage = environment.defaultLanguage;
13
+ const sitecoreSiteName = environment.sitecoreSiteName;
14
14
  const getClientFactoryConfig = getGraphQLClientFactoryConfig;
15
15
 
16
16
  export {
@@ -19,6 +19,7 @@ export {
19
19
  dictionaryServiceFactory,
20
20
  layoutServiceFactory,
21
21
  defaultLanguage,
22
+ sitecoreSiteName,
22
23
  components,
23
24
  metadata,
24
25
  };
@@ -28,10 +28,9 @@ export const getGraphQLClientFactoryConfig = () => {
28
28
  : getEdgeProxyContentUrl(env.sitecoreEdgeContextId, env.proxyHost),
29
29
  };
30
30
  } else if (env.graphQLEndpoint && env.sitecoreApiKey) {
31
- const graphQLEndpointPath = new URL(env.graphQLEndpoint).pathname;
32
-
31
+ // we ignore ssr-proxy and query CM directly in case apiKey is used (i.e. in dev docker deployments)
33
32
  clientConfig = {
34
- endpoint: isServer ? env.graphQLEndpoint : `${env.proxyHost}${graphQLEndpointPath}`,
33
+ endpoint: env.graphQLEndpoint,
35
34
  apiKey: env.sitecoreApiKey,
36
35
  };
37
36
  }
@@ -12,3 +12,14 @@ PROXY_BUNDLE_PATH=
12
12
 
13
13
  # Set the DEBUG environment variable to 'sitecore-jss:*,sitecore-jss:proxy,http-proxy-middleware*' to see all logs:
14
14
  #DEBUG=sitecore-jss:*,http-proxy-middleware*
15
+
16
+ # An optional Sitecore Personalize scope identifier.
17
+ # This can be used to isolate personalization data when multiple XM Cloud Environments share a Personalize tenant.
18
+ # This should match the PAGES_PERSONALIZE_SCOPE environment variable for your connected XM Cloud Environment.
19
+ PERSONALIZE_SCOPE=
20
+
21
+ # Timeout (ms) for Sitecore CDP requests to respond within. Default is 400.
22
+ PERSONALIZE_MIDDLEWARE_CDP_TIMEOUT=
23
+
24
+ # Timeout (ms) for Sitecore Experience Edge requests to respond within. Default is 400.
25
+ PERSONALIZE_MIDDLEWARE_EDGE_TIMEOUT=
@@ -1,5 +1,5 @@
1
1
  import { Config, ServerBundle } from './types';
2
-
2
+ import { PersonalizeConfig } from '@sitecore-jss/sitecore-jss-proxy';
3
3
  /**
4
4
  * The server.bundle.js file from your pre-built SPA app.
5
5
  */
@@ -13,6 +13,39 @@ try {
13
13
  throw new Error(`ERROR: The server.bundle.js error. ${error}`);
14
14
  }
15
15
 
16
+ const clientFactoryConfig = serverBundle.getClientFactoryConfig();
17
+
18
+ /**
19
+ * GraphQL endpoint resolution to meet the requirements of the http-proxy-middleware
20
+ */
21
+ export const graphQLEndpoint = (() => {
22
+ try {
23
+ const graphQLEndpoint = new URL(clientFactoryConfig.endpoint);
24
+ // GraphQL endpoint URL (Edge endpoint for production and GraphQL Sitecore CM endpoint for dev)
25
+ const graphQLEndpointUrl = `${graphQLEndpoint.protocol}//${graphQLEndpoint.hostname}`;
26
+ // Sitecore Edge Context ID - will only be present for production
27
+ const sitecoreEdgeContextId = graphQLEndpoint.searchParams.get('sitecoreContextId');
28
+ // Browser request path to the proxy. Includes only the pathname.
29
+ const pathname = graphQLEndpoint.pathname;
30
+ // Target URL for the proxy. Can't include the query string.
31
+ const target = `${graphQLEndpointUrl}${pathname}`;
32
+
33
+ return {
34
+ target,
35
+ path: pathname,
36
+ graphQLEndpointUrl,
37
+ sitecoreEdgeContextId,
38
+ };
39
+ } catch (error) {
40
+ throw new Error(
41
+ `ERROR: The serverBundle should export a getClientFactoryConfig function with valid GraphQL endpoint URL returned, current value is ${clientFactoryConfig.endpoint}. ` +
42
+ 'Please check your server bundle.'
43
+ );
44
+ }
45
+ })();
46
+
47
+ const { clientFactory } = serverBundle;
48
+
16
49
  export const config: Config = {
17
50
  /**
18
51
  * The require'd server.bundle.js file from your pre-built SPA app.
@@ -23,3 +56,36 @@ export const config: Config = {
23
56
  */
24
57
  port: process.env.PROXY_PORT || 3000,
25
58
  };
59
+
60
+ export const personalizeConfig: PersonalizeConfig = {
61
+ // Configuration for your Sitecore Experience Edge endpoint
62
+ edgeConfig: {
63
+ clientFactory,
64
+ timeout:
65
+ (process.env.PERSONALIZE_MIDDLEWARE_EDGE_TIMEOUT &&
66
+ parseInt(process.env.PERSONALIZE_MIDDLEWARE_EDGE_TIMEOUT)) ||
67
+ 400,
68
+ },
69
+ // Configuration for your Sitecore CDP endpoint
70
+ // Edge URL and ID can be taken from proxy env, or the base SPA app
71
+ cdpConfig: {
72
+ sitecoreEdgeUrl: graphQLEndpoint.graphQLEndpointUrl,
73
+ sitecoreEdgeContextId: graphQLEndpoint.sitecoreEdgeContextId || '',
74
+ timeout:
75
+ (process.env.PERSONALIZE_MIDDLEWARE_CDP_TIMEOUT &&
76
+ parseInt(process.env.PERSONALIZE_MIDDLEWARE_CDP_TIMEOUT)) ||
77
+ 400,
78
+ },
79
+ // Optional Sitecore Personalize scope identifier.
80
+ scope: process.env.PERSONALIZE_SCOPE,
81
+ // This function determines if the personalization should be turned off.
82
+ // IMPORTANT: You should implement based on your cookie consent management solution of choice.
83
+ // You may wish to keep it disabled while in development mode.
84
+ // Personalization will also be disabled when edge context id is missing
85
+ disabled: () => process.env.NODE_ENV === 'development' || !graphQLEndpoint.sitecoreEdgeContextId,
86
+ // This function determines if a route should be excluded from personalization.
87
+ excludeRoute: () => false,
88
+ sitecoreSiteName: serverBundle.sitecoreSiteName || '',
89
+ // defaultLanguage will be used as fallback for personalization, if language cannot be read from layout service data
90
+ defaultLanguage: serverBundle.defaultLanguage,
91
+ };
@@ -1,11 +1,11 @@
1
1
  import 'dotenv/config';
2
2
  import express, { Response } from 'express';
3
3
  import compression from 'compression';
4
- import { createProxyMiddleware } from 'http-proxy-middleware';
4
+ import { createProxyMiddleware, fixRequestBody } from 'http-proxy-middleware';
5
5
  import { debug } from '@sitecore-jss/sitecore-jss';
6
- import { editingRouter } from '@sitecore-jss/sitecore-jss-proxy';
7
- import { healthCheck } from '@sitecore-jss/sitecore-jss-proxy';
8
- import { config } from './config';
6
+ import { editingRouter, healthCheck } from '@sitecore-jss/sitecore-jss-proxy';
7
+ import { config, graphQLEndpoint } from './config';
8
+ import { personalizeHelper, personalizePlugin } from './personalize';
9
9
 
10
10
  const server = express();
11
11
 
@@ -45,31 +45,6 @@ const layoutService = layoutServiceFactory.create();
45
45
 
46
46
  const dictionaryService = dictionaryServiceFactory.create();
47
47
 
48
- const clientFactoryConfig = config.serverBundle.getClientFactoryConfig();
49
-
50
- /**
51
- * GraphQL endpoint resolution to meet the requirements of the http-proxy-middleware
52
- */
53
- const graphQLEndpoint = (() => {
54
- try {
55
- const graphQLEndpoint = new URL(clientFactoryConfig.endpoint);
56
- // Browser request path to the proxy. Includes only the pathname.
57
- const pathname = graphQLEndpoint.pathname;
58
- // Target URL for the proxy. Can't include the query string.
59
- const target = `${graphQLEndpoint.protocol}//${graphQLEndpoint.hostname}${pathname}`;
60
-
61
- return {
62
- target,
63
- path: pathname,
64
- };
65
- } catch (error) {
66
- throw new Error(
67
- `ERROR: The serverBundle should export a getClientFactoryConfig function with valid GraphQL endpoint URL returned, current value is ${clientFactoryConfig.endpoint}. ` +
68
- 'Please check your server bundle.'
69
- );
70
- }
71
- })();
72
-
73
48
  /**
74
49
  * Parse requested url in order to detect current route and language
75
50
  * @param {string} reqRoute requested route
@@ -105,6 +80,8 @@ const handleError = (res: Response, err: unknown) => {
105
80
 
106
81
  // enable gzip compression for appropriate file types
107
82
  server.use(compression());
83
+ // enable access to req.body
84
+ server.use(graphQLEndpoint.path, express.json());
108
85
 
109
86
  // turn off x-powered-by http header
110
87
  server.settings['x-powered-by'] = false;
@@ -125,6 +102,12 @@ server.use(
125
102
  createProxyMiddleware({
126
103
  target: graphQLEndpoint.target,
127
104
  changeOrigin: true,
105
+ selfHandleResponse: true,
106
+ on: {
107
+ proxyReq: fixRequestBody,
108
+ },
109
+ // for client-side routing, personalization is performed by modifying layout service response
110
+ plugins: [personalizePlugin],
128
111
  })
129
112
  );
130
113
 
@@ -165,11 +148,17 @@ server.use(async (req, res) => {
165
148
  }
166
149
 
167
150
  // Language is required. In case it's not specified in the requested URL, fallback to the default language from the app configuration.
168
- const layoutData = await layoutService.fetchLayoutData(
151
+ let layoutData = await layoutService.fetchLayoutData(
169
152
  route,
170
153
  lang || config.serverBundle.defaultLanguage
171
154
  );
172
-
155
+ // for SSR loading routing, personalization is performed by modifying layoutData directly
156
+ const personalizedLayoutData = await personalizeHelper.personalizeLayoutData(
157
+ req,
158
+ res,
159
+ layoutData
160
+ );
161
+ layoutData = personalizedLayoutData;
173
162
  const viewBag = { dictionary: {} };
174
163
 
175
164
  viewBag.dictionary = await dictionaryService.fetchDictionaryData(
@@ -0,0 +1,34 @@
1
+ import { GRAPHQL_LAYOUT_QUERY_NAME, PersonalizeHelper } from '@sitecore-jss/sitecore-jss-proxy';
2
+ import { personalizeConfig } from './config';
3
+ import { responseInterceptor } from 'http-proxy-middleware';
4
+ import { Plugin } from 'http-proxy-middleware/dist/types';
5
+ import { IncomingMessageWithBody } from './types';
6
+
7
+ export const personalizeHelper = new PersonalizeHelper(personalizeConfig);
8
+
9
+ // personalize plugin to modify intercepted Layout Service request data
10
+ export const personalizePlugin: Plugin = (proxyServer) => {
11
+ proxyServer.on(
12
+ 'proxyRes',
13
+ responseInterceptor(async (responseBuffer, _, req, res) => {
14
+ let responseText = responseBuffer.toString('utf8');
15
+ const payload = JSON.stringify((req as IncomingMessageWithBody).body);
16
+
17
+ // only apply personalization onto JSS layout service results
18
+ if (payload.includes(GRAPHQL_LAYOUT_QUERY_NAME)) {
19
+ let layoutDataRaw = JSON.parse(responseText);
20
+ if (!layoutDataRaw?.data?.layout?.item?.rendered?.sitecore) {
21
+ return responseText;
22
+ }
23
+ const personalizedLayout = await personalizeHelper.personalizeLayoutData(
24
+ req as IncomingMessageWithBody,
25
+ res,
26
+ layoutDataRaw?.data?.layout?.item?.rendered
27
+ );
28
+ layoutDataRaw.data.layout.item.rendered = personalizedLayout;
29
+ responseText = JSON.stringify(layoutDataRaw);
30
+ }
31
+ return responseText;
32
+ })
33
+ );
34
+ };
@@ -1,3 +1,4 @@
1
+ /* eslint-disable no-unused-vars */
1
2
  import {
2
3
  GraphQLRequestClientFactory,
3
4
  GraphQLRequestClientFactoryConfig,
@@ -6,6 +7,7 @@ import { DictionaryService } from '@sitecore-jss/sitecore-jss/i18n';
6
7
  import { Metadata } from '@sitecore-jss/sitecore-jss/utils';
7
8
  import { LayoutService } from '@sitecore-jss/sitecore-jss/layout';
8
9
  import { AppRenderer, RouteUrlParser } from '@sitecore-jss/sitecore-jss-proxy';
10
+ import { IncomingMessage } from 'http';
9
11
 
10
12
  export interface ServerBundle {
11
13
  [key: string]: unknown;
@@ -14,6 +16,7 @@ export interface ServerBundle {
14
16
  clientFactory: GraphQLRequestClientFactory;
15
17
  getClientFactoryConfig: () => GraphQLRequestClientFactoryConfig;
16
18
  defaultLanguage: string;
19
+ sitecoreSiteName: string;
17
20
  layoutServiceFactory: { create: () => LayoutService };
18
21
  dictionaryServiceFactory: { create: () => DictionaryService };
19
22
  components: string[] | Map<string, unknown>;
@@ -25,3 +28,10 @@ export interface Config {
25
28
  port: string | number;
26
29
  serverBundle: ServerBundle;
27
30
  }
31
+
32
+ /**
33
+ * IncomingMessage type modified with exporess.json() call to include request body
34
+ */
35
+ export type IncomingMessageWithBody = IncomingMessage & {
36
+ body: ReadableStream<Uint8Array> | null;
37
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-sitecore-jss",
3
- "version": "22.3.0-canary.3",
3
+ "version": "22.3.0-canary.4",
4
4
  "description": "Sitecore JSS initializer",
5
5
  "bin": "./dist/index.js",
6
6
  "scripts": {
@@ -63,5 +63,5 @@
63
63
  "ts-node": "^10.9.1",
64
64
  "typescript": "~4.9.5"
65
65
  },
66
- "gitHead": "3de00d81185a1676b639f8f4b955268a62a44ac6"
66
+ "gitHead": "41544c7c448dc62a8327fe3d3fe3937b61252226"
67
67
  }