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

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 (55) hide show
  1. package/LICENSE.txt +202 -202
  2. package/README.md +11 -11
  3. package/dist/cjs/components/Link.js +7 -3
  4. package/dist/cjs/components/NextImage.js +16 -6
  5. package/dist/cjs/components/RichText.js +2 -2
  6. package/dist/cjs/editing/constants.js +12 -3
  7. package/dist/cjs/editing/editing-config-middleware.js +8 -0
  8. package/dist/cjs/editing/editing-data-middleware.js +6 -0
  9. package/dist/cjs/editing/editing-render-middleware.js +228 -103
  10. package/dist/cjs/editing/feaas-render-middleware.js +8 -0
  11. package/dist/cjs/editing/index.js +4 -1
  12. package/dist/cjs/editing/render-middleware.js +18 -4
  13. package/dist/cjs/index.js +9 -7
  14. package/dist/cjs/middleware/middleware.js +12 -0
  15. package/dist/cjs/middleware/personalize-middleware.js +85 -25
  16. package/dist/cjs/middleware/redirects-middleware.js +63 -25
  17. package/dist/cjs/services/base-graphql-sitemap-service.js +5 -4
  18. package/dist/cjs/utils/index.js +4 -3
  19. package/dist/cjs/utils/utils.js +3 -3
  20. package/dist/esm/components/Link.js +7 -3
  21. package/dist/esm/components/NextImage.js +17 -6
  22. package/dist/esm/components/RichText.js +2 -2
  23. package/dist/esm/editing/constants.js +11 -2
  24. package/dist/esm/editing/editing-config-middleware.js +9 -1
  25. package/dist/esm/editing/editing-data-middleware.js +7 -1
  26. package/dist/esm/editing/editing-render-middleware.js +226 -103
  27. package/dist/esm/editing/feaas-render-middleware.js +9 -1
  28. package/dist/esm/editing/index.js +2 -1
  29. package/dist/esm/editing/render-middleware.js +19 -5
  30. package/dist/esm/index.js +3 -4
  31. package/dist/esm/middleware/middleware.js +12 -0
  32. package/dist/esm/middleware/personalize-middleware.js +86 -26
  33. package/dist/esm/middleware/redirects-middleware.js +63 -25
  34. package/dist/esm/services/base-graphql-sitemap-service.js +5 -4
  35. package/dist/esm/utils/index.js +2 -1
  36. package/dist/esm/utils/utils.js +1 -1
  37. package/package.json +10 -11
  38. package/types/ComponentBuilder.d.ts +3 -5
  39. package/types/components/Placeholder.d.ts +7 -2
  40. package/types/components/RichText.d.ts +6 -0
  41. package/types/editing/constants.d.ts +11 -2
  42. package/types/editing/editing-config-middleware.d.ts +7 -0
  43. package/types/editing/editing-data-service.d.ts +1 -0
  44. package/types/editing/editing-render-middleware.d.ts +111 -23
  45. package/types/editing/index.d.ts +2 -1
  46. package/types/editing/render-middleware.d.ts +9 -0
  47. package/types/index.d.ts +3 -4
  48. package/types/middleware/middleware.d.ts +6 -0
  49. package/types/middleware/personalize-middleware.d.ts +22 -2
  50. package/types/middleware/redirects-middleware.d.ts +8 -0
  51. package/types/services/base-graphql-sitemap-service.d.ts +3 -2
  52. package/types/utils/index.d.ts +2 -1
  53. package/dist/cjs/components/EditingComponentPlaceholder.js +0 -12
  54. package/dist/esm/components/EditingComponentPlaceholder.js +0 -5
  55. package/types/components/EditingComponentPlaceholder.d.ts +0 -4
@@ -61,6 +61,15 @@ class PersonalizeMiddleware extends middleware_1.MiddlewareBase {
61
61
  sitecore_jss_1.debug.personalize('skipped (no personalization configured)');
62
62
  return response;
63
63
  }
64
+ if (this.isPrefetch(req)) {
65
+ sitecore_jss_1.debug.personalize('skipped (prefetch)');
66
+ // Personalized, but this is a prefetch request.
67
+ // In this case, don't execute a personalize request; otherwise, the metrics for component A/B experiments would be inaccurate.
68
+ // Disable preflight caching to force revalidation on client-side navigation (personalization WILL be influenced).
69
+ // Note the reason we don't move this any earlier in the middleware is that we would then be sacrificing performance for non-personalized pages.
70
+ response.headers.set('x-middleware-cache', 'no-cache');
71
+ return response;
72
+ }
64
73
  yield this.initPersonalizeServer({
65
74
  hostname,
66
75
  siteName: site.name,
@@ -68,32 +77,36 @@ class PersonalizeMiddleware extends middleware_1.MiddlewareBase {
68
77
  response,
69
78
  });
70
79
  const params = this.getExperienceParams(req);
71
- sitecore_jss_1.debug.personalize('executing experience for %s %o', personalizeInfo.contentId, params);
72
- let variantId;
73
- // Execute targeted experience in Personalize SDK
74
- // eslint-disable-next-line no-useless-catch
75
- try {
76
- const personalization = yield this.personalize({ personalizeInfo, params, language, timeout }, req);
77
- variantId = personalization.variantId;
78
- }
79
- catch (error) {
80
- throw error;
81
- }
82
- if (!variantId) {
83
- sitecore_jss_1.debug.personalize('skipped (no variant identified)');
84
- return response;
85
- }
86
- if (!personalizeInfo.variantIds.includes(variantId)) {
87
- sitecore_jss_1.debug.personalize('skipped (invalid variant)');
80
+ const executions = this.getPersonalizeExecutions(personalizeInfo, language);
81
+ const identifiedVariantIds = [];
82
+ yield Promise.all(executions.map((execution) => this.personalize({
83
+ friendlyId: execution.friendlyId,
84
+ variantIds: execution.variantIds,
85
+ params,
86
+ language,
87
+ timeout,
88
+ }, req).then((personalization) => {
89
+ const variantId = personalization.variantId;
90
+ if (variantId) {
91
+ if (!execution.variantIds.includes(variantId)) {
92
+ sitecore_jss_1.debug.personalize('invalid variant %s', variantId);
93
+ }
94
+ else {
95
+ identifiedVariantIds.push(variantId);
96
+ }
97
+ }
98
+ })));
99
+ if (identifiedVariantIds.length === 0) {
100
+ sitecore_jss_1.debug.personalize('skipped (no variant(s) identified)');
88
101
  return response;
89
102
  }
90
103
  // Path can be rewritten by previously executed middleware
91
104
  const basePath = (res === null || res === void 0 ? void 0 : res.headers.get('x-sc-rewrite')) || pathname;
92
105
  // Rewrite to persononalized path
93
- const rewritePath = (0, personalize_1.getPersonalizedRewrite)(basePath, { variantId });
106
+ const rewritePath = (0, personalize_1.getPersonalizedRewrite)(basePath, identifiedVariantIds);
94
107
  response = this.rewrite(rewritePath, req, response);
95
- // Disable preflight caching to force revalidation on client-side navigation (personalization may be influenced)
96
- // See https://github.com/vercel/next.js/issues/32727
108
+ // Disable preflight caching to force revalidation on client-side navigation (personalization MAY be influenced).
109
+ // See https://github.com/vercel/next.js/pull/32767
97
110
  response.headers.set('x-middleware-cache', 'no-cache');
98
111
  sitecore_jss_1.debug.personalize('personalize middleware end in %dms: %o', Date.now() - startTimestamp, {
99
112
  rewritePath,
@@ -132,17 +145,18 @@ class PersonalizeMiddleware extends middleware_1.MiddlewareBase {
132
145
  });
133
146
  });
134
147
  }
135
- personalize({ params, personalizeInfo, language, timeout, }, request) {
148
+ personalize({ params, friendlyId, language, timeout, variantIds, }, request) {
136
149
  var _a;
137
150
  return __awaiter(this, void 0, void 0, function* () {
138
- const personalizationData = {
151
+ sitecore_jss_1.debug.personalize('executing experience for %s %o', friendlyId, params);
152
+ return (yield (0, server_2.personalize)(request, {
139
153
  channel: this.config.cdpConfig.channel || 'WEB',
140
154
  currency: (_a = this.config.cdpConfig.currency) !== null && _a !== void 0 ? _a : 'USD',
141
- friendlyId: personalizeInfo.contentId,
155
+ friendlyId,
142
156
  params,
143
157
  language,
144
- };
145
- return (yield (0, server_2.personalize)(request, personalizationData, { timeout }));
158
+ pageVariantIds: variantIds,
159
+ }, { timeout }));
146
160
  });
147
161
  }
148
162
  getExperienceParams(req) {
@@ -164,5 +178,51 @@ class PersonalizeMiddleware extends middleware_1.MiddlewareBase {
164
178
  // ignore files
165
179
  return pathname.includes('.') || super.excludeRoute(pathname);
166
180
  }
181
+ /**
182
+ * Aggregates personalize executions based on the provided route personalize information and language
183
+ * @param {PersonalizeInfo} personalizeInfo the route personalize information
184
+ * @param {string} language the language
185
+ * @returns An array of personalize executions
186
+ */
187
+ getPersonalizeExecutions(personalizeInfo, language) {
188
+ if (personalizeInfo.variantIds.length === 0) {
189
+ return [];
190
+ }
191
+ const results = [];
192
+ return personalizeInfo.variantIds.reduce((results, variantId) => {
193
+ if (variantId.includes('_')) {
194
+ // Component-level personalization in format "<ComponentID>_<VariantID>"
195
+ const componentId = variantId.split('_')[0];
196
+ const friendlyId = personalize_1.CdpHelper.getComponentFriendlyId(personalizeInfo.pageId, componentId, language, this.config.scope || this.config.edgeConfig.scope);
197
+ const execution = results.find((x) => x.friendlyId === friendlyId);
198
+ if (execution) {
199
+ execution.variantIds.push(variantId);
200
+ }
201
+ else {
202
+ // The default/control variant (format "<ComponentID>_default") is also a valid value returned by the execution
203
+ const defaultVariant = `${componentId}${personalize_1.DEFAULT_VARIANT}`;
204
+ results.push({
205
+ friendlyId,
206
+ variantIds: [defaultVariant, variantId],
207
+ });
208
+ }
209
+ }
210
+ else {
211
+ // Embedded (page-level) personalization in format "<VariantID>"
212
+ const friendlyId = personalize_1.CdpHelper.getPageFriendlyId(personalizeInfo.pageId, language, this.config.scope || this.config.edgeConfig.scope);
213
+ const execution = results.find((x) => x.friendlyId === friendlyId);
214
+ if (execution) {
215
+ execution.variantIds.push(variantId);
216
+ }
217
+ else {
218
+ results.push({
219
+ friendlyId,
220
+ variantIds: [variantId],
221
+ });
222
+ }
223
+ }
224
+ return results;
225
+ }, results);
226
+ }
167
227
  }
168
228
  exports.PersonalizeMiddleware = PersonalizeMiddleware;
@@ -43,6 +43,7 @@ class RedirectsMiddleware extends middleware_1.MiddlewareBase {
43
43
  hostname,
44
44
  });
45
45
  const createResponse = () => __awaiter(this, void 0, void 0, function* () {
46
+ var _a;
46
47
  if (this.config.disabled && this.config.disabled(req, res || server_1.NextResponse.next())) {
47
48
  sitecore_jss_1.debug.redirects('skipped (redirects middleware is disabled)');
48
49
  return res || server_1.NextResponse.next();
@@ -64,47 +65,44 @@ class RedirectsMiddleware extends middleware_1.MiddlewareBase {
64
65
  existsRedirect.target.includes(hostname))) {
65
66
  existsRedirect.target = existsRedirect.target.replace(REGEXP_CONTEXT_SITE_LANG, site.language);
66
67
  }
67
- const url = req.nextUrl.clone();
68
+ const url = this.normalizeUrl(req.nextUrl.clone());
68
69
  if (REGEXP_ABSOLUTE_URL.test(existsRedirect.target)) {
69
70
  url.href = existsRedirect.target;
70
71
  }
71
72
  else {
72
- const source = `${url.pathname}${url.search}`;
73
- url.search = existsRedirect.isQueryStringPreserved ? url.search : '';
73
+ const source = `${url.pathname.replace(/\/*$/gi, '')}${url.search}`;
74
74
  const urlFirstPart = existsRedirect.target.split('/')[1];
75
75
  if (this.locales.includes(urlFirstPart)) {
76
- url.locale = urlFirstPart;
76
+ req.nextUrl.locale = urlFirstPart;
77
77
  existsRedirect.target = existsRedirect.target.replace(`/${urlFirstPart}`, '');
78
78
  }
79
79
  const target = source
80
80
  .replace((0, regex_parser_1.default)(existsRedirect.pattern), existsRedirect.target)
81
81
  .replace(/^\/\//, '/')
82
82
  .split('?');
83
- url.pathname = target[0];
84
- if (target[1]) {
85
- const newParams = new URLSearchParams(target[1]);
86
- for (const [key, val] of newParams.entries()) {
87
- url.searchParams.append(key, val);
88
- }
83
+ if (url.search && existsRedirect.isQueryStringPreserved) {
84
+ const targetQueryString = (_a = target[1]) !== null && _a !== void 0 ? _a : '';
85
+ url.search = '?' + new URLSearchParams(`${url.search}&${targetQueryString}`).toString();
89
86
  }
87
+ else if (target[1]) {
88
+ url.search = '?' + target[1];
89
+ }
90
+ else {
91
+ url.search = '';
92
+ }
93
+ const prepareNewURL = new URL(`${target[0]}${url.search}`, url.origin);
94
+ url.href = prepareNewURL.href;
90
95
  }
91
96
  const redirectUrl = decodeURIComponent(url.href);
92
97
  /** return Response redirect with http code of redirect type **/
93
98
  switch (existsRedirect.redirectType) {
94
99
  case site_1.REDIRECT_TYPE_301:
95
- return server_1.NextResponse.redirect(redirectUrl, {
96
- status: 301,
97
- statusText: 'Moved Permanently',
98
- headers: res === null || res === void 0 ? void 0 : res.headers,
99
- });
100
+ return server_1.NextResponse.redirect(redirectUrl, Object.assign(Object.assign({}, res), { status: 301, statusText: 'Moved Permanently', headers: res === null || res === void 0 ? void 0 : res.headers }));
100
101
  case site_1.REDIRECT_TYPE_302:
101
- return server_1.NextResponse.redirect(redirectUrl, {
102
- status: 302,
103
- statusText: 'Found',
104
- headers: res === null || res === void 0 ? void 0 : res.headers,
105
- });
106
- case site_1.REDIRECT_TYPE_SERVER_TRANSFER:
107
- return server_1.NextResponse.rewrite(redirectUrl, res);
102
+ return server_1.NextResponse.redirect(redirectUrl, Object.assign(Object.assign({}, res), { status: 302, statusText: 'Found', headers: res === null || res === void 0 ? void 0 : res.headers }));
103
+ case site_1.REDIRECT_TYPE_SERVER_TRANSFER: {
104
+ return this.rewrite(redirectUrl, req, res || server_1.NextResponse.next());
105
+ }
108
106
  default:
109
107
  return res || server_1.NextResponse.next();
110
108
  }
@@ -149,8 +147,9 @@ class RedirectsMiddleware extends middleware_1.MiddlewareBase {
149
147
  getExistsRedirect(req, siteName) {
150
148
  return __awaiter(this, void 0, void 0, function* () {
151
149
  const redirects = yield this.redirectsService.fetchRedirects(siteName);
152
- const tragetURL = req.nextUrl.pathname;
153
- const targetQS = req.nextUrl.search || '';
150
+ const normalizedUrl = this.normalizeUrl(req.nextUrl.clone());
151
+ const tragetURL = normalizedUrl.pathname;
152
+ const targetQS = normalizedUrl.search || '';
154
153
  const language = this.getLanguage(req);
155
154
  const modifyRedirects = structuredClone(redirects);
156
155
  return modifyRedirects.length
@@ -163,7 +162,7 @@ class RedirectsMiddleware extends middleware_1.MiddlewareBase {
163
162
  .replace(/(?<!\\)\?/g, '\\?')
164
163
  .replace(/\$\/gi$/g, '')}[\/]?$/gi`;
165
164
  return (((0, regex_parser_1.default)(redirect.pattern).test(tragetURL) ||
166
- (0, regex_parser_1.default)(redirect.pattern).test(`${tragetURL}${targetQS}`) ||
165
+ (0, regex_parser_1.default)(redirect.pattern).test(`${tragetURL.replace(/\/*$/gi, '')}${targetQS}`) ||
167
166
  (0, regex_parser_1.default)(redirect.pattern).test(`/${req.nextUrl.locale}${tragetURL}`) ||
168
167
  (0, regex_parser_1.default)(redirect.pattern).test(`/${req.nextUrl.locale}${tragetURL}${targetQS}`)) &&
169
168
  (redirect.locale
@@ -173,5 +172,44 @@ class RedirectsMiddleware extends middleware_1.MiddlewareBase {
173
172
  : undefined;
174
173
  });
175
174
  }
175
+ /**
176
+ * When a user clicks on a link generated by the Link component from next/link,
177
+ * Next.js adds special parameters in the route called path.
178
+ * This method removes these special parameters.
179
+ * @param {URL} url
180
+ * @returns {string} normalize url
181
+ */
182
+ normalizeUrl(url) {
183
+ if (!url.search) {
184
+ return url;
185
+ }
186
+ /**
187
+ * Prepare special parameters for exclusion.
188
+ */
189
+ const splittedPathname = url.pathname
190
+ .split('/')
191
+ .filter((route) => route)
192
+ .map((route) => `path=${route}`);
193
+ /**
194
+ * Remove special parameters(Next.JS)
195
+ * Example: /about/contact/us
196
+ * When a user clicks on this link, Next.js should generate a link for the middleware, formatted like this:
197
+ * http://host/about/contact/us?path=about&path=contact&path=us
198
+ */
199
+ const newQueryString = url.search
200
+ .replace(/^\?/, '')
201
+ .split('&')
202
+ .filter((param) => {
203
+ if (!splittedPathname.includes(param)) {
204
+ return param;
205
+ }
206
+ return false;
207
+ })
208
+ .join('&');
209
+ if (newQueryString) {
210
+ return new URL(`${url.pathname}?${newQueryString}`, url.origin);
211
+ }
212
+ return new URL(`${url.pathname}`, url.origin);
213
+ }
176
214
  }
177
215
  exports.RedirectsMiddleware = RedirectsMiddleware;
@@ -148,13 +148,14 @@ class BaseGraphQLSitemapService {
148
148
  const formatPath = (path) => formatStaticPath(path.replace(/^\/|\/$/g, '').split('/'), language);
149
149
  const aggregatedPaths = [];
150
150
  sitePaths.forEach((item) => {
151
- var _a, _b, _c, _d;
151
+ var _a, _b, _c;
152
152
  if (!item)
153
153
  return;
154
154
  aggregatedPaths.push(formatPath(item.path));
155
- // check for type safety's sake - personalize may be empty depending on query type
156
- if ((_b = (_a = item.route) === null || _a === void 0 ? void 0 : _a.personalization) === null || _b === void 0 ? void 0 : _b.variantIds.length) {
157
- aggregatedPaths.push(...(((_d = (_c = item.route) === null || _c === void 0 ? void 0 : _c.personalization) === null || _d === void 0 ? void 0 : _d.variantIds.map((varId) => formatPath((0, personalize_1.getPersonalizedRewrite)(item.path, { variantId: varId })))) || {}));
155
+ const variantIds = (_c = (_b = (_a = item.route) === null || _a === void 0 ? void 0 : _a.personalization) === null || _b === void 0 ? void 0 : _b.variantIds) === null || _c === void 0 ? void 0 : _c.filter((variantId) => !variantId.includes('_') // exclude component A/B test variants
156
+ );
157
+ if (variantIds === null || variantIds === void 0 ? void 0 : variantIds.length) {
158
+ aggregatedPaths.push(...variantIds.map((varId) => formatPath((0, personalize_1.getPersonalizedRewrite)(item.path, [varId]))));
158
159
  }
159
160
  });
160
161
  return aggregatedPaths;
@@ -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
  };
@@ -16,14 +16,18 @@ import { Link as ReactLink, LinkPropTypes, } from '@sitecore-jss/sitecore-jss-re
16
16
  export const Link = forwardRef((props, ref) => {
17
17
  const { field, editable = true, children, internalLinkMatcher = /^\//g, showLinkTextWithChildrenPresent } = props, htmlLinkProps = __rest(props, ["field", "editable", "children", "internalLinkMatcher", "showLinkTextWithChildrenPresent"]);
18
18
  if (!field ||
19
- (!field.editable && !field.value && !field.href)) {
19
+ (!field.editable &&
20
+ !field.value &&
21
+ !field.href &&
22
+ !field.metadata)) {
20
23
  return null;
21
24
  }
22
25
  const value = (field.href
23
26
  ? field
24
27
  : field.value);
25
- const { href, querystring, anchor } = value;
26
- const isEditing = editable && field.editable;
28
+ // fallback to {} if value is undefined; could happen if field is LinkFieldValue, href is empty in metadata mode
29
+ const { href, querystring, anchor } = value || {};
30
+ const isEditing = editable && (field.editable || field.metadata);
27
31
  if (href && !isEditing) {
28
32
  const text = showLinkTextWithChildrenPresent || !children ? value.text || value.href : null;
29
33
  // determine if a link is a route or not.
@@ -12,18 +12,22 @@ var __rest = (this && this.__rest) || function (s, e) {
12
12
  import { mediaApi } from '@sitecore-jss/sitecore-jss/media';
13
13
  import PropTypes from 'prop-types';
14
14
  import React from 'react';
15
- import { getEEMarkup, } from '@sitecore-jss/sitecore-jss-react';
15
+ import { getEEMarkup, withFieldMetadata, SitecoreContextReactContext, } from '@sitecore-jss/sitecore-jss-react';
16
16
  import Image from 'next/image';
17
- export const NextImage = (_a) => {
17
+ import { withEmptyFieldEditingComponent } from '@sitecore-jss/sitecore-jss-react';
18
+ import { DefaultEmptyFieldEditingComponentImage } from '@sitecore-jss/sitecore-jss-react';
19
+ import { isFieldValueEmpty, LayoutServicePageState } from '@sitecore-jss/sitecore-jss/layout';
20
+ export const NextImage = withFieldMetadata(withEmptyFieldEditingComponent((_a) => {
21
+ var _b;
18
22
  var { editable = true, imageParams, field, mediaUrlPrefix, fill, priority } = _a, otherProps = __rest(_a, ["editable", "imageParams", "field", "mediaUrlPrefix", "fill", "priority"]);
23
+ const sitecoreContext = React.useContext(SitecoreContextReactContext);
19
24
  // next handles src and we use a custom loader,
20
25
  // throw error if these are present
21
26
  if (otherProps.src) {
22
27
  throw new Error('Detected src prop. If you wish to use src, use next/image directly.');
23
28
  }
24
29
  const dynamicMedia = field;
25
- if (!field ||
26
- (!dynamicMedia.editable && !dynamicMedia.value && !dynamicMedia.src)) {
30
+ if (!field || (!dynamicMedia.editable && isFieldValueEmpty(dynamicMedia))) {
27
31
  return null;
28
32
  }
29
33
  const imageField = dynamicMedia;
@@ -38,8 +42,11 @@ export const NextImage = (_a) => {
38
42
  if (!img) {
39
43
  return null;
40
44
  }
45
+ // disable image optimization for Edit and Preview, but preserve original value if true
46
+ const unoptimized = otherProps.unoptimized ||
47
+ ((_b = sitecoreContext.context) === null || _b === void 0 ? void 0 : _b.pageState) !== LayoutServicePageState.Normal;
41
48
  const attrs = Object.assign(Object.assign(Object.assign({}, img), otherProps), { fill,
42
- priority, src: mediaApi.updateImageUrl(img.src, imageParams, mediaUrlPrefix) });
49
+ priority, src: mediaApi.updateImageUrl(img.src, imageParams, mediaUrlPrefix), unoptimized });
43
50
  const imageProps = Object.assign(Object.assign({}, attrs), {
44
51
  // force replace /media with /jssmedia in src since we _know_ we will be adding a 'mw' query string parameter
45
52
  // this is required for Sitecore media API resizing to work properly
@@ -53,7 +60,7 @@ export const NextImage = (_a) => {
53
60
  return React.createElement(Image, Object.assign({ alt: "" }, imageProps));
54
61
  }
55
62
  return null; // we can't handle the truth
56
- };
63
+ }, { defaultEmptyFieldEditingComponent: DefaultEmptyFieldEditingComponentImage }));
57
64
  NextImage.propTypes = {
58
65
  field: PropTypes.oneOfType([
59
66
  PropTypes.shape({
@@ -67,5 +74,9 @@ NextImage.propTypes = {
67
74
  editable: PropTypes.bool,
68
75
  mediaUrlPrefix: PropTypes.instanceOf(RegExp),
69
76
  imageParams: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.number.isRequired, PropTypes.string.isRequired]).isRequired),
77
+ emptyFieldEditingComponent: PropTypes.oneOfType([
78
+ PropTypes.object,
79
+ PropTypes.func,
80
+ ]),
70
81
  };
71
82
  NextImage.displayName = 'NextImage';
@@ -17,7 +17,7 @@ const prefetched = {};
17
17
  export const RichText = (props) => {
18
18
  const { internalLinksSelector = 'a[href^="/"]', prefetchLinks = true, editable = true } = props, rest = __rest(props, ["internalLinksSelector", "prefetchLinks", "editable"]);
19
19
  const hasText = props.field && props.field.value;
20
- const isEditing = editable && props.field && props.field.editable;
20
+ const isEditing = editable && props.field && (props.field.editable || props.field.metadata);
21
21
  const router = useRouter();
22
22
  const richTextRef = useRef(null);
23
23
  useEffect(() => {
@@ -49,7 +49,7 @@ export const RichText = (props) => {
49
49
  link.addEventListener('click', routeHandler, false);
50
50
  });
51
51
  };
52
- return React.createElement(ReactRichText, Object.assign({ ref: richTextRef }, rest));
52
+ return React.createElement(ReactRichText, Object.assign({ ref: richTextRef, editable: editable }, rest));
53
53
  };
54
54
  RichText.propTypes = Object.assign({ internalLinksSelector: PropTypes.string }, RichTextPropTypes);
55
55
  RichText.displayName = 'NextRichText';
@@ -1,3 +1,12 @@
1
1
  export const QUERY_PARAM_EDITING_SECRET = 'secret';
2
- export const QUERY_PARAM_PROTECTION_BYPASS_SITECORE = 'x-sitecore-protection-bypass';
3
- export const QUERY_PARAM_PROTECTION_BYPASS_VERCEL = 'x-vercel-protection-bypass';
2
+ export const QUERY_PARAM_VERCEL_PROTECTION_BYPASS = 'x-vercel-protection-bypass';
3
+ export const QUERY_PARAM_VERCEL_SET_BYPASS_COOKIE = 'x-vercel-set-bypass-cookie';
4
+ /**
5
+ * Headers that should be passed along to (Editing Chromes handler) SSR request.
6
+ * Note these are in lowercase format to match expected `IncomingHttpHeaders`.
7
+ */
8
+ export const EDITING_PASS_THROUGH_HEADERS = ['authorization', 'cookie'];
9
+ /**
10
+ * Default allowed origins for editing requests. This is used to enforce CORS, CSP headers.
11
+ */
12
+ export const EDITING_ALLOWED_ORIGINS = ['https://pages.sitecorecloud.io'];
@@ -7,9 +7,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { QUERY_PARAM_EDITING_SECRET } from './constants';
10
+ import { EDITING_ALLOWED_ORIGINS, QUERY_PARAM_EDITING_SECRET } from './constants';
11
11
  import { getJssEditingSecret } from '../utils/utils';
12
12
  import { debug } from '@sitecore-jss/sitecore-jss';
13
+ import { EditMode } from '@sitecore-jss/sitecore-jss/layout';
14
+ import { enforceCors } from '@sitecore-jss/sitecore-jss/utils';
13
15
  /**
14
16
  * Middleware / handler used in the editing config API route in xmcloud add on (e.g. '/api/editing/config')
15
17
  * provides configuration information to determine feature compatibility on Pages side.
@@ -22,6 +24,10 @@ export class EditingConfigMiddleware {
22
24
  this.config = config;
23
25
  this.handler = (_req, res) => __awaiter(this, void 0, void 0, function* () {
24
26
  const secret = _req.query[QUERY_PARAM_EDITING_SECRET];
27
+ if (!enforceCors(_req, res, EDITING_ALLOWED_ORIGINS)) {
28
+ debug.editing('invalid origin host - set allowed origins in JSS_ALLOWED_ORIGINS environment variable');
29
+ return res.status(401).json({ message: 'Invalid origin' });
30
+ }
25
31
  if (secret !== getJssEditingSecret()) {
26
32
  debug.editing('invalid editing secret - sent "%s" expected "%s"', secret, getJssEditingSecret());
27
33
  return res.status(401).json({ message: 'Missing or invalid editing secret' });
@@ -29,9 +35,11 @@ export class EditingConfigMiddleware {
29
35
  const components = Array.isArray(this.config.components)
30
36
  ? this.config.components
31
37
  : Array.from(this.config.components.keys());
38
+ const editMode = this.config.pagesEditMode || EditMode.Metadata;
32
39
  return res.status(200).json({
33
40
  components,
34
41
  packages: this.config.metadata.packages,
42
+ editMode,
35
43
  });
36
44
  });
37
45
  }
@@ -9,8 +9,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import { editingDataDiskCache } from './editing-data-cache';
11
11
  import { isEditingData } from './editing-data';
12
- import { QUERY_PARAM_EDITING_SECRET } from './constants';
12
+ import { EDITING_ALLOWED_ORIGINS, QUERY_PARAM_EDITING_SECRET } from './constants';
13
13
  import { getJssEditingSecret } from '../utils/utils';
14
+ import { enforceCors } from '@sitecore-jss/sitecore-jss/utils';
15
+ import { debug } from '@sitecore-jss/sitecore-jss';
14
16
  /**
15
17
  * Middleware / handler for use in the editing data Next.js API dynamic route (e.g. '/api/editing/data/[key]')
16
18
  * which is required for Sitecore editing support.
@@ -25,6 +27,10 @@ export class EditingDataMiddleware {
25
27
  const { method, query, body } = req;
26
28
  const secret = query[QUERY_PARAM_EDITING_SECRET];
27
29
  const key = query[this.queryParamKey];
30
+ if (!enforceCors(req, res, EDITING_ALLOWED_ORIGINS)) {
31
+ debug.editing('invalid origin host - set allowed origins in JSS_ALLOWED_ORIGINS environment variable');
32
+ return res.status(401).json({ message: 'Invalid origin' });
33
+ }
28
34
  // Validate secret
29
35
  if (secret !== getJssEditingSecret()) {
30
36
  res.status(401).end('Missing or invalid secret');