@sitecore-jss/sitecore-jss-nextjs 22.1.0-canary.9 → 22.2.0-canary.10

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.
Files changed (50) hide show
  1. package/dist/cjs/components/Link.js +7 -3
  2. package/dist/cjs/components/NextImage.js +10 -5
  3. package/dist/cjs/components/RichText.js +2 -2
  4. package/dist/cjs/editing/constants.js +12 -3
  5. package/dist/cjs/editing/editing-config-middleware.js +8 -0
  6. package/dist/cjs/editing/editing-data-middleware.js +6 -0
  7. package/dist/cjs/editing/editing-render-middleware.js +229 -103
  8. package/dist/cjs/editing/feaas-render-middleware.js +8 -0
  9. package/dist/cjs/editing/index.js +4 -1
  10. package/dist/cjs/editing/render-middleware.js +18 -4
  11. package/dist/cjs/index.js +9 -7
  12. package/dist/cjs/middleware/middleware.js +12 -0
  13. package/dist/cjs/middleware/personalize-middleware.js +85 -25
  14. package/dist/cjs/services/base-graphql-sitemap-service.js +5 -4
  15. package/dist/cjs/utils/index.js +4 -3
  16. package/dist/cjs/utils/utils.js +3 -3
  17. package/dist/esm/components/Link.js +7 -3
  18. package/dist/esm/components/NextImage.js +11 -5
  19. package/dist/esm/components/RichText.js +2 -2
  20. package/dist/esm/editing/constants.js +11 -2
  21. package/dist/esm/editing/editing-config-middleware.js +9 -1
  22. package/dist/esm/editing/editing-data-middleware.js +7 -1
  23. package/dist/esm/editing/editing-render-middleware.js +227 -103
  24. package/dist/esm/editing/feaas-render-middleware.js +9 -1
  25. package/dist/esm/editing/index.js +2 -1
  26. package/dist/esm/editing/render-middleware.js +19 -5
  27. package/dist/esm/index.js +3 -4
  28. package/dist/esm/middleware/middleware.js +12 -0
  29. package/dist/esm/middleware/personalize-middleware.js +86 -26
  30. package/dist/esm/services/base-graphql-sitemap-service.js +5 -4
  31. package/dist/esm/utils/index.js +2 -1
  32. package/dist/esm/utils/utils.js +1 -1
  33. package/package.json +10 -11
  34. package/types/ComponentBuilder.d.ts +3 -5
  35. package/types/components/Placeholder.d.ts +7 -2
  36. package/types/components/RichText.d.ts +6 -0
  37. package/types/editing/constants.d.ts +11 -2
  38. package/types/editing/editing-config-middleware.d.ts +7 -0
  39. package/types/editing/editing-data-service.d.ts +1 -0
  40. package/types/editing/editing-render-middleware.d.ts +111 -23
  41. package/types/editing/index.d.ts +2 -1
  42. package/types/editing/render-middleware.d.ts +9 -0
  43. package/types/index.d.ts +3 -4
  44. package/types/middleware/middleware.d.ts +6 -0
  45. package/types/middleware/personalize-middleware.d.ts +22 -2
  46. package/types/services/base-graphql-sitemap-service.d.ts +3 -2
  47. package/types/utils/index.d.ts +2 -1
  48. package/dist/cjs/components/EditingComponentPlaceholder.js +0 -12
  49. package/dist/esm/components/EditingComponentPlaceholder.js +0 -5
  50. package/types/components/EditingComponentPlaceholder.d.ts +0 -4
@@ -9,55 +9,63 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import { STATIC_PROPS_ID, SERVER_PROPS_ID } from 'next/constants';
11
11
  import { AxiosDataFetcher, debug } from '@sitecore-jss/sitecore-jss';
12
- import { EDITING_COMPONENT_ID, RenderingType } from '@sitecore-jss/sitecore-jss/layout';
13
- import { parse } from 'node-html-parser';
12
+ import { EditMode } from '@sitecore-jss/sitecore-jss/layout';
14
13
  import { editingDataService } from './editing-data-service';
15
- import { QUERY_PARAM_EDITING_SECRET } from './constants';
14
+ import { EDITING_ALLOWED_ORIGINS, QUERY_PARAM_EDITING_SECRET } from './constants';
16
15
  import { getJssEditingSecret } from '../utils/utils';
17
16
  import { RenderMiddlewareBase } from './render-middleware';
17
+ import { enforceCors, getAllowedOriginsFromEnv } from '@sitecore-jss/sitecore-jss/utils';
18
+ import { DEFAULT_VARIANT } from '@sitecore-jss/sitecore-jss/personalize';
18
19
  /**
19
- * Middleware / handler for use in the editing render Next.js API route (e.g. '/api/editing/render')
20
- * which is required for Sitecore editing support.
20
+ * Handler for the Editing Chromes POST requests.
21
+ * This handler is responsible for rendering the page and returning the HTML content that is provided via request.
21
22
  */
22
- export class EditingRenderMiddleware extends RenderMiddlewareBase {
23
- /**
24
- * @param {EditingRenderMiddlewareConfig} [config] Editing render middleware config
25
- */
23
+ export class ChromesHandler extends RenderMiddlewareBase {
26
24
  constructor(config) {
27
25
  var _a, _b, _c, _d;
28
26
  super();
29
- this.handler = (req, res) => __awaiter(this, void 0, void 0, function* () {
30
- var _e, _f;
31
- const { method, query, body, headers } = req;
27
+ this.config = config;
28
+ /**
29
+ * Default page URL resolution.
30
+ * @param {Object} args Arguments for resolving the page URL
31
+ * @param {string} args.serverUrl The root server URL e.g. 'http://localhost:3000'
32
+ * @param {string} args.itemPath The Sitecore relative item path e.g. '/styleguide'
33
+ * @returns {string} The URL to render
34
+ */
35
+ this.defaultResolvePageUrl = ({ serverUrl, itemPath, }) => {
36
+ return `${serverUrl}${itemPath}`;
37
+ };
38
+ /**
39
+ * Default server URL resolution.
40
+ * Note we use https protocol on Vercel due to serverless function architecture.
41
+ * In all other scenarios, including localhost (with or without a proxy e.g. ngrok)
42
+ * and within a nodejs container, http protocol should be used.
43
+ *
44
+ * For information about the VERCEL environment variable, see
45
+ * https://vercel.com/docs/environment-variables#system-environment-variables
46
+ * @param {NextApiRequest} req
47
+ */
48
+ this.defaultResolveServerUrl = (req) => {
49
+ return `${process.env.VERCEL ? 'https' : 'http'}://${req.headers.host}`;
50
+ };
51
+ this.editingDataService = (_a = config === null || config === void 0 ? void 0 : config.editingDataService) !== null && _a !== void 0 ? _a : editingDataService;
52
+ this.dataFetcher = (_b = config === null || config === void 0 ? void 0 : config.dataFetcher) !== null && _b !== void 0 ? _b : new AxiosDataFetcher({ debugger: debug.editing });
53
+ this.resolvePageUrl = (_c = config === null || config === void 0 ? void 0 : config.resolvePageUrl) !== null && _c !== void 0 ? _c : this.defaultResolvePageUrl;
54
+ this.resolveServerUrl = (_d = config === null || config === void 0 ? void 0 : config.resolveServerUrl) !== null && _d !== void 0 ? _d : this.defaultResolveServerUrl;
55
+ }
56
+ render(req, res) {
57
+ return __awaiter(this, void 0, void 0, function* () {
58
+ const { query, headers: incomingHeaders } = req;
32
59
  const startTimestamp = Date.now();
33
- debug.editing('editing render middleware start: %o', {
34
- method,
35
- query,
36
- headers,
37
- body,
38
- });
39
- if (method !== 'POST') {
40
- debug.editing('invalid method - sent %s expected POST', method);
41
- res.setHeader('Allow', 'POST');
42
- return res.status(405).json({
43
- html: `<html><body>Invalid request method '${method}'</body></html>`,
44
- });
45
- }
46
- // Validate secret
47
- const secret = (_e = query[QUERY_PARAM_EDITING_SECRET]) !== null && _e !== void 0 ? _e : body === null || body === void 0 ? void 0 : body.jssEditingSecret;
48
- if (secret !== getJssEditingSecret()) {
49
- debug.editing('invalid editing secret - sent "%s" expected "%s"', secret, getJssEditingSecret());
50
- return res.status(401).json({
51
- html: '<html><body>Missing or invalid secret</body></html>',
52
- });
53
- }
54
60
  try {
55
61
  // Extract data from EE payload
56
- const editingData = extractEditingData(req);
62
+ const editingData = this.extractEditingData(req);
57
63
  // Resolve server URL
58
64
  const serverUrl = this.resolveServerUrl(req);
59
65
  // Get query string parameters to propagate on subsequent requests (e.g. for deployment protection bypass)
60
66
  const params = this.getQueryParamsForPropagation(query);
67
+ // Get headers to propagate on subsequent requests
68
+ const headers = this.getHeadersForPropagation(incomingHeaders);
61
69
  // Stash for use later on (i.e. within getStatic/ServerSideProps).
62
70
  // Note we can't set this directly in setPreviewData since it's stored as a cookie (2KB limit)
63
71
  // https://nextjs.org/docs/advanced-features/preview-mode#previewdata-size-limits)
@@ -66,10 +74,11 @@ export class EditingRenderMiddleware extends RenderMiddlewareBase {
66
74
  res.setPreviewData(previewData);
67
75
  // Grab the Next.js preview cookies to send on to the render request
68
76
  const cookies = res.getHeader('Set-Cookie');
77
+ headers.cookie = `${headers.cookie ? headers.cookie + ';' : ''}${cookies.join(';')}`;
69
78
  // Make actual render request for page route, passing on preview cookies as well as any approved query string parameters.
70
79
  // Note timestamp effectively disables caching the request in Axios (no amount of cache headers seemed to do it)
71
80
  debug.editing('fetching page route for %s', editingData.path);
72
- const requestUrl = new URL(this.resolvePageUrl(serverUrl, editingData.path));
81
+ const requestUrl = new URL(this.resolvePageUrl({ serverUrl, itemPath: editingData.path }));
73
82
  for (const key in params) {
74
83
  if ({}.hasOwnProperty.call(params, key)) {
75
84
  requestUrl.searchParams.append(key, params[key]);
@@ -78,9 +87,7 @@ export class EditingRenderMiddleware extends RenderMiddlewareBase {
78
87
  requestUrl.searchParams.append('timestamp', Date.now().toString());
79
88
  const pageRes = yield this.dataFetcher
80
89
  .get(requestUrl.toString(), {
81
- headers: {
82
- Cookie: cookies.join(';'),
83
- },
90
+ headers,
84
91
  })
85
92
  .catch((err) => {
86
93
  // We need to handle not found error provided by Vercel
@@ -104,12 +111,6 @@ export class EditingRenderMiddleware extends RenderMiddlewareBase {
104
111
  // certain route configurations (e.g. multiple catch-all routes).
105
112
  // The following line will trick it into thinking we're SSR, thus avoiding any router.replace.
106
113
  html = html.replace(STATIC_PROPS_ID, SERVER_PROPS_ID);
107
- if (editingData.layoutData.sitecore.context.renderingType === RenderingType.Component) {
108
- // Handle component rendering. Extract component markup only
109
- html = (_f = parse(html).getElementById(EDITING_COMPONENT_ID)) === null || _f === void 0 ? void 0 : _f.innerHTML;
110
- if (!html)
111
- throw new Error(`Failed to render component for ${editingData.path}`);
112
- }
113
114
  const body = { html };
114
115
  // Return expected JSON result
115
116
  debug.editing('editing render middleware end in %dms: %o', Date.now() - startTimestamp, {
@@ -132,72 +133,195 @@ export class EditingRenderMiddleware extends RenderMiddlewareBase {
132
133
  });
133
134
  }
134
135
  });
135
- /**
136
- * Default page URL resolution.
137
- * @param {string} serverUrl
138
- * @param {string} itemPath
139
- */
140
- this.defaultResolvePageUrl = (serverUrl, itemPath) => {
141
- return `${serverUrl}${itemPath}`;
142
- };
143
- /**
144
- * Default server URL resolution.
145
- * Note we use https protocol on Vercel due to serverless function architecture.
146
- * In all other scenarios, including localhost (with or without a proxy e.g. ngrok)
147
- * and within a nodejs container, http protocol should be used.
148
- *
149
- * For information about the VERCEL environment variable, see
150
- * https://vercel.com/docs/environment-variables#system-environment-variables
151
- * @param {NextApiRequest} req
152
- */
153
- this.defaultResolveServerUrl = (req) => {
154
- return `${process.env.VERCEL ? 'https' : 'http'}://${req.headers.host}`;
136
+ }
137
+ extractEditingData(req) {
138
+ // Sitecore editors will send the following body data structure,
139
+ // though we're only concerned with the "args".
140
+ // {
141
+ // id: 'JSS app name',
142
+ // args: ['path', 'serialized layout data object', 'serialized viewbag object'],
143
+ // functionName: 'renderView',
144
+ // moduleName: 'server.bundle'
145
+ // }
146
+ // The 'serialized viewbag object' structure:
147
+ // {
148
+ // language: 'language',
149
+ // dictionary: 'key-value representation of tokens and their corresponding translations',
150
+ // httpContext: 'serialized request data'
151
+ // }
152
+ var _a;
153
+ // Note req.body _should_ have already been parsed as JSON at this point (via Next.js `bodyParser` API middleware)
154
+ const payload = req.body;
155
+ if (!payload || !payload.args || !Array.isArray(payload.args) || payload.args.length < 3) {
156
+ throw new Error('Unable to extract editing data from request. Ensure `bodyParser` middleware is enabled on your Next.js API route.');
157
+ }
158
+ const layoutData = JSON.parse(payload.args[1]);
159
+ const viewBag = JSON.parse(payload.args[2]);
160
+ // Keep backwards compatibility in case people use an older JSS version that doesn't send the path in the context
161
+ const path = (_a = layoutData.sitecore.context.itemPath) !== null && _a !== void 0 ? _a : viewBag.httpContext.request.path;
162
+ return {
163
+ path,
164
+ layoutData,
165
+ language: viewBag.language,
166
+ dictionary: viewBag.dictionary,
155
167
  };
156
- this.editingDataService = (_a = config === null || config === void 0 ? void 0 : config.editingDataService) !== null && _a !== void 0 ? _a : editingDataService;
157
- this.dataFetcher = (_b = config === null || config === void 0 ? void 0 : config.dataFetcher) !== null && _b !== void 0 ? _b : new AxiosDataFetcher({ debugger: debug.editing });
158
- this.resolvePageUrl = (_c = config === null || config === void 0 ? void 0 : config.resolvePageUrl) !== null && _c !== void 0 ? _c : this.defaultResolvePageUrl;
159
- this.resolveServerUrl = (_d = config === null || config === void 0 ? void 0 : config.resolveServerUrl) !== null && _d !== void 0 ? _d : this.defaultResolveServerUrl;
168
+ }
169
+ }
170
+ /**
171
+ * Type guard for EditingMetadataPreviewData
172
+ * @param {Object} data preview data to check
173
+ * @returns true if the data is EditingMetadataPreviewData
174
+ * @see EditingMetadataPreviewData
175
+ */
176
+ export const isEditingMetadataPreviewData = (data) => {
177
+ return (typeof data === 'object' &&
178
+ data !== null &&
179
+ 'editMode' in data &&
180
+ data.editMode === EditMode.Metadata);
181
+ };
182
+ /**
183
+ * Handler for the Editing Metadata GET requests.
184
+ * This handler is responsible for redirecting the request to the page route.
185
+ * The page fetches the layout, dictionary and renders the page.
186
+ */
187
+ export class MetadataHandler {
188
+ constructor(config) {
189
+ this.config = config;
190
+ }
191
+ render(req, res) {
192
+ var _a, _b, _c;
193
+ const { query } = req;
194
+ const startTimestamp = Date.now();
195
+ const requiredQueryParams = [
196
+ 'sc_site',
197
+ 'sc_itemid',
198
+ 'sc_lang',
199
+ 'route',
200
+ 'mode',
201
+ ];
202
+ const missingQueryParams = requiredQueryParams.filter((param) => !query[param]);
203
+ // Validate query parameters
204
+ if (missingQueryParams.length) {
205
+ debug.editing('missing required query parameters: %o', missingQueryParams);
206
+ return res.status(400).json({
207
+ html: `<html><body>Missing required query parameters: ${missingQueryParams.join(', ')}</body></html>`,
208
+ });
209
+ }
210
+ res.setPreviewData({
211
+ site: query.sc_site,
212
+ itemId: query.sc_itemid,
213
+ language: query.sc_lang,
214
+ // for sc_variantId we may employ multiple variants (page-layout + component level)
215
+ variantIds: ((_a = query.sc_variant) === null || _a === void 0 ? void 0 : _a.split(',')) || [DEFAULT_VARIANT],
216
+ version: query.sc_version,
217
+ editMode: EditMode.Metadata,
218
+ pageState: query.mode,
219
+ },
220
+ // Cache the preview data for 3 seconds to ensure the page is rendered with the correct preview data not the cached one
221
+ {
222
+ path: query.route,
223
+ maxAge: 3,
224
+ });
225
+ // Cookies with the SameSite=Lax policy set by Next.js setPreviewData function causes CORS issue
226
+ // when Next.js preview mode is activated, resulting the page to render in normal mode instead.
227
+ // By replacing it with "SameSite=None; Secure", we ensure cookies are correctly sent with
228
+ // cross-origin requests, allowing the page to be editable. This change should be reverted
229
+ // once vercel addresses this open issue: https://github.com/vercel/next.js/issues/49927
230
+ const setCookieHeader = res.getHeader('Set-Cookie');
231
+ if (setCookieHeader && Array.isArray(setCookieHeader)) {
232
+ const modifiedCookies = setCookieHeader.map((cookie) => {
233
+ const cookieIdentifiers = {
234
+ __prerender_bypass: /^__prerender_bypass=/,
235
+ __next_preview_data: /^__next_preview_data=/,
236
+ };
237
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
238
+ for (const [_, regex] of Object.entries(cookieIdentifiers)) {
239
+ if (cookie.match(regex)) {
240
+ return cookie.replace(/SameSite=Lax/, 'SameSite=None; Secure');
241
+ }
242
+ }
243
+ return cookie;
244
+ });
245
+ res.setHeader('Set-Cookie', modifiedCookies);
246
+ }
247
+ const route = ((_c = (_b = this.config).resolvePageUrl) === null || _c === void 0 ? void 0 : _c.call(_b, {
248
+ itemPath: query.route,
249
+ })) || query.route;
250
+ debug.editing('editing render middleware end in %dms: redirect %o', Date.now() - startTimestamp, {
251
+ status: 307,
252
+ route,
253
+ });
254
+ // Restrict the page to be rendered only within the allowed origins
255
+ res.setHeader('Content-Security-Policy', this.getSCPHeader());
256
+ res.redirect(route);
160
257
  }
161
258
  /**
162
- * Gets the Next.js API route handler
163
- * @returns route handler
259
+ * Gets the Content-Security-Policy header value
260
+ * @returns Content-Security-Policy header value
164
261
  */
165
- getHandler() {
166
- return this.handler;
262
+ getSCPHeader() {
263
+ return `frame-ancestors 'self' ${[getAllowedOriginsFromEnv(), ...EDITING_ALLOWED_ORIGINS].join(' ')}`;
167
264
  }
168
265
  }
169
266
  /**
170
- * @param {NextApiRequest} req
267
+ * Middleware / handler for use in the editing render Next.js API route (e.g. '/api/editing/render')
268
+ * which is required for Sitecore editing support.
171
269
  */
172
- export function extractEditingData(req) {
173
- // Sitecore editors will send the following body data structure,
174
- // though we're only concerned with the "args".
175
- // {
176
- // id: 'JSS app name',
177
- // args: ['path', 'serialized layout data object', 'serialized viewbag object'],
178
- // functionName: 'renderView',
179
- // moduleName: 'server.bundle'
180
- // }
181
- // The 'serialized viewbag object' structure:
182
- // {
183
- // language: 'language',
184
- // dictionary: 'key-value representation of tokens and their corresponding translations',
185
- // httpContext: 'serialized request data'
186
- // }
187
- var _a;
188
- // Note req.body _should_ have already been parsed as JSON at this point (via Next.js `bodyParser` API middleware)
189
- const payload = req.body;
190
- if (!payload || !payload.args || !Array.isArray(payload.args) || payload.args.length < 3) {
191
- throw new Error('Unable to extract editing data from request. Ensure `bodyParser` middleware is enabled on your Next.js API route.');
270
+ export class EditingRenderMiddleware extends RenderMiddlewareBase {
271
+ /**
272
+ * @param {EditingRenderMiddlewareConfig} [config] Editing render middleware config
273
+ */
274
+ constructor(config) {
275
+ super();
276
+ this.config = config;
277
+ this.handler = (req, res) => __awaiter(this, void 0, void 0, function* () {
278
+ var _a, _b, _c;
279
+ const { query, body, method, headers } = req;
280
+ debug.editing('editing render middleware start: %o', {
281
+ method,
282
+ query,
283
+ headers,
284
+ body,
285
+ });
286
+ if (!enforceCors(req, res, EDITING_ALLOWED_ORIGINS)) {
287
+ debug.editing('invalid origin host - set allowed origins in JSS_ALLOWED_ORIGINS environment variable');
288
+ return res.status(401).json({
289
+ html: `<html><body>Requests from origin ${(_a = req.headers) === null || _a === void 0 ? void 0 : _a.origin} not allowed</body></html>`,
290
+ });
291
+ }
292
+ // Validate secret
293
+ const secret = (_b = query[QUERY_PARAM_EDITING_SECRET]) !== null && _b !== void 0 ? _b : body === null || body === void 0 ? void 0 : body.jssEditingSecret;
294
+ if (secret !== getJssEditingSecret()) {
295
+ debug.editing('invalid editing secret - sent "%s" expected "%s"', secret, getJssEditingSecret());
296
+ return res.status(401).json({
297
+ html: '<html><body>Missing or invalid secret</body></html>',
298
+ });
299
+ }
300
+ switch (req.method) {
301
+ case 'GET': {
302
+ const handler = new MetadataHandler({ resolvePageUrl: (_c = this.config) === null || _c === void 0 ? void 0 : _c.resolvePageUrl });
303
+ yield handler.render(req, res);
304
+ break;
305
+ }
306
+ case 'POST': {
307
+ const handler = new ChromesHandler(this.config);
308
+ yield handler.render(req, res);
309
+ break;
310
+ }
311
+ default:
312
+ debug.editing('invalid method - sent %s expected GET/POST', req.method);
313
+ res.setHeader('Allow', 'GET, POST');
314
+ return res.status(405).json({
315
+ html: `<html><body>Invalid request method '${req.method}'</body></html>`,
316
+ });
317
+ }
318
+ });
319
+ }
320
+ /**
321
+ * Gets the Next.js API route handler
322
+ * @returns route handler
323
+ */
324
+ getHandler() {
325
+ return this.handler;
192
326
  }
193
- const layoutData = JSON.parse(payload.args[1]);
194
- const viewBag = JSON.parse(payload.args[2]);
195
- // Keep backwards compatibility in case people use an older JSS version that doesn't send the path in the context
196
- const path = (_a = layoutData.sitecore.context.itemPath) !== null && _a !== void 0 ? _a : viewBag.httpContext.request.path;
197
- return {
198
- path,
199
- layoutData,
200
- language: viewBag.language,
201
- dictionary: viewBag.dictionary,
202
- };
203
327
  }
@@ -8,9 +8,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { debug } from '@sitecore-jss/sitecore-jss';
11
- import { QUERY_PARAM_EDITING_SECRET } from './constants';
11
+ import { EDITING_ALLOWED_ORIGINS, QUERY_PARAM_EDITING_SECRET } from './constants';
12
12
  import { getJssEditingSecret } from '../utils/utils';
13
13
  import { RenderMiddlewareBase } from './render-middleware';
14
+ import { enforceCors } from '@sitecore-jss/sitecore-jss/utils';
14
15
  /**
15
16
  * Middleware / handler for use in the feaas render Next.js API route (e.g. '/api/editing/feaas/render')
16
17
  * which is required for Sitecore editing support.
@@ -25,6 +26,7 @@ export class FEAASRenderMiddleware extends RenderMiddlewareBase {
25
26
  this.config = config;
26
27
  this.defaultPageUrl = '/feaas/render';
27
28
  this.handler = (req, res) => __awaiter(this, void 0, void 0, function* () {
29
+ var _b;
28
30
  const { method, query, headers } = req;
29
31
  const startTimestamp = Date.now();
30
32
  debug.editing('feaas render middleware start: %o', {
@@ -32,6 +34,12 @@ export class FEAASRenderMiddleware extends RenderMiddlewareBase {
32
34
  query,
33
35
  headers,
34
36
  });
37
+ if (!enforceCors(req, res, EDITING_ALLOWED_ORIGINS)) {
38
+ debug.editing('invalid origin host - set allowed origins in JSS_ALLOWED_ORIGINS environment variable');
39
+ return res
40
+ .status(401)
41
+ .send(`<html><body>Requests from origin ${(_b = req.headers) === null || _b === void 0 ? void 0 : _b.origin} are not allowed</body></html>`);
42
+ }
35
43
  if (method !== 'GET') {
36
44
  debug.editing('invalid method - sent %s expected GET', method);
37
45
  res.setHeader('Allow', 'GET');
@@ -1,6 +1,7 @@
1
+ export { GraphQLEditingService } from '@sitecore-jss/sitecore-jss/editing';
1
2
  export { EditingDataDiskCache } from './editing-data-cache';
2
3
  export { EditingDataMiddleware } from './editing-data-middleware';
3
- export { EditingRenderMiddleware, } from './editing-render-middleware';
4
+ export { EditingRenderMiddleware, isEditingMetadataPreviewData, } from './editing-render-middleware';
4
5
  export { BasicEditingDataService, ServerlessEditingDataService, editingDataService, } from './editing-data-service';
5
6
  export { VercelEditingDataCache } from './vercel-editing-data-cache';
6
7
  export { FEAASRenderMiddleware } from './feaas-render-middleware';
@@ -1,4 +1,4 @@
1
- import { QUERY_PARAM_PROTECTION_BYPASS_SITECORE, QUERY_PARAM_PROTECTION_BYPASS_VERCEL, } from './constants';
1
+ import { QUERY_PARAM_VERCEL_PROTECTION_BYPASS, QUERY_PARAM_VERCEL_SET_BYPASS_COOKIE, EDITING_PASS_THROUGH_HEADERS, } from './constants';
2
2
  /**
3
3
  * Base class for middleware that handles pages and components rendering in Sitecore Editors.
4
4
  */
@@ -11,13 +11,27 @@ export class RenderMiddlewareBase {
11
11
  */
12
12
  this.getQueryParamsForPropagation = (query) => {
13
13
  const params = {};
14
- if (query[QUERY_PARAM_PROTECTION_BYPASS_SITECORE]) {
15
- params[QUERY_PARAM_PROTECTION_BYPASS_SITECORE] = query[QUERY_PARAM_PROTECTION_BYPASS_SITECORE];
14
+ if (query[QUERY_PARAM_VERCEL_PROTECTION_BYPASS]) {
15
+ params[QUERY_PARAM_VERCEL_PROTECTION_BYPASS] = query[QUERY_PARAM_VERCEL_PROTECTION_BYPASS];
16
16
  }
17
- if (query[QUERY_PARAM_PROTECTION_BYPASS_VERCEL]) {
18
- params[QUERY_PARAM_PROTECTION_BYPASS_VERCEL] = query[QUERY_PARAM_PROTECTION_BYPASS_VERCEL];
17
+ if (query[QUERY_PARAM_VERCEL_SET_BYPASS_COOKIE]) {
18
+ params[QUERY_PARAM_VERCEL_SET_BYPASS_COOKIE] = query[QUERY_PARAM_VERCEL_SET_BYPASS_COOKIE];
19
19
  }
20
20
  return params;
21
21
  };
22
+ /**
23
+ * Get headers that should be passed along to subsequent requests
24
+ * @param {IncomingHttpHeaders} headers Incoming HTTP Headers
25
+ * @returns Object of approved headers
26
+ */
27
+ this.getHeadersForPropagation = (headers) => {
28
+ const result = {};
29
+ EDITING_PASS_THROUGH_HEADERS.forEach((header) => {
30
+ if (headers[header]) {
31
+ result[header] = headers[header];
32
+ }
33
+ });
34
+ return result;
35
+ };
22
36
  }
23
37
  }
package/dist/esm/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  export { constants, AxiosDataFetcher, NativeDataFetcher, enableDebug, debug, } from '@sitecore-jss/sitecore-jss';
2
- export { LayoutServicePageState, GraphQLLayoutService, RestLayoutService, getChildPlaceholder, getFieldValue, RenderingType, EDITING_COMPONENT_PLACEHOLDER, EDITING_COMPONENT_ID, getContentStylesheetLink, } from '@sitecore-jss/sitecore-jss/layout';
2
+ export { LayoutServicePageState, GraphQLLayoutService, RestLayoutService, getChildPlaceholder, getFieldValue, getContentStylesheetLink, EditMode, } from '@sitecore-jss/sitecore-jss/layout';
3
3
  export { mediaApi } from '@sitecore-jss/sitecore-jss/media';
4
4
  export { trackingApi, } from '@sitecore-jss/sitecore-jss/tracking';
5
5
  export { GraphQLDictionaryService, RestDictionaryService, } from '@sitecore-jss/sitecore-jss/i18n';
6
- export { personalizeLayout, getPersonalizedRewrite, getPersonalizedRewriteData, normalizePersonalizedRewrite, CdpHelper, } from '@sitecore-jss/sitecore-jss/personalize';
6
+ export { personalizeLayout, getPersonalizedRewrite, getPersonalizedRewriteData, getGroomedVariantIds, normalizePersonalizedRewrite, CdpHelper, } from '@sitecore-jss/sitecore-jss/personalize';
7
7
  export { ComponentPropsService } from './services/component-props-service';
8
8
  export { DisconnectedSitemapService } from './services/disconnected-sitemap-service';
9
9
  export { GraphQLSitemapService, } from './services/graphql-sitemap-service';
@@ -13,7 +13,6 @@ export { ComponentPropsReactContext, ComponentPropsContext, useComponentProps, }
13
13
  export { Link } from './components/Link';
14
14
  export { RichText } from './components/RichText';
15
15
  export { Placeholder } from './components/Placeholder';
16
- export { EditingComponentPlaceholder } from './components/EditingComponentPlaceholder';
17
16
  export { NextImage } from './components/NextImage';
18
17
  import * as FEaaSWrapper from './components/FEaaSWrapper';
19
18
  import * as BYOCWrapper from './components/BYOCWrapper';
@@ -21,4 +20,4 @@ export { FEaaSWrapper };
21
20
  export { BYOCWrapper };
22
21
  export { ComponentBuilder } from './ComponentBuilder';
23
22
  export { Context } from './context';
24
- export { Image, Text, DateField, EditFrame, FEaaSComponent, fetchFEaaSComponentServerProps, BYOCComponent, getComponentLibraryStylesheetLinks, File, VisitorIdentification, SitecoreContext, SitecoreContextReactContext, withSitecoreContext, useSitecoreContext, withEditorChromes, withPlaceholder, withDatasourceCheck, } from '@sitecore-jss/sitecore-jss-react';
23
+ export { Image, Text, DateField, EditFrame, FEaaSComponent, fetchFEaaSComponentServerProps, BYOCComponent, getComponentLibraryStylesheetLinks, File, DefaultEmptyFieldEditingComponentImage, DefaultEmptyFieldEditingComponentText, VisitorIdentification, SitecoreContext, SitecoreContextReactContext, withSitecoreContext, useSitecoreContext, withEditorChromes, withPlaceholder, withDatasourceCheck, withFieldMetadata, withEmptyFieldEditingComponent, EditingScripts, } from '@sitecore-jss/sitecore-jss-react';
@@ -15,6 +15,18 @@ export class MiddlewareBase {
15
15
  var _a, _b;
16
16
  return !!(((_a = req.cookies.get('__prerender_bypass')) === null || _a === void 0 ? void 0 : _a.value) || ((_b = req.cookies.get('__next_preview_data')) === null || _b === void 0 ? void 0 : _b.value));
17
17
  }
18
+ /**
19
+ * Determines if the request is a Next.js (next/link) prefetch request
20
+ * @param {NextRequest} req request
21
+ * @returns {boolean} is prefetch
22
+ */
23
+ isPrefetch(req) {
24
+ return (
25
+ // eslint-disable-next-line prettier/prettier
26
+ req.headers.get('purpose') === 'prefetch' || // Pages Router
27
+ req.headers.get('Next-Router-Prefetch') === '1' // App Router
28
+ );
29
+ }
18
30
  excludeRoute(pathname) {
19
31
  var _a, _b;
20
32
  return (pathname.startsWith('/api/') || // Ignore Next.js API calls