@sitecore-jss/sitecore-jss-nextjs 22.1.0-canary.19 → 22.1.0-canary.22

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.
@@ -4,4 +4,7 @@ exports.EDITING_ALLOWED_ORIGINS = exports.QUERY_PARAM_PROTECTION_BYPASS_VERCEL =
4
4
  exports.QUERY_PARAM_EDITING_SECRET = 'secret';
5
5
  exports.QUERY_PARAM_PROTECTION_BYPASS_SITECORE = 'x-sitecore-protection-bypass';
6
6
  exports.QUERY_PARAM_PROTECTION_BYPASS_VERCEL = 'x-vercel-protection-bypass';
7
+ /**
8
+ * Default allowed origins for editing requests. This is used to enforce CORS, CSP headers.
9
+ */
7
10
  exports.EDITING_ALLOWED_ORIGINS = ['https://pages*.cloud', 'https://pages.sitecorecloud.io'];
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.extractEditingData = exports.EditingRenderMiddleware = void 0;
12
+ exports.EditingRenderMiddleware = exports.MetadataHandler = exports.isEditingMetadataPreviewData = exports.ChromesHandler = void 0;
13
13
  const constants_1 = require("next/constants");
14
14
  const sitecore_jss_1 = require("@sitecore-jss/sitecore-jss");
15
15
  const layout_1 = require("@sitecore-jss/sitecore-jss/layout");
@@ -20,50 +20,50 @@ const utils_1 = require("../utils/utils");
20
20
  const render_middleware_1 = require("./render-middleware");
21
21
  const utils_2 = require("@sitecore-jss/sitecore-jss/utils");
22
22
  /**
23
- * Middleware / handler for use in the editing render Next.js API route (e.g. '/api/editing/render')
24
- * which is required for Sitecore editing support.
23
+ * Handler for the Editing Chromes POST requests.
24
+ * This handler is responsible for rendering the page and returning the HTML content that is provided via request.
25
25
  */
26
- class EditingRenderMiddleware extends render_middleware_1.RenderMiddlewareBase {
27
- /**
28
- * @param {EditingRenderMiddlewareConfig} [config] Editing render middleware config
29
- */
26
+ class ChromesHandler extends render_middleware_1.RenderMiddlewareBase {
30
27
  constructor(config) {
31
28
  var _a, _b, _c, _d;
32
29
  super();
33
- this.handler = (req, res) => __awaiter(this, void 0, void 0, function* () {
34
- var _e, _f, _g;
35
- const { method, query, body, headers } = req;
30
+ this.config = config;
31
+ /**
32
+ * Default page URL resolution.
33
+ * @param {Object} args Arguments for resolving the page URL
34
+ * @param {string} args.serverUrl The root server URL e.g. 'http://localhost:3000'
35
+ * @param {string} args.itemPath The Sitecore relative item path e.g. '/styleguide'
36
+ * @returns {string} The URL to render
37
+ */
38
+ this.defaultResolvePageUrl = ({ serverUrl, itemPath, }) => {
39
+ return `${serverUrl}${itemPath}`;
40
+ };
41
+ /**
42
+ * Default server URL resolution.
43
+ * Note we use https protocol on Vercel due to serverless function architecture.
44
+ * In all other scenarios, including localhost (with or without a proxy e.g. ngrok)
45
+ * and within a nodejs container, http protocol should be used.
46
+ *
47
+ * For information about the VERCEL environment variable, see
48
+ * https://vercel.com/docs/environment-variables#system-environment-variables
49
+ * @param {NextApiRequest} req
50
+ */
51
+ this.defaultResolveServerUrl = (req) => {
52
+ return `${process.env.VERCEL ? 'https' : 'http'}://${req.headers.host}`;
53
+ };
54
+ this.editingDataService = (_a = config === null || config === void 0 ? void 0 : config.editingDataService) !== null && _a !== void 0 ? _a : editing_data_service_1.editingDataService;
55
+ this.dataFetcher = (_b = config === null || config === void 0 ? void 0 : config.dataFetcher) !== null && _b !== void 0 ? _b : new sitecore_jss_1.AxiosDataFetcher({ debugger: sitecore_jss_1.debug.editing });
56
+ this.resolvePageUrl = (_c = config === null || config === void 0 ? void 0 : config.resolvePageUrl) !== null && _c !== void 0 ? _c : this.defaultResolvePageUrl;
57
+ this.resolveServerUrl = (_d = config === null || config === void 0 ? void 0 : config.resolveServerUrl) !== null && _d !== void 0 ? _d : this.defaultResolveServerUrl;
58
+ }
59
+ render(req, res) {
60
+ var _a;
61
+ return __awaiter(this, void 0, void 0, function* () {
62
+ const { query } = req;
36
63
  const startTimestamp = Date.now();
37
- sitecore_jss_1.debug.editing('editing render middleware start: %o', {
38
- method,
39
- query,
40
- headers,
41
- body,
42
- });
43
- if (!(0, utils_2.enforceCors)(req, res, constants_2.EDITING_ALLOWED_ORIGINS)) {
44
- sitecore_jss_1.debug.editing('invalid origin host - set allowed origins in JSS_ALLOWED_ORIGINS environment variable');
45
- return res.status(401).json({
46
- html: `<html><body>Requests from origin ${(_e = req.headers) === null || _e === void 0 ? void 0 : _e.origin} not allowed</body></html>`,
47
- });
48
- }
49
- if (method !== 'POST') {
50
- sitecore_jss_1.debug.editing('invalid method - sent %s expected POST', method);
51
- res.setHeader('Allow', 'POST');
52
- return res.status(405).json({
53
- html: `<html><body>Invalid request method '${method}'</body></html>`,
54
- });
55
- }
56
- // Validate secret
57
- const secret = (_f = query[constants_2.QUERY_PARAM_EDITING_SECRET]) !== null && _f !== void 0 ? _f : body === null || body === void 0 ? void 0 : body.jssEditingSecret;
58
- if (secret !== (0, utils_1.getJssEditingSecret)()) {
59
- sitecore_jss_1.debug.editing('invalid editing secret - sent "%s" expected "%s"', secret, (0, utils_1.getJssEditingSecret)());
60
- return res.status(401).json({
61
- html: '<html><body>Missing or invalid secret</body></html>',
62
- });
63
- }
64
64
  try {
65
65
  // Extract data from EE payload
66
- const editingData = extractEditingData(req);
66
+ const editingData = this.extractEditingData(req);
67
67
  // Resolve server URL
68
68
  const serverUrl = this.resolveServerUrl(req);
69
69
  // Get query string parameters to propagate on subsequent requests (e.g. for deployment protection bypass)
@@ -79,7 +79,7 @@ class EditingRenderMiddleware extends render_middleware_1.RenderMiddlewareBase {
79
79
  // Make actual render request for page route, passing on preview cookies as well as any approved query string parameters.
80
80
  // Note timestamp effectively disables caching the request in Axios (no amount of cache headers seemed to do it)
81
81
  sitecore_jss_1.debug.editing('fetching page route for %s', editingData.path);
82
- const requestUrl = new URL(this.resolvePageUrl(serverUrl, editingData.path));
82
+ const requestUrl = new URL(this.resolvePageUrl({ serverUrl, itemPath: editingData.path }));
83
83
  for (const key in params) {
84
84
  if ({}.hasOwnProperty.call(params, key)) {
85
85
  requestUrl.searchParams.append(key, params[key]);
@@ -116,7 +116,7 @@ class EditingRenderMiddleware extends render_middleware_1.RenderMiddlewareBase {
116
116
  html = html.replace(constants_1.STATIC_PROPS_ID, constants_1.SERVER_PROPS_ID);
117
117
  if (editingData.layoutData.sitecore.context.renderingType === layout_1.RenderingType.Component) {
118
118
  // Handle component rendering. Extract component markup only
119
- html = (_g = (0, node_html_parser_1.parse)(html).getElementById(layout_1.EDITING_COMPONENT_ID)) === null || _g === void 0 ? void 0 : _g.innerHTML;
119
+ html = (_a = (0, node_html_parser_1.parse)(html).getElementById(layout_1.EDITING_COMPONENT_ID)) === null || _a === void 0 ? void 0 : _a.innerHTML;
120
120
  if (!html)
121
121
  throw new Error(`Failed to render component for ${editingData.path}`);
122
122
  }
@@ -142,31 +142,171 @@ class EditingRenderMiddleware extends render_middleware_1.RenderMiddlewareBase {
142
142
  });
143
143
  }
144
144
  });
145
- /**
146
- * Default page URL resolution.
147
- * @param {string} serverUrl
148
- * @param {string} itemPath
149
- */
150
- this.defaultResolvePageUrl = (serverUrl, itemPath) => {
151
- return `${serverUrl}${itemPath}`;
152
- };
153
- /**
154
- * Default server URL resolution.
155
- * Note we use https protocol on Vercel due to serverless function architecture.
156
- * In all other scenarios, including localhost (with or without a proxy e.g. ngrok)
157
- * and within a nodejs container, http protocol should be used.
158
- *
159
- * For information about the VERCEL environment variable, see
160
- * https://vercel.com/docs/environment-variables#system-environment-variables
161
- * @param {NextApiRequest} req
162
- */
163
- this.defaultResolveServerUrl = (req) => {
164
- return `${process.env.VERCEL ? 'https' : 'http'}://${req.headers.host}`;
145
+ }
146
+ extractEditingData(req) {
147
+ // Sitecore editors will send the following body data structure,
148
+ // though we're only concerned with the "args".
149
+ // {
150
+ // id: 'JSS app name',
151
+ // args: ['path', 'serialized layout data object', 'serialized viewbag object'],
152
+ // functionName: 'renderView',
153
+ // moduleName: 'server.bundle'
154
+ // }
155
+ // The 'serialized viewbag object' structure:
156
+ // {
157
+ // language: 'language',
158
+ // dictionary: 'key-value representation of tokens and their corresponding translations',
159
+ // httpContext: 'serialized request data'
160
+ // }
161
+ var _a;
162
+ // Note req.body _should_ have already been parsed as JSON at this point (via Next.js `bodyParser` API middleware)
163
+ const payload = req.body;
164
+ if (!payload || !payload.args || !Array.isArray(payload.args) || payload.args.length < 3) {
165
+ throw new Error('Unable to extract editing data from request. Ensure `bodyParser` middleware is enabled on your Next.js API route.');
166
+ }
167
+ const layoutData = JSON.parse(payload.args[1]);
168
+ const viewBag = JSON.parse(payload.args[2]);
169
+ // Keep backwards compatibility in case people use an older JSS version that doesn't send the path in the context
170
+ const path = (_a = layoutData.sitecore.context.itemPath) !== null && _a !== void 0 ? _a : viewBag.httpContext.request.path;
171
+ return {
172
+ path,
173
+ layoutData,
174
+ language: viewBag.language,
175
+ dictionary: viewBag.dictionary,
165
176
  };
166
- this.editingDataService = (_a = config === null || config === void 0 ? void 0 : config.editingDataService) !== null && _a !== void 0 ? _a : editing_data_service_1.editingDataService;
167
- this.dataFetcher = (_b = config === null || config === void 0 ? void 0 : config.dataFetcher) !== null && _b !== void 0 ? _b : new sitecore_jss_1.AxiosDataFetcher({ debugger: sitecore_jss_1.debug.editing });
168
- this.resolvePageUrl = (_c = config === null || config === void 0 ? void 0 : config.resolvePageUrl) !== null && _c !== void 0 ? _c : this.defaultResolvePageUrl;
169
- this.resolveServerUrl = (_d = config === null || config === void 0 ? void 0 : config.resolveServerUrl) !== null && _d !== void 0 ? _d : this.defaultResolveServerUrl;
177
+ }
178
+ }
179
+ exports.ChromesHandler = ChromesHandler;
180
+ /**
181
+ * Type guard for EditingMetadataPreviewData
182
+ * @param {Object} data preview data to check
183
+ * @returns true if the data is EditingMetadataPreviewData
184
+ * @see EditingMetadataPreviewData
185
+ */
186
+ const isEditingMetadataPreviewData = (data) => {
187
+ return (typeof data === 'object' &&
188
+ data !== null &&
189
+ 'editMode' in data &&
190
+ data.editMode === layout_1.EditMode.Metadata);
191
+ };
192
+ exports.isEditingMetadataPreviewData = isEditingMetadataPreviewData;
193
+ /**
194
+ * Handler for the Editing Metadata GET requests.
195
+ * This handler is responsible for redirecting the request to the page route.
196
+ * The page fetches the layout, dictionary and renders the page.
197
+ */
198
+ class MetadataHandler {
199
+ constructor(config) {
200
+ this.config = config;
201
+ }
202
+ render(req, res) {
203
+ var _a, _b;
204
+ const { query } = req;
205
+ const startTimestamp = Date.now();
206
+ const requiredQueryParams = [
207
+ 'sc_site',
208
+ 'sc_itemid',
209
+ 'sc_lang',
210
+ 'sc_variant',
211
+ 'sc_version',
212
+ 'mode',
213
+ ];
214
+ const missingQueryParams = requiredQueryParams.filter((param) => !query[param]);
215
+ // Validate query parameters
216
+ if (missingQueryParams.length) {
217
+ sitecore_jss_1.debug.editing('missing required query parameters: %o', missingQueryParams);
218
+ return res.status(400).json({
219
+ html: `<html><body>Missing required query parameters: ${missingQueryParams.join(', ')}</body></html>`,
220
+ });
221
+ }
222
+ res.setPreviewData({
223
+ site: query.sc_site,
224
+ itemId: query.sc_itemid,
225
+ language: query.sc_lang,
226
+ // sc_variant is an array in the query params, but we only need the first value
227
+ variantId: query.sc_variant.split(',')[0],
228
+ version: query.sc_version,
229
+ editMode: layout_1.EditMode.Metadata,
230
+ pageState: query.mode,
231
+ },
232
+ // Cache the preview data for 3 seconds to ensure the page is rendered with the correct preview data not the cached one
233
+ {
234
+ path: query.route,
235
+ maxAge: 3,
236
+ });
237
+ const route = ((_b = (_a = this.config).resolvePageUrl) === null || _b === void 0 ? void 0 : _b.call(_a, {
238
+ itemPath: query.route,
239
+ })) || query.route;
240
+ sitecore_jss_1.debug.editing('editing render middleware end in %dms: redirect %o', Date.now() - startTimestamp, {
241
+ status: 307,
242
+ route,
243
+ });
244
+ // Restrict the page to be rendered only within the allowed origins
245
+ res.setHeader('Content-Security-Policy', this.getSCPHeader());
246
+ res.redirect(route);
247
+ }
248
+ /**
249
+ * Gets the Content-Security-Policy header value
250
+ * @returns Content-Security-Policy header value
251
+ */
252
+ getSCPHeader() {
253
+ return `frame-ancestors 'self' ${[(0, utils_2.getAllowedOriginsFromEnv)(), ...constants_2.EDITING_ALLOWED_ORIGINS].join(' ')}`;
254
+ }
255
+ }
256
+ exports.MetadataHandler = MetadataHandler;
257
+ /**
258
+ * Middleware / handler for use in the editing render Next.js API route (e.g. '/api/editing/render')
259
+ * which is required for Sitecore editing support.
260
+ */
261
+ class EditingRenderMiddleware extends render_middleware_1.RenderMiddlewareBase {
262
+ /**
263
+ * @param {EditingRenderMiddlewareConfig} [config] Editing render middleware config
264
+ */
265
+ constructor(config) {
266
+ super();
267
+ this.config = config;
268
+ this.handler = (req, res) => __awaiter(this, void 0, void 0, function* () {
269
+ var _a, _b, _c;
270
+ const { query, body, method, headers } = req;
271
+ sitecore_jss_1.debug.editing('editing render middleware start: %o', {
272
+ method,
273
+ query,
274
+ headers,
275
+ body,
276
+ });
277
+ if (!(0, utils_2.enforceCors)(req, res, constants_2.EDITING_ALLOWED_ORIGINS)) {
278
+ sitecore_jss_1.debug.editing('invalid origin host - set allowed origins in JSS_ALLOWED_ORIGINS environment variable');
279
+ return res.status(401).json({
280
+ html: `<html><body>Requests from origin ${(_a = req.headers) === null || _a === void 0 ? void 0 : _a.origin} not allowed</body></html>`,
281
+ });
282
+ }
283
+ // Validate secret
284
+ const secret = (_b = query[constants_2.QUERY_PARAM_EDITING_SECRET]) !== null && _b !== void 0 ? _b : body === null || body === void 0 ? void 0 : body.jssEditingSecret;
285
+ if (secret !== (0, utils_1.getJssEditingSecret)()) {
286
+ sitecore_jss_1.debug.editing('invalid editing secret - sent "%s" expected "%s"', secret, (0, utils_1.getJssEditingSecret)());
287
+ return res.status(401).json({
288
+ html: '<html><body>Missing or invalid secret</body></html>',
289
+ });
290
+ }
291
+ switch (req.method) {
292
+ case 'GET': {
293
+ const handler = new MetadataHandler({ resolvePageUrl: (_c = this.config) === null || _c === void 0 ? void 0 : _c.resolvePageUrl });
294
+ yield handler.render(req, res);
295
+ break;
296
+ }
297
+ case 'POST': {
298
+ const handler = new ChromesHandler(this.config);
299
+ yield handler.render(req, res);
300
+ break;
301
+ }
302
+ default:
303
+ sitecore_jss_1.debug.editing('invalid method - sent %s expected GET/POST', req.method);
304
+ res.setHeader('Allow', 'GET, POST');
305
+ return res.status(405).json({
306
+ html: `<html><body>Invalid request method '${req.method}'</body></html>`,
307
+ });
308
+ }
309
+ });
170
310
  }
171
311
  /**
172
312
  * Gets the Next.js API route handler
@@ -177,39 +317,3 @@ class EditingRenderMiddleware extends render_middleware_1.RenderMiddlewareBase {
177
317
  }
178
318
  }
179
319
  exports.EditingRenderMiddleware = EditingRenderMiddleware;
180
- /**
181
- * @param {NextApiRequest} req
182
- */
183
- function extractEditingData(req) {
184
- // Sitecore editors will send the following body data structure,
185
- // though we're only concerned with the "args".
186
- // {
187
- // id: 'JSS app name',
188
- // args: ['path', 'serialized layout data object', 'serialized viewbag object'],
189
- // functionName: 'renderView',
190
- // moduleName: 'server.bundle'
191
- // }
192
- // The 'serialized viewbag object' structure:
193
- // {
194
- // language: 'language',
195
- // dictionary: 'key-value representation of tokens and their corresponding translations',
196
- // httpContext: 'serialized request data'
197
- // }
198
- var _a;
199
- // Note req.body _should_ have already been parsed as JSON at this point (via Next.js `bodyParser` API middleware)
200
- const payload = req.body;
201
- if (!payload || !payload.args || !Array.isArray(payload.args) || payload.args.length < 3) {
202
- throw new Error('Unable to extract editing data from request. Ensure `bodyParser` middleware is enabled on your Next.js API route.');
203
- }
204
- const layoutData = JSON.parse(payload.args[1]);
205
- const viewBag = JSON.parse(payload.args[2]);
206
- // Keep backwards compatibility in case people use an older JSS version that doesn't send the path in the context
207
- const path = (_a = layoutData.sitecore.context.itemPath) !== null && _a !== void 0 ? _a : viewBag.httpContext.request.path;
208
- return {
209
- path,
210
- layoutData,
211
- language: viewBag.language,
212
- dictionary: viewBag.dictionary,
213
- };
214
- }
215
- exports.extractEditingData = extractEditingData;
@@ -1,12 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.EditingConfigMiddleware = exports.FEAASRenderMiddleware = exports.VercelEditingDataCache = exports.editingDataService = exports.ServerlessEditingDataService = exports.BasicEditingDataService = exports.EditingRenderMiddleware = exports.EditingDataMiddleware = exports.EditingDataDiskCache = void 0;
3
+ exports.EditingConfigMiddleware = exports.FEAASRenderMiddleware = exports.VercelEditingDataCache = exports.editingDataService = exports.ServerlessEditingDataService = exports.BasicEditingDataService = exports.isEditingMetadataPreviewData = exports.EditingRenderMiddleware = exports.EditingDataMiddleware = exports.EditingDataDiskCache = exports.GraphQLEditingService = void 0;
4
+ var editing_1 = require("@sitecore-jss/sitecore-jss/editing");
5
+ Object.defineProperty(exports, "GraphQLEditingService", { enumerable: true, get: function () { return editing_1.GraphQLEditingService; } });
4
6
  var editing_data_cache_1 = require("./editing-data-cache");
5
7
  Object.defineProperty(exports, "EditingDataDiskCache", { enumerable: true, get: function () { return editing_data_cache_1.EditingDataDiskCache; } });
6
8
  var editing_data_middleware_1 = require("./editing-data-middleware");
7
9
  Object.defineProperty(exports, "EditingDataMiddleware", { enumerable: true, get: function () { return editing_data_middleware_1.EditingDataMiddleware; } });
8
10
  var editing_render_middleware_1 = require("./editing-render-middleware");
9
11
  Object.defineProperty(exports, "EditingRenderMiddleware", { enumerable: true, get: function () { return editing_render_middleware_1.EditingRenderMiddleware; } });
12
+ Object.defineProperty(exports, "isEditingMetadataPreviewData", { enumerable: true, get: function () { return editing_render_middleware_1.isEditingMetadataPreviewData; } });
10
13
  var editing_data_service_1 = require("./editing-data-service");
11
14
  Object.defineProperty(exports, "BasicEditingDataService", { enumerable: true, get: function () { return editing_data_service_1.BasicEditingDataService; } });
12
15
  Object.defineProperty(exports, "ServerlessEditingDataService", { enumerable: true, get: function () { return editing_data_service_1.ServerlessEditingDataService; } });
@@ -1,11 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.resolveUrl = exports.resetEditorChromes = exports.isEditorActive = exports.tryParseEnvValue = exports.handleEditorFastRefresh = exports.getPublicUrl = void 0;
3
+ exports.resetEditorChromes = exports.isEditorActive = exports.resolveUrl = exports.tryParseEnvValue = exports.handleEditorFastRefresh = exports.getPublicUrl = void 0;
4
4
  var utils_1 = require("./utils");
5
5
  Object.defineProperty(exports, "getPublicUrl", { enumerable: true, get: function () { return utils_1.getPublicUrl; } });
6
6
  Object.defineProperty(exports, "handleEditorFastRefresh", { enumerable: true, get: function () { return utils_1.handleEditorFastRefresh; } });
7
7
  var utils_2 = require("@sitecore-jss/sitecore-jss/utils");
8
8
  Object.defineProperty(exports, "tryParseEnvValue", { enumerable: true, get: function () { return utils_2.tryParseEnvValue; } });
9
- Object.defineProperty(exports, "isEditorActive", { enumerable: true, get: function () { return utils_2.isEditorActive; } });
10
- Object.defineProperty(exports, "resetEditorChromes", { enumerable: true, get: function () { return utils_2.resetEditorChromes; } });
11
9
  Object.defineProperty(exports, "resolveUrl", { enumerable: true, get: function () { return utils_2.resolveUrl; } });
10
+ var editing_1 = require("@sitecore-jss/sitecore-jss/editing");
11
+ Object.defineProperty(exports, "isEditorActive", { enumerable: true, get: function () { return editing_1.isEditorActive; } });
12
+ Object.defineProperty(exports, "resetEditorChromes", { enumerable: true, get: function () { return editing_1.resetEditorChromes; } });
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getJssEditingSecret = exports.handleEditorFastRefresh = exports.getPublicUrl = void 0;
4
- const utils_1 = require("@sitecore-jss/sitecore-jss/utils");
4
+ const editing_1 = require("@sitecore-jss/sitecore-jss/editing");
5
5
  /**
6
6
  * Get the publicUrl.
7
7
  * This is used primarily to enable compatibility with Sitecore editors.
@@ -32,7 +32,7 @@ exports.getPublicUrl = getPublicUrl;
32
32
  * @default forceReload false
33
33
  */
34
34
  const handleEditorFastRefresh = (forceReload = false) => {
35
- if (process.env.NODE_ENV !== 'development' || !(0, utils_1.isEditorActive)()) {
35
+ if (process.env.NODE_ENV !== 'development' || !(0, editing_1.isEditorActive)()) {
36
36
  // Only run if development mode and editor is active
37
37
  return;
38
38
  }
@@ -50,7 +50,7 @@ const handleEditorFastRefresh = (forceReload = false) => {
50
50
  return window.location.reload();
51
51
  setTimeout(() => {
52
52
  console.log('[Sitecore Editor HMR Listener] Sitecore editor does not support Fast Refresh, reloading chromes...');
53
- (0, utils_1.resetEditorChromes)();
53
+ (0, editing_1.resetEditorChromes)();
54
54
  }, 500);
55
55
  };
56
56
  };
@@ -1,4 +1,7 @@
1
1
  export const QUERY_PARAM_EDITING_SECRET = 'secret';
2
2
  export const QUERY_PARAM_PROTECTION_BYPASS_SITECORE = 'x-sitecore-protection-bypass';
3
3
  export const QUERY_PARAM_PROTECTION_BYPASS_VERCEL = 'x-vercel-protection-bypass';
4
+ /**
5
+ * Default allowed origins for editing requests. This is used to enforce CORS, CSP headers.
6
+ */
4
7
  export const EDITING_ALLOWED_ORIGINS = ['https://pages*.cloud', 'https://pages.sitecorecloud.io'];
@@ -9,58 +9,58 @@ 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';
12
+ import { EDITING_COMPONENT_ID, EditMode, RenderingType, } from '@sitecore-jss/sitecore-jss/layout';
13
13
  import { parse } from 'node-html-parser';
14
14
  import { editingDataService } from './editing-data-service';
15
15
  import { EDITING_ALLOWED_ORIGINS, QUERY_PARAM_EDITING_SECRET } from './constants';
16
16
  import { getJssEditingSecret } from '../utils/utils';
17
17
  import { RenderMiddlewareBase } from './render-middleware';
18
- import { enforceCors } from '@sitecore-jss/sitecore-jss/utils';
18
+ import { enforceCors, getAllowedOriginsFromEnv } from '@sitecore-jss/sitecore-jss/utils';
19
19
  /**
20
- * Middleware / handler for use in the editing render Next.js API route (e.g. '/api/editing/render')
21
- * 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.
22
22
  */
23
- export class EditingRenderMiddleware extends RenderMiddlewareBase {
24
- /**
25
- * @param {EditingRenderMiddlewareConfig} [config] Editing render middleware config
26
- */
23
+ export class ChromesHandler extends RenderMiddlewareBase {
27
24
  constructor(config) {
28
25
  var _a, _b, _c, _d;
29
26
  super();
30
- this.handler = (req, res) => __awaiter(this, void 0, void 0, function* () {
31
- var _e, _f, _g;
32
- 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
+ var _a;
58
+ return __awaiter(this, void 0, void 0, function* () {
59
+ const { query } = req;
33
60
  const startTimestamp = Date.now();
34
- debug.editing('editing render middleware start: %o', {
35
- method,
36
- query,
37
- headers,
38
- body,
39
- });
40
- if (!enforceCors(req, res, EDITING_ALLOWED_ORIGINS)) {
41
- debug.editing('invalid origin host - set allowed origins in JSS_ALLOWED_ORIGINS environment variable');
42
- return res.status(401).json({
43
- html: `<html><body>Requests from origin ${(_e = req.headers) === null || _e === void 0 ? void 0 : _e.origin} not allowed</body></html>`,
44
- });
45
- }
46
- if (method !== 'POST') {
47
- debug.editing('invalid method - sent %s expected POST', method);
48
- res.setHeader('Allow', 'POST');
49
- return res.status(405).json({
50
- html: `<html><body>Invalid request method '${method}'</body></html>`,
51
- });
52
- }
53
- // Validate secret
54
- const secret = (_f = query[QUERY_PARAM_EDITING_SECRET]) !== null && _f !== void 0 ? _f : body === null || body === void 0 ? void 0 : body.jssEditingSecret;
55
- if (secret !== getJssEditingSecret()) {
56
- debug.editing('invalid editing secret - sent "%s" expected "%s"', secret, getJssEditingSecret());
57
- return res.status(401).json({
58
- html: '<html><body>Missing or invalid secret</body></html>',
59
- });
60
- }
61
61
  try {
62
62
  // Extract data from EE payload
63
- const editingData = extractEditingData(req);
63
+ const editingData = this.extractEditingData(req);
64
64
  // Resolve server URL
65
65
  const serverUrl = this.resolveServerUrl(req);
66
66
  // Get query string parameters to propagate on subsequent requests (e.g. for deployment protection bypass)
@@ -76,7 +76,7 @@ export class EditingRenderMiddleware extends RenderMiddlewareBase {
76
76
  // Make actual render request for page route, passing on preview cookies as well as any approved query string parameters.
77
77
  // Note timestamp effectively disables caching the request in Axios (no amount of cache headers seemed to do it)
78
78
  debug.editing('fetching page route for %s', editingData.path);
79
- const requestUrl = new URL(this.resolvePageUrl(serverUrl, editingData.path));
79
+ const requestUrl = new URL(this.resolvePageUrl({ serverUrl, itemPath: editingData.path }));
80
80
  for (const key in params) {
81
81
  if ({}.hasOwnProperty.call(params, key)) {
82
82
  requestUrl.searchParams.append(key, params[key]);
@@ -113,7 +113,7 @@ export class EditingRenderMiddleware extends RenderMiddlewareBase {
113
113
  html = html.replace(STATIC_PROPS_ID, SERVER_PROPS_ID);
114
114
  if (editingData.layoutData.sitecore.context.renderingType === RenderingType.Component) {
115
115
  // Handle component rendering. Extract component markup only
116
- html = (_g = parse(html).getElementById(EDITING_COMPONENT_ID)) === null || _g === void 0 ? void 0 : _g.innerHTML;
116
+ html = (_a = parse(html).getElementById(EDITING_COMPONENT_ID)) === null || _a === void 0 ? void 0 : _a.innerHTML;
117
117
  if (!html)
118
118
  throw new Error(`Failed to render component for ${editingData.path}`);
119
119
  }
@@ -139,72 +139,174 @@ export class EditingRenderMiddleware extends RenderMiddlewareBase {
139
139
  });
140
140
  }
141
141
  });
142
- /**
143
- * Default page URL resolution.
144
- * @param {string} serverUrl
145
- * @param {string} itemPath
146
- */
147
- this.defaultResolvePageUrl = (serverUrl, itemPath) => {
148
- return `${serverUrl}${itemPath}`;
149
- };
150
- /**
151
- * Default server URL resolution.
152
- * Note we use https protocol on Vercel due to serverless function architecture.
153
- * In all other scenarios, including localhost (with or without a proxy e.g. ngrok)
154
- * and within a nodejs container, http protocol should be used.
155
- *
156
- * For information about the VERCEL environment variable, see
157
- * https://vercel.com/docs/environment-variables#system-environment-variables
158
- * @param {NextApiRequest} req
159
- */
160
- this.defaultResolveServerUrl = (req) => {
161
- return `${process.env.VERCEL ? 'https' : 'http'}://${req.headers.host}`;
142
+ }
143
+ extractEditingData(req) {
144
+ // Sitecore editors will send the following body data structure,
145
+ // though we're only concerned with the "args".
146
+ // {
147
+ // id: 'JSS app name',
148
+ // args: ['path', 'serialized layout data object', 'serialized viewbag object'],
149
+ // functionName: 'renderView',
150
+ // moduleName: 'server.bundle'
151
+ // }
152
+ // The 'serialized viewbag object' structure:
153
+ // {
154
+ // language: 'language',
155
+ // dictionary: 'key-value representation of tokens and their corresponding translations',
156
+ // httpContext: 'serialized request data'
157
+ // }
158
+ var _a;
159
+ // Note req.body _should_ have already been parsed as JSON at this point (via Next.js `bodyParser` API middleware)
160
+ const payload = req.body;
161
+ if (!payload || !payload.args || !Array.isArray(payload.args) || payload.args.length < 3) {
162
+ throw new Error('Unable to extract editing data from request. Ensure `bodyParser` middleware is enabled on your Next.js API route.');
163
+ }
164
+ const layoutData = JSON.parse(payload.args[1]);
165
+ const viewBag = JSON.parse(payload.args[2]);
166
+ // Keep backwards compatibility in case people use an older JSS version that doesn't send the path in the context
167
+ const path = (_a = layoutData.sitecore.context.itemPath) !== null && _a !== void 0 ? _a : viewBag.httpContext.request.path;
168
+ return {
169
+ path,
170
+ layoutData,
171
+ language: viewBag.language,
172
+ dictionary: viewBag.dictionary,
162
173
  };
163
- this.editingDataService = (_a = config === null || config === void 0 ? void 0 : config.editingDataService) !== null && _a !== void 0 ? _a : editingDataService;
164
- this.dataFetcher = (_b = config === null || config === void 0 ? void 0 : config.dataFetcher) !== null && _b !== void 0 ? _b : new AxiosDataFetcher({ debugger: debug.editing });
165
- this.resolvePageUrl = (_c = config === null || config === void 0 ? void 0 : config.resolvePageUrl) !== null && _c !== void 0 ? _c : this.defaultResolvePageUrl;
166
- this.resolveServerUrl = (_d = config === null || config === void 0 ? void 0 : config.resolveServerUrl) !== null && _d !== void 0 ? _d : this.defaultResolveServerUrl;
174
+ }
175
+ }
176
+ /**
177
+ * Type guard for EditingMetadataPreviewData
178
+ * @param {Object} data preview data to check
179
+ * @returns true if the data is EditingMetadataPreviewData
180
+ * @see EditingMetadataPreviewData
181
+ */
182
+ export const isEditingMetadataPreviewData = (data) => {
183
+ return (typeof data === 'object' &&
184
+ data !== null &&
185
+ 'editMode' in data &&
186
+ data.editMode === EditMode.Metadata);
187
+ };
188
+ /**
189
+ * Handler for the Editing Metadata GET requests.
190
+ * This handler is responsible for redirecting the request to the page route.
191
+ * The page fetches the layout, dictionary and renders the page.
192
+ */
193
+ export class MetadataHandler {
194
+ constructor(config) {
195
+ this.config = config;
196
+ }
197
+ render(req, res) {
198
+ var _a, _b;
199
+ const { query } = req;
200
+ const startTimestamp = Date.now();
201
+ const requiredQueryParams = [
202
+ 'sc_site',
203
+ 'sc_itemid',
204
+ 'sc_lang',
205
+ 'sc_variant',
206
+ 'sc_version',
207
+ 'mode',
208
+ ];
209
+ const missingQueryParams = requiredQueryParams.filter((param) => !query[param]);
210
+ // Validate query parameters
211
+ if (missingQueryParams.length) {
212
+ debug.editing('missing required query parameters: %o', missingQueryParams);
213
+ return res.status(400).json({
214
+ html: `<html><body>Missing required query parameters: ${missingQueryParams.join(', ')}</body></html>`,
215
+ });
216
+ }
217
+ res.setPreviewData({
218
+ site: query.sc_site,
219
+ itemId: query.sc_itemid,
220
+ language: query.sc_lang,
221
+ // sc_variant is an array in the query params, but we only need the first value
222
+ variantId: query.sc_variant.split(',')[0],
223
+ version: query.sc_version,
224
+ editMode: EditMode.Metadata,
225
+ pageState: query.mode,
226
+ },
227
+ // Cache the preview data for 3 seconds to ensure the page is rendered with the correct preview data not the cached one
228
+ {
229
+ path: query.route,
230
+ maxAge: 3,
231
+ });
232
+ const route = ((_b = (_a = this.config).resolvePageUrl) === null || _b === void 0 ? void 0 : _b.call(_a, {
233
+ itemPath: query.route,
234
+ })) || query.route;
235
+ debug.editing('editing render middleware end in %dms: redirect %o', Date.now() - startTimestamp, {
236
+ status: 307,
237
+ route,
238
+ });
239
+ // Restrict the page to be rendered only within the allowed origins
240
+ res.setHeader('Content-Security-Policy', this.getSCPHeader());
241
+ res.redirect(route);
167
242
  }
168
243
  /**
169
- * Gets the Next.js API route handler
170
- * @returns route handler
244
+ * Gets the Content-Security-Policy header value
245
+ * @returns Content-Security-Policy header value
171
246
  */
172
- getHandler() {
173
- return this.handler;
247
+ getSCPHeader() {
248
+ return `frame-ancestors 'self' ${[getAllowedOriginsFromEnv(), ...EDITING_ALLOWED_ORIGINS].join(' ')}`;
174
249
  }
175
250
  }
176
251
  /**
177
- * @param {NextApiRequest} req
252
+ * Middleware / handler for use in the editing render Next.js API route (e.g. '/api/editing/render')
253
+ * which is required for Sitecore editing support.
178
254
  */
179
- export function extractEditingData(req) {
180
- // Sitecore editors will send the following body data structure,
181
- // though we're only concerned with the "args".
182
- // {
183
- // id: 'JSS app name',
184
- // args: ['path', 'serialized layout data object', 'serialized viewbag object'],
185
- // functionName: 'renderView',
186
- // moduleName: 'server.bundle'
187
- // }
188
- // The 'serialized viewbag object' structure:
189
- // {
190
- // language: 'language',
191
- // dictionary: 'key-value representation of tokens and their corresponding translations',
192
- // httpContext: 'serialized request data'
193
- // }
194
- var _a;
195
- // Note req.body _should_ have already been parsed as JSON at this point (via Next.js `bodyParser` API middleware)
196
- const payload = req.body;
197
- if (!payload || !payload.args || !Array.isArray(payload.args) || payload.args.length < 3) {
198
- throw new Error('Unable to extract editing data from request. Ensure `bodyParser` middleware is enabled on your Next.js API route.');
255
+ export class EditingRenderMiddleware extends RenderMiddlewareBase {
256
+ /**
257
+ * @param {EditingRenderMiddlewareConfig} [config] Editing render middleware config
258
+ */
259
+ constructor(config) {
260
+ super();
261
+ this.config = config;
262
+ this.handler = (req, res) => __awaiter(this, void 0, void 0, function* () {
263
+ var _a, _b, _c;
264
+ const { query, body, method, headers } = req;
265
+ debug.editing('editing render middleware start: %o', {
266
+ method,
267
+ query,
268
+ headers,
269
+ body,
270
+ });
271
+ if (!enforceCors(req, res, EDITING_ALLOWED_ORIGINS)) {
272
+ debug.editing('invalid origin host - set allowed origins in JSS_ALLOWED_ORIGINS environment variable');
273
+ return res.status(401).json({
274
+ html: `<html><body>Requests from origin ${(_a = req.headers) === null || _a === void 0 ? void 0 : _a.origin} not allowed</body></html>`,
275
+ });
276
+ }
277
+ // Validate secret
278
+ const secret = (_b = query[QUERY_PARAM_EDITING_SECRET]) !== null && _b !== void 0 ? _b : body === null || body === void 0 ? void 0 : body.jssEditingSecret;
279
+ if (secret !== getJssEditingSecret()) {
280
+ debug.editing('invalid editing secret - sent "%s" expected "%s"', secret, getJssEditingSecret());
281
+ return res.status(401).json({
282
+ html: '<html><body>Missing or invalid secret</body></html>',
283
+ });
284
+ }
285
+ switch (req.method) {
286
+ case 'GET': {
287
+ const handler = new MetadataHandler({ resolvePageUrl: (_c = this.config) === null || _c === void 0 ? void 0 : _c.resolvePageUrl });
288
+ yield handler.render(req, res);
289
+ break;
290
+ }
291
+ case 'POST': {
292
+ const handler = new ChromesHandler(this.config);
293
+ yield handler.render(req, res);
294
+ break;
295
+ }
296
+ default:
297
+ debug.editing('invalid method - sent %s expected GET/POST', req.method);
298
+ res.setHeader('Allow', 'GET, POST');
299
+ return res.status(405).json({
300
+ html: `<html><body>Invalid request method '${req.method}'</body></html>`,
301
+ });
302
+ }
303
+ });
304
+ }
305
+ /**
306
+ * Gets the Next.js API route handler
307
+ * @returns route handler
308
+ */
309
+ getHandler() {
310
+ return this.handler;
199
311
  }
200
- const layoutData = JSON.parse(payload.args[1]);
201
- const viewBag = JSON.parse(payload.args[2]);
202
- // Keep backwards compatibility in case people use an older JSS version that doesn't send the path in the context
203
- const path = (_a = layoutData.sitecore.context.itemPath) !== null && _a !== void 0 ? _a : viewBag.httpContext.request.path;
204
- return {
205
- path,
206
- layoutData,
207
- language: viewBag.language,
208
- dictionary: viewBag.dictionary,
209
- };
210
312
  }
@@ -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,2 +1,3 @@
1
1
  export { getPublicUrl, handleEditorFastRefresh } from './utils';
2
- export { tryParseEnvValue, isEditorActive, resetEditorChromes, resolveUrl, } from '@sitecore-jss/sitecore-jss/utils';
2
+ export { tryParseEnvValue, resolveUrl } from '@sitecore-jss/sitecore-jss/utils';
3
+ export { isEditorActive, resetEditorChromes } from '@sitecore-jss/sitecore-jss/editing';
@@ -1,4 +1,4 @@
1
- import { isEditorActive, resetEditorChromes } from '@sitecore-jss/sitecore-jss/utils';
1
+ import { isEditorActive, resetEditorChromes } from '@sitecore-jss/sitecore-jss/editing';
2
2
  /**
3
3
  * Get the publicUrl.
4
4
  * This is used primarily to enable compatibility with Sitecore editors.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sitecore-jss/sitecore-jss-nextjs",
3
- "version": "22.1.0-canary.19",
3
+ "version": "22.1.0-canary.22",
4
4
  "main": "dist/cjs/index.js",
5
5
  "module": "dist/esm/index.js",
6
6
  "sideEffects": false,
@@ -72,9 +72,9 @@
72
72
  "react-dom": "^18.2.0"
73
73
  },
74
74
  "dependencies": {
75
- "@sitecore-jss/sitecore-jss": "^22.1.0-canary.19",
76
- "@sitecore-jss/sitecore-jss-dev-tools": "^22.1.0-canary.19",
77
- "@sitecore-jss/sitecore-jss-react": "^22.1.0-canary.19",
75
+ "@sitecore-jss/sitecore-jss": "^22.1.0-canary.22",
76
+ "@sitecore-jss/sitecore-jss-dev-tools": "^22.1.0-canary.22",
77
+ "@sitecore-jss/sitecore-jss-react": "^22.1.0-canary.22",
78
78
  "@vercel/kv": "^0.2.1",
79
79
  "node-html-parser": "^6.1.4",
80
80
  "prop-types": "^15.8.1",
@@ -83,7 +83,7 @@
83
83
  },
84
84
  "description": "",
85
85
  "types": "types/index.d.ts",
86
- "gitHead": "2da1f09fc76a46fbd3e092dd12dcc793cced4224",
86
+ "gitHead": "c2f87c54444970255294606e5412c73095d64396",
87
87
  "files": [
88
88
  "dist",
89
89
  "types",
@@ -1,18 +1,16 @@
1
- /// <reference types="@types/react" />
2
- import { ComponentFactory } from '@sitecore-jss/sitecore-jss-react';
1
+ import { ComponentFactory, JssComponentType } from '@sitecore-jss/sitecore-jss-react';
3
2
  import { Module, ModuleFactory } from './sharedTypes/module-factory';
4
- import { ComponentType } from 'react';
5
3
  /**
6
4
  * Represents a component that can be imported dynamically
7
5
  */
8
6
  export type LazyModule = {
9
7
  module: () => Promise<Module>;
10
- element: (isEditing?: boolean) => ComponentType;
8
+ element: (isEditing?: boolean) => JssComponentType;
11
9
  };
12
10
  /**
13
11
  * Component is a module or a lazy module
14
12
  */
15
- type Component = Module | LazyModule | ComponentType;
13
+ type Component = Module | LazyModule | JssComponentType;
16
14
  /**
17
15
  * Configuration for ComponentBuilder
18
16
  */
@@ -1,4 +1,7 @@
1
1
  export declare const QUERY_PARAM_EDITING_SECRET = "secret";
2
2
  export declare const QUERY_PARAM_PROTECTION_BYPASS_SITECORE = "x-sitecore-protection-bypass";
3
3
  export declare const QUERY_PARAM_PROTECTION_BYPASS_VERCEL = "x-vercel-protection-bypass";
4
+ /**
5
+ * Default allowed origins for editing requests. This is used to enforce CORS, CSP headers.
6
+ */
4
7
  export declare const EDITING_ALLOWED_ORIGINS: string[];
@@ -4,6 +4,7 @@ import { EditingDataCache } from './editing-data-cache';
4
4
  import { PreviewData } from 'next';
5
5
  /**
6
6
  * Data for Next.js Preview (Editing) mode
7
+ * Used in Chromes Edit Mode only
7
8
  */
8
9
  export interface EditingPreviewData {
9
10
  key: string;
@@ -1,16 +1,23 @@
1
1
  import { NextApiRequest, NextApiResponse } from 'next';
2
2
  import { AxiosDataFetcher } from '@sitecore-jss/sitecore-jss';
3
- import { EditingData } from './editing-data';
3
+ import { EditMode, LayoutServicePageState } from '@sitecore-jss/sitecore-jss/layout';
4
4
  import { EditingDataService } from './editing-data-service';
5
5
  import { RenderMiddlewareBase } from './render-middleware';
6
- export interface EditingRenderMiddlewareConfig {
6
+ /**
7
+ * Configuration for the Editing Render Middleware.
8
+ */
9
+ export type EditingRenderMiddlewareConfig = {
7
10
  /**
11
+ * -- Edit Mode Chromes --
12
+ *
8
13
  * The `AxiosDataFetcher` instance to use for API requests.
9
14
  * @default new AxiosDataFetcher()
10
15
  * @see AxiosDataFetcher
11
16
  */
12
17
  dataFetcher?: AxiosDataFetcher;
13
18
  /**
19
+ * -- Edit Mode Chromes --
20
+ *
14
21
  * The `EditingDataService` instance to use.
15
22
  * This would typically only be necessary if you've got a custom `EditingDataService` instance (e.g. using a custom API route).
16
23
  * By default, this is `editingDataService` (the `EditingDataService` default instance).
@@ -20,16 +27,25 @@ export interface EditingRenderMiddlewareConfig {
20
27
  */
21
28
  editingDataService?: EditingDataService;
22
29
  /**
30
+ * -- Edit Mode Chromes / Metadata --
31
+ *
23
32
  * Function used to determine route/page URL to render.
24
33
  * This may be necessary for certain custom Next.js routing configurations.
25
- * @param {string} serverUrl The root server URL e.g. 'http://localhost:3000'
34
+ * @param {Object} args Arguments for resolving the page URL
35
+ * @param {string} args.serverUrl The root server URL e.g. 'http://localhost:3000'. Available in Chromes Edit Mode only.
26
36
  * @param {string} itemPath The Sitecore relative item path e.g. '/styleguide'
27
37
  * @returns {string} The URL to render
28
- * @default `${serverUrl}${itemPath}`
38
+ * @default `${serverUrl}${itemPath}` In Edit Mode Chromes
39
+ * @default `${itemPath}` In XMCloud Pages for Edit Mode Metadata
29
40
  * @see resolveServerUrl
30
41
  */
31
- resolvePageUrl?: (serverUrl: string, itemPath: string) => string;
42
+ resolvePageUrl?: (args: {
43
+ serverUrl?: string;
44
+ itemPath: string;
45
+ }) => string;
32
46
  /**
47
+ * -- Edit Mode Chromes --
48
+ *
33
49
  * Function used to determine the root server URL. This is used for the route/page and subsequent data API requests.
34
50
  * By default, the host header is used, with https protocol on Vercel (due to serverless function architecture) and http protocol elsewhere.
35
51
  * @param {NextApiRequest} req The current request.
@@ -37,30 +53,29 @@ export interface EditingRenderMiddlewareConfig {
37
53
  * @see resolvePageUrl
38
54
  */
39
55
  resolveServerUrl?: (req: NextApiRequest) => string;
40
- }
56
+ };
41
57
  /**
42
- * Middleware / handler for use in the editing render Next.js API route (e.g. '/api/editing/render')
43
- * which is required for Sitecore editing support.
58
+ * Configuration for the Editing Chromes Handler.
44
59
  */
45
- export declare class EditingRenderMiddleware extends RenderMiddlewareBase {
60
+ export type EditingRenderMiddlewareChromesConfig = EditingRenderMiddlewareConfig;
61
+ /**
62
+ * Handler for the Editing Chromes POST requests.
63
+ * This handler is responsible for rendering the page and returning the HTML content that is provided via request.
64
+ */
65
+ export declare class ChromesHandler extends RenderMiddlewareBase {
66
+ config?: EditingRenderMiddlewareConfig | undefined;
46
67
  private editingDataService;
47
68
  private dataFetcher;
48
69
  private resolvePageUrl;
49
70
  private resolveServerUrl;
50
- /**
51
- * @param {EditingRenderMiddlewareConfig} [config] Editing render middleware config
52
- */
53
- constructor(config?: EditingRenderMiddlewareConfig);
54
- /**
55
- * Gets the Next.js API route handler
56
- * @returns route handler
57
- */
58
- getHandler(): (req: NextApiRequest, res: NextApiResponse) => Promise<void>;
59
- private handler;
71
+ constructor(config?: EditingRenderMiddlewareConfig | undefined);
72
+ render(req: NextApiRequest, res: NextApiResponse): Promise<void>;
60
73
  /**
61
74
  * Default page URL resolution.
62
- * @param {string} serverUrl
63
- * @param {string} itemPath
75
+ * @param {Object} args Arguments for resolving the page URL
76
+ * @param {string} args.serverUrl The root server URL e.g. 'http://localhost:3000'
77
+ * @param {string} args.itemPath The Sitecore relative item path e.g. '/styleguide'
78
+ * @returns {string} The URL to render
64
79
  */
65
80
  private defaultResolvePageUrl;
66
81
  /**
@@ -74,8 +89,81 @@ export declare class EditingRenderMiddleware extends RenderMiddlewareBase {
74
89
  * @param {NextApiRequest} req
75
90
  */
76
91
  private defaultResolveServerUrl;
92
+ private extractEditingData;
93
+ }
94
+ /**
95
+ * Configuration for the Editing Metadata Handler.
96
+ */
97
+ export type EditingRenderMiddlewareMetadataConfig = Pick<EditingRenderMiddlewareConfig, 'resolvePageUrl'>;
98
+ /**
99
+ * Query parameters appended to the page route URL
100
+ * Appended when XMCloud Pages preview (editing) Metadata Edit Mode is used
101
+ */
102
+ export type MetadataQueryParams = {
103
+ secret: string;
104
+ sc_lang: string;
105
+ sc_itemid: string;
106
+ sc_version: string;
107
+ sc_site: string;
108
+ route: string;
109
+ sc_variant: string;
110
+ mode: Exclude<LayoutServicePageState, 'normal'>;
111
+ };
112
+ /**
113
+ * Next.js API request with Metadata query parameters.
114
+ */
115
+ type MetadataNextApiRequest = NextApiRequest & {
116
+ query: MetadataQueryParams;
117
+ };
118
+ /**
119
+ * Data for Next.js Preview (Editing) Metadata Edit Mode.
120
+ */
121
+ export type EditingMetadataPreviewData = {
122
+ site: string;
123
+ itemId: string;
124
+ language: string;
125
+ variantId: string;
126
+ version: string;
127
+ editMode: EditMode.Metadata;
128
+ pageState: Exclude<LayoutServicePageState, 'Normal'>;
129
+ };
130
+ /**
131
+ * Type guard for EditingMetadataPreviewData
132
+ * @param {Object} data preview data to check
133
+ * @returns true if the data is EditingMetadataPreviewData
134
+ * @see EditingMetadataPreviewData
135
+ */
136
+ export declare const isEditingMetadataPreviewData: (data: unknown) => data is EditingMetadataPreviewData;
137
+ /**
138
+ * Handler for the Editing Metadata GET requests.
139
+ * This handler is responsible for redirecting the request to the page route.
140
+ * The page fetches the layout, dictionary and renders the page.
141
+ */
142
+ export declare class MetadataHandler {
143
+ config: EditingRenderMiddlewareMetadataConfig;
144
+ constructor(config: EditingRenderMiddlewareMetadataConfig);
145
+ render(req: MetadataNextApiRequest, res: NextApiResponse): void;
146
+ /**
147
+ * Gets the Content-Security-Policy header value
148
+ * @returns Content-Security-Policy header value
149
+ */
150
+ getSCPHeader(): string;
77
151
  }
78
152
  /**
79
- * @param {NextApiRequest} req
153
+ * Middleware / handler for use in the editing render Next.js API route (e.g. '/api/editing/render')
154
+ * which is required for Sitecore editing support.
80
155
  */
81
- export declare function extractEditingData(req: NextApiRequest): EditingData;
156
+ export declare class EditingRenderMiddleware extends RenderMiddlewareBase {
157
+ config?: EditingRenderMiddlewareConfig | undefined;
158
+ /**
159
+ * @param {EditingRenderMiddlewareConfig} [config] Editing render middleware config
160
+ */
161
+ constructor(config?: EditingRenderMiddlewareConfig | undefined);
162
+ /**
163
+ * Gets the Next.js API route handler
164
+ * @returns route handler
165
+ */
166
+ getHandler(): (req: NextApiRequest, res: NextApiResponse) => Promise<void>;
167
+ private handler;
168
+ }
169
+ export {};
@@ -1,7 +1,8 @@
1
+ export { GraphQLEditingService } from '@sitecore-jss/sitecore-jss/editing';
1
2
  export { EditingData } from './editing-data';
2
3
  export { EditingDataCache, EditingDataDiskCache } from './editing-data-cache';
3
4
  export { EditingDataMiddleware, EditingDataMiddlewareConfig } from './editing-data-middleware';
4
- export { EditingRenderMiddleware, EditingRenderMiddlewareConfig, } from './editing-render-middleware';
5
+ export { EditingRenderMiddleware, EditingRenderMiddlewareConfig, EditingMetadataPreviewData, isEditingMetadataPreviewData, } from './editing-render-middleware';
5
6
  export { EditingPreviewData, EditingDataService, BasicEditingDataService, BasicEditingDataServiceConfig, ServerlessEditingDataService, ServerlessEditingDataServiceConfig, editingDataService, } from './editing-data-service';
6
7
  export { VercelEditingDataCache } from './vercel-editing-data-cache';
7
8
  export { FEAASRenderMiddleware, FEAASRenderMiddlewareConfig } from './feaas-render-middleware';
@@ -1,2 +1,3 @@
1
1
  export { getPublicUrl, handleEditorFastRefresh } from './utils';
2
- export { tryParseEnvValue, isEditorActive, resetEditorChromes, resolveUrl, } from '@sitecore-jss/sitecore-jss/utils';
2
+ export { tryParseEnvValue, resolveUrl } from '@sitecore-jss/sitecore-jss/utils';
3
+ export { isEditorActive, resetEditorChromes } from '@sitecore-jss/sitecore-jss/editing';