@sitecore-content-sdk/nextjs 0.1.0-beta.9 → 0.1.0-canary.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.
- package/README.md +5 -4
- package/client.d.ts +1 -0
- package/client.js +1 -0
- package/component-props-loader.d.ts +1 -0
- package/component-props-loader.js +3 -0
- package/dist/cjs/client/index.js +10 -0
- package/dist/cjs/client/sitecore-nextjs-client.js +117 -0
- package/dist/cjs/components/RichText.js +16 -4
- package/dist/cjs/config/define-cli-config.js +30 -0
- package/dist/cjs/config/index.js +3 -1
- package/dist/cjs/editing/editing-config-middleware.js +1 -3
- package/dist/cjs/editing/editing-render-middleware.js +13 -9
- package/dist/cjs/editing/index.js +2 -2
- package/dist/cjs/index.js +16 -18
- package/dist/cjs/middleware/index.js +3 -1
- package/dist/cjs/middleware/middleware.js +23 -12
- package/dist/cjs/middleware/multisite-middleware.js +19 -8
- package/dist/cjs/middleware/personalize-middleware.js +3 -3
- package/dist/cjs/middleware/redirects-middleware.js +28 -13
- package/dist/cjs/middleware/sitemap-middleware.js +47 -0
- package/dist/cjs/services/component-props-service.js +41 -50
- package/dist/cjs/tools/component-props.loader.js +101 -0
- package/dist/cjs/tools/index.js +9 -0
- package/dist/cjs/tools/templating/byoc-component.js +75 -0
- package/dist/cjs/tools/templating/constants.js +7 -0
- package/dist/cjs/tools/templating/default-component.js +54 -0
- package/dist/cjs/tools/templating/utils.js +18 -0
- package/dist/cjs/utils/index.js +2 -1
- package/dist/cjs/utils/utils.js +10 -1
- package/dist/esm/client/index.js +2 -0
- package/dist/esm/client/sitecore-nextjs-client.js +113 -0
- package/dist/esm/components/RichText.js +14 -2
- package/dist/esm/config/define-cli-config.js +26 -0
- package/dist/esm/config/index.js +1 -0
- package/dist/esm/editing/editing-config-middleware.js +1 -3
- package/dist/esm/editing/editing-render-middleware.js +11 -7
- package/dist/esm/editing/index.js +1 -1
- package/dist/esm/index.js +3 -5
- package/dist/esm/middleware/index.js +1 -0
- package/dist/esm/middleware/middleware.js +23 -12
- package/dist/esm/middleware/multisite-middleware.js +20 -9
- package/dist/esm/middleware/personalize-middleware.js +4 -4
- package/dist/esm/middleware/redirects-middleware.js +29 -14
- package/dist/esm/middleware/sitemap-middleware.js +43 -0
- package/dist/esm/services/component-props-service.js +41 -50
- package/dist/esm/tools/component-props.loader.js +65 -0
- package/dist/esm/tools/index.js +1 -0
- package/dist/esm/tools/templating/byoc-component.js +69 -0
- package/dist/esm/tools/templating/constants.js +4 -0
- package/dist/esm/tools/templating/default-component.js +48 -0
- package/dist/esm/tools/templating/utils.js +12 -0
- package/dist/esm/utils/index.js +1 -1
- package/dist/esm/utils/utils.js +8 -0
- package/package.json +16 -13
- package/tools.d.ts +1 -0
- package/tools.js +1 -0
- package/types/client/index.d.ts +2 -0
- package/types/client/sitecore-nextjs-client.d.ts +44 -0
- package/types/components/RichText.d.ts +7 -1
- package/types/config/define-cli-config.d.ts +8 -0
- package/types/config/index.d.ts +1 -0
- package/types/editing/editing-config-middleware.d.ts +4 -2
- package/types/editing/editing-render-middleware.d.ts +3 -31
- package/types/editing/index.d.ts +1 -1
- package/types/index.d.ts +6 -8
- package/types/middleware/index.d.ts +1 -0
- package/types/middleware/middleware.d.ts +13 -7
- package/types/middleware/sitemap-middleware.d.ts +12 -0
- package/types/services/component-props-service.d.ts +10 -26
- package/types/sharedTypes/component-props.d.ts +18 -0
- package/types/sharedTypes/sitecore-page-props.d.ts +5 -0
- package/types/tools/component-props.loader.d.ts +7 -0
- package/types/tools/index.d.ts +1 -0
- package/types/tools/templating/byoc-component.d.ts +2 -0
- package/types/tools/templating/constants.d.ts +4 -0
- package/types/tools/templating/default-component.d.ts +2 -0
- package/types/tools/templating/utils.d.ts +6 -0
- package/types/utils/index.d.ts +1 -1
- package/types/utils/utils.d.ts +2 -0
- package/dist/cjs/ComponentBuilder.js +0 -63
- package/dist/cjs/graphql/index.js +0 -7
- package/dist/cjs/services/base-graphql-sitemap-service.js +0 -206
- package/dist/cjs/services/graphql-sitemap-service.js +0 -64
- package/dist/cjs/services/mutisite-graphql-sitemap-service.js +0 -81
- package/dist/esm/ComponentBuilder.js +0 -59
- package/dist/esm/graphql/index.js +0 -1
- package/dist/esm/services/base-graphql-sitemap-service.js +0 -201
- package/dist/esm/services/graphql-sitemap-service.js +0 -59
- package/dist/esm/services/mutisite-graphql-sitemap-service.js +0 -77
- package/graphql.d.ts +0 -1
- package/graphql.js +0 -1
- package/types/ComponentBuilder.d.ts +0 -59
- package/types/graphql/index.d.ts +0 -1
- package/types/services/base-graphql-sitemap-service.d.ts +0 -148
- package/types/services/graphql-sitemap-service.d.ts +0 -51
- package/types/services/mutisite-graphql-sitemap-service.d.ts +0 -42
- package/types/sharedTypes/module-factory.d.ts +0 -32
- /package/dist/cjs/sharedTypes/{module-factory.js → sitecore-page-props.js} +0 -0
- /package/dist/esm/sharedTypes/{module-factory.js → sitecore-page-props.js} +0 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { SitecoreClient, } from '@sitecore-content-sdk/core/client';
|
|
11
|
+
import { ComponentPropsService } from '../services/component-props-service';
|
|
12
|
+
import { getSiteRewriteData, normalizeSiteRewrite } from '@sitecore-content-sdk/core/site';
|
|
13
|
+
import { getPersonalizedRewriteData, normalizePersonalizedRewrite, } from '@sitecore-content-sdk/core/personalize';
|
|
14
|
+
export class SitecoreNextjsClient extends SitecoreClient {
|
|
15
|
+
constructor(initOptions) {
|
|
16
|
+
super(initOptions);
|
|
17
|
+
this.initOptions = initOptions;
|
|
18
|
+
this.componentPropsService = this.getComponentPropsService();
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Resolves site based on the provided path
|
|
22
|
+
* @param {string | string[]} path path to resolve site from
|
|
23
|
+
* @returns resolved site, or default site info if not found
|
|
24
|
+
*/
|
|
25
|
+
resolveSiteFromPath(path) {
|
|
26
|
+
const resolvedPath = super.parsePath(path);
|
|
27
|
+
// Get site name (from path rewritten in middleware)
|
|
28
|
+
const siteData = getSiteRewriteData(resolvedPath, this.initOptions.defaultSite);
|
|
29
|
+
// Resolve site by name
|
|
30
|
+
return (this.siteResolver.getByName(siteData.siteName) || {
|
|
31
|
+
name: siteData.siteName,
|
|
32
|
+
hostName: '',
|
|
33
|
+
language: '',
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Normalizes a nextjs path that could have been rewritten
|
|
38
|
+
* @param {string | string[]} path nextjs path
|
|
39
|
+
* @returns path string without nextjs prefixes
|
|
40
|
+
*/
|
|
41
|
+
parsePath(path) {
|
|
42
|
+
const basePath = super.parsePath(path);
|
|
43
|
+
return normalizeSiteRewrite(normalizePersonalizedRewrite(basePath));
|
|
44
|
+
}
|
|
45
|
+
getPage(path, pageOptions, options) {
|
|
46
|
+
const _super = Object.create(null, {
|
|
47
|
+
parsePath: { get: () => super.parsePath },
|
|
48
|
+
getPage: { get: () => super.getPage }
|
|
49
|
+
});
|
|
50
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
51
|
+
var _a;
|
|
52
|
+
const resolvedPath = this.parsePath(path);
|
|
53
|
+
// Get variant(s) for personalization (from path), must ensure path is of type string
|
|
54
|
+
const personalizeData = pageOptions.personalize || getPersonalizedRewriteData(_super.parsePath.call(this, path));
|
|
55
|
+
const site = pageOptions.site || ((_a = this.resolveSiteFromPath(path)) === null || _a === void 0 ? void 0 : _a.name);
|
|
56
|
+
const page = yield _super.getPage.call(this, resolvedPath, {
|
|
57
|
+
locale: pageOptions.locale,
|
|
58
|
+
site,
|
|
59
|
+
personalize: personalizeData,
|
|
60
|
+
}, options);
|
|
61
|
+
return page;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Retrieves preview page and layout details
|
|
66
|
+
* @param {PreviewData} previewData - The editing preview data for metadata mode.
|
|
67
|
+
* @param {FetchOptions} [fetchOptions] Additional fetch fetch options to override GraphQL requests (like retries and fetch)
|
|
68
|
+
*/
|
|
69
|
+
getPreview(previewData, fetchOptions) {
|
|
70
|
+
const _super = Object.create(null, {
|
|
71
|
+
getPreview: { get: () => super.getPreview }
|
|
72
|
+
});
|
|
73
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
74
|
+
return _super.getPreview.call(this, previewData, fetchOptions);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Parses components from nextjs component map and layoutData, executes getServerProps/getStaticProps methods
|
|
79
|
+
* and returns resulting props from components
|
|
80
|
+
* @param {LayoutServiceData} layoutData layout data to parse compnents from
|
|
81
|
+
* @param {PreviewData} context Nextjs preview data
|
|
82
|
+
* @param {ComponentMap<NextjsJssComponent>} components component map to get props for
|
|
83
|
+
* @returns {ComponentPropsCollection} component props
|
|
84
|
+
*/
|
|
85
|
+
getComponentData(layoutData, context, components) {
|
|
86
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
87
|
+
let componentProps = {};
|
|
88
|
+
if (!layoutData.sitecore.route)
|
|
89
|
+
return componentProps;
|
|
90
|
+
// Retrieve component props using side-effects defined on components level
|
|
91
|
+
componentProps = yield this.componentPropsService.fetchComponentProps({
|
|
92
|
+
layoutData: layoutData,
|
|
93
|
+
context,
|
|
94
|
+
components,
|
|
95
|
+
});
|
|
96
|
+
const errors = Object.keys(componentProps)
|
|
97
|
+
.map((id) => {
|
|
98
|
+
const component = componentProps[id];
|
|
99
|
+
return component.error
|
|
100
|
+
? `\nUnable to get component props for ${component.componentName} (${id}): ${component.error}`
|
|
101
|
+
: '';
|
|
102
|
+
})
|
|
103
|
+
.join('');
|
|
104
|
+
if (errors.length) {
|
|
105
|
+
throw new Error(errors);
|
|
106
|
+
}
|
|
107
|
+
return componentProps;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
getComponentPropsService() {
|
|
111
|
+
return new ComponentPropsService();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -13,7 +13,7 @@ import React, { useEffect, useRef } from 'react';
|
|
|
13
13
|
import PropTypes from 'prop-types';
|
|
14
14
|
import { useRouter } from 'next/router';
|
|
15
15
|
import { RichText as ReactRichText, RichTextPropTypes, } from '@sitecore-content-sdk/react';
|
|
16
|
-
const prefetched = {};
|
|
16
|
+
export 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;
|
|
@@ -42,9 +42,21 @@ export const RichText = (props) => {
|
|
|
42
42
|
internalLinks.forEach((link) => {
|
|
43
43
|
if (link.target === '_blank')
|
|
44
44
|
return;
|
|
45
|
-
|
|
45
|
+
const prefetch = () => {
|
|
46
46
|
router.prefetch(link.pathname, undefined, { locale: false });
|
|
47
47
|
prefetched[link.pathname] = true;
|
|
48
|
+
};
|
|
49
|
+
if (!prefetched[link.pathname] && prefetchLinks !== false) {
|
|
50
|
+
if (prefetchLinks === true) {
|
|
51
|
+
prefetch();
|
|
52
|
+
}
|
|
53
|
+
if (prefetchLinks === 'hover') {
|
|
54
|
+
const mouseOverHandler = () => {
|
|
55
|
+
prefetch();
|
|
56
|
+
link.removeEventListener('mouseover', mouseOverHandler);
|
|
57
|
+
};
|
|
58
|
+
link.addEventListener('mouseover', mouseOverHandler, false);
|
|
59
|
+
}
|
|
48
60
|
}
|
|
49
61
|
link.addEventListener('click', routeHandler, false);
|
|
50
62
|
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { defineCliConfig as defineCliConfigCore, } from '@sitecore-content-sdk/core/config';
|
|
2
|
+
import { byocTemplate } from '../tools/templating/byoc-component';
|
|
3
|
+
import { defaultTemplate } from '../tools/templating/default-component';
|
|
4
|
+
/**
|
|
5
|
+
* Accepts a `SitecoreCliConfigInput` object and returns the Sitecore Content SDK CLI configuration from the specified file,
|
|
6
|
+
* updated with the required default values.
|
|
7
|
+
* @param {SitecoreCliConfigInput} cliConfig the cli configuration provided by the application
|
|
8
|
+
* @returns {SitecoreCliConfig} full sitecore cli configuration to use with cli
|
|
9
|
+
*/
|
|
10
|
+
export const defineCliConfig = (cliConfig) => {
|
|
11
|
+
addDefaultScaffoldTemplates(cliConfig);
|
|
12
|
+
return defineCliConfigCore(cliConfig);
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Adds default scaffold templates to the CLI configuration.
|
|
16
|
+
* @param {SitecoreCliConfigInput} cliConfig - The CLI configuration object
|
|
17
|
+
*/
|
|
18
|
+
function addDefaultScaffoldTemplates(cliConfig) {
|
|
19
|
+
if (!cliConfig.scaffold) {
|
|
20
|
+
cliConfig.scaffold = {};
|
|
21
|
+
}
|
|
22
|
+
if (!cliConfig.scaffold.templates) {
|
|
23
|
+
cliConfig.scaffold.templates = [];
|
|
24
|
+
}
|
|
25
|
+
cliConfig.scaffold.templates.unshift(defaultTemplate, byocTemplate);
|
|
26
|
+
}
|
package/dist/esm/config/index.js
CHANGED
|
@@ -38,9 +38,7 @@ export class EditingConfigMiddleware {
|
|
|
38
38
|
// CORS headers are set by enforceCors
|
|
39
39
|
return res.status(204).send(null);
|
|
40
40
|
}
|
|
41
|
-
const components = Array.
|
|
42
|
-
? this.config.components
|
|
43
|
-
: Array.from(this.config.components.keys());
|
|
41
|
+
const components = Array.from(this.config.components.keys());
|
|
44
42
|
return res.status(200).json({
|
|
45
43
|
components,
|
|
46
44
|
packages: this.config.metadata.packages,
|
|
@@ -8,19 +8,20 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
import { debug } from '@sitecore-content-sdk/core';
|
|
11
|
+
import { QUERY_PARAM_EDITING_SECRET, EDITING_ALLOWED_ORIGINS, PREVIEW_KEY, } from '@sitecore-content-sdk/core/editing';
|
|
11
12
|
import { LayoutServicePageState } from '@sitecore-content-sdk/core/layout';
|
|
12
|
-
import { QUERY_PARAM_EDITING_SECRET, EDITING_ALLOWED_ORIGINS, } from '@sitecore-content-sdk/core/editing';
|
|
13
13
|
import { getJssEditingSecret } from '../utils/utils';
|
|
14
14
|
import { RenderMiddlewareBase } from './render-middleware';
|
|
15
15
|
import { enforceCors, getAllowedOriginsFromEnv } from '@sitecore-content-sdk/core/utils';
|
|
16
16
|
import { DEFAULT_VARIANT } from '@sitecore-content-sdk/core/personalize';
|
|
17
|
+
import { SITE_KEY } from '@sitecore-content-sdk/core/site';
|
|
17
18
|
/**
|
|
18
|
-
* Type guard for
|
|
19
|
+
* Type guard for Design Library mode
|
|
19
20
|
* @param {object} data preview data to check
|
|
20
21
|
* @returns true if the data is EditingPreviewData
|
|
21
22
|
* @see EditingPreviewData
|
|
22
23
|
*/
|
|
23
|
-
export const
|
|
24
|
+
export const isDesignLibraryPreviewData = (data) => {
|
|
24
25
|
return (typeof data === 'object' &&
|
|
25
26
|
data !== null &&
|
|
26
27
|
'mode' in data &&
|
|
@@ -93,15 +94,12 @@ export class EditingRenderMiddleware extends RenderMiddlewareBase {
|
|
|
93
94
|
});
|
|
94
95
|
}
|
|
95
96
|
if (mode === 'library') {
|
|
96
|
-
// dedicated route and layout to SSR component library
|
|
97
|
-
query.route = '/component-library/render';
|
|
98
97
|
res.setPreviewData({
|
|
99
98
|
itemId: query.sc_itemid,
|
|
100
99
|
componentUid: query.sc_uid,
|
|
101
100
|
renderingId: query.sc_renderingId,
|
|
102
101
|
language: query.sc_lang,
|
|
103
102
|
site: query.sc_site,
|
|
104
|
-
pageState: LayoutServicePageState.Normal,
|
|
105
103
|
mode: 'library',
|
|
106
104
|
dataSourceId: query.sc_datasourceId,
|
|
107
105
|
version: query.sc_version,
|
|
@@ -117,7 +115,7 @@ export class EditingRenderMiddleware extends RenderMiddlewareBase {
|
|
|
117
115
|
// for sc_variantId we may employ multiple variants (page-layout + component level)
|
|
118
116
|
variantIds: ((_c = query.sc_variant) === null || _c === void 0 ? void 0 : _c.split(',')) || [DEFAULT_VARIANT],
|
|
119
117
|
version: query.sc_version,
|
|
120
|
-
|
|
118
|
+
mode: query.mode,
|
|
121
119
|
layoutKind: query.sc_layoutKind,
|
|
122
120
|
},
|
|
123
121
|
// Cache the preview data for 3 seconds to ensure the page is rendered with the correct preview data not the cached one
|
|
@@ -145,6 +143,12 @@ export class EditingRenderMiddleware extends RenderMiddlewareBase {
|
|
|
145
143
|
}
|
|
146
144
|
return cookie;
|
|
147
145
|
});
|
|
146
|
+
// Set Preview mode identifier cookie, if the page is rendered in Sitecore Preview mode
|
|
147
|
+
if (mode === LayoutServicePageState.Preview) {
|
|
148
|
+
const previewSite = `${SITE_KEY}=${query.sc_site}; Path=/; HttpOnly; SameSite=None; Secure`;
|
|
149
|
+
const previewCookie = `${PREVIEW_KEY}=true; Path=/; HttpOnly; SameSite=None; Secure`;
|
|
150
|
+
modifiedCookies.push(previewSite, previewCookie);
|
|
151
|
+
}
|
|
148
152
|
res.setHeader('Set-Cookie', modifiedCookies);
|
|
149
153
|
}
|
|
150
154
|
const route = ((_e = (_d = this.config) === null || _d === void 0 ? void 0 : _d.resolvePageUrl) === null || _e === void 0 ? void 0 : _e.call(_d, query.route)) || query.route;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { GraphQLEditingService } from '@sitecore-content-sdk/core/editing';
|
|
2
|
-
export { EditingRenderMiddleware,
|
|
2
|
+
export { EditingRenderMiddleware, isDesignLibraryPreviewData, } from './editing-render-middleware';
|
|
3
3
|
export { FEAASRenderMiddleware } from './feaas-render-middleware';
|
|
4
4
|
export { EditingConfigMiddleware, } from './editing-config-middleware';
|
|
5
5
|
export { RenderingType, EDITING_COMPONENT_PLACEHOLDER, EDITING_COMPONENT_ID, } from '@sitecore-content-sdk/core/layout';
|
package/dist/esm/index.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
export { constants,
|
|
2
2
|
// generic data access
|
|
3
3
|
NativeDataFetcher, enableDebug, debug, MemoryCacheClient, } from '@sitecore-content-sdk/core';
|
|
4
|
-
export { LayoutServicePageState, GraphQLLayoutService, getChildPlaceholder, getFieldValue, getContentStylesheetLink, EditMode, } from '@sitecore-content-sdk/core/layout';
|
|
4
|
+
export { LayoutServicePageState, GraphQLLayoutService, getChildPlaceholder, getFieldValue, getContentStylesheetLink, EditMode, RenderingType, } from '@sitecore-content-sdk/core/layout';
|
|
5
5
|
export { RestComponentLayoutService } from '@sitecore-content-sdk/core/editing';
|
|
6
6
|
export { mediaApi } from '@sitecore-content-sdk/core/media';
|
|
7
7
|
export { GraphQLDictionaryService, } from '@sitecore-content-sdk/core/i18n';
|
|
8
8
|
export { personalizeLayout, getPersonalizedRewrite, getPersonalizedRewriteData, getGroomedVariantIds, normalizePersonalizedRewrite, CdpHelper, } from '@sitecore-content-sdk/core/personalize';
|
|
9
9
|
export { ComponentPropsService } from './services/component-props-service';
|
|
10
|
-
export {
|
|
11
|
-
export { MultisiteGraphQLSitemapService, } from './services/mutisite-graphql-sitemap-service';
|
|
10
|
+
export { GraphQLSitePathService, } from '@sitecore-content-sdk/core/site';
|
|
12
11
|
export { GraphQLSitemapXmlService, GraphQLErrorPagesService, GraphQLRobotsService, SiteResolver, GraphQLSiteInfoService, getSiteRewrite, getSiteRewriteData, normalizeSiteRewrite, } from '@sitecore-content-sdk/core/site';
|
|
13
12
|
export { ComponentPropsReactContext, ComponentPropsContext, useComponentProps, } from './components/ComponentPropsContext';
|
|
14
13
|
export { Link } from './components/Link';
|
|
@@ -19,5 +18,4 @@ import * as FEaaSWrapper from './components/FEaaSWrapper';
|
|
|
19
18
|
import * as BYOCWrapper from './components/BYOCWrapper';
|
|
20
19
|
export { FEaaSWrapper };
|
|
21
20
|
export { BYOCWrapper };
|
|
22
|
-
export {
|
|
23
|
-
export { Image, Text, DateField, FEaaSComponent, fetchFEaaSComponentServerProps, BYOCComponent, getComponentLibraryStylesheetLinks, File, ComponentLibraryLayout, DefaultEmptyFieldEditingComponentImage, DefaultEmptyFieldEditingComponentText, SitecoreContext, SitecoreContextReactContext, withSitecoreContext, useSitecoreContext, withEditorChromes, withPlaceholder, withDatasourceCheck, withFieldMetadata, withEmptyFieldEditingComponent, EditingScripts, } from '@sitecore-content-sdk/react';
|
|
21
|
+
export { Image, Text, DateField, FEaaSComponent, fetchFEaaSComponentServerProps, BYOCComponent, getDesignLibraryStylesheetLinks, File, DesignLibrary, DefaultEmptyFieldEditingComponentImage, DefaultEmptyFieldEditingComponentText, SitecoreContext, SitecoreContextReactContext, withSitecoreContext, useSitecoreContext, withEditorChromes, withPlaceholder, withDatasourceCheck, withFieldMetadata, withEmptyFieldEditingComponent, EditingScripts, Form, } from '@sitecore-content-sdk/react';
|
|
@@ -3,3 +3,4 @@ export { MiddlewareBase, Middleware, defineMiddleware } from './middleware';
|
|
|
3
3
|
export { RedirectsMiddleware } from './redirects-middleware';
|
|
4
4
|
export { PersonalizeMiddleware } from './personalize-middleware';
|
|
5
5
|
export { MultisiteMiddleware } from './multisite-middleware';
|
|
6
|
+
export { SitemapMiddleware } from './sitemap-middleware';
|
|
@@ -7,9 +7,10 @@ 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 { SiteResolver } from '@sitecore-content-sdk/core/site';
|
|
10
|
+
import { SITE_KEY, SiteResolver } from '@sitecore-content-sdk/core/site';
|
|
11
11
|
import { debug } from '@sitecore-content-sdk/core';
|
|
12
12
|
import { NextResponse } from 'next/server';
|
|
13
|
+
export const REWRITE_HEADER_NAME = 'x-sc-rewrite';
|
|
13
14
|
/**
|
|
14
15
|
* Middleware class to be extended by all middleware implementations
|
|
15
16
|
*/
|
|
@@ -22,8 +23,6 @@ export class MiddlewareBase extends Middleware {
|
|
|
22
23
|
constructor(config) {
|
|
23
24
|
super();
|
|
24
25
|
this.config = config;
|
|
25
|
-
this.SITE_SYMBOL = 'sc_site';
|
|
26
|
-
this.REWRITE_HEADER_NAME = 'x-sc-rewrite';
|
|
27
26
|
this.siteResolver = new SiteResolver(config.sites);
|
|
28
27
|
this.defaultHostname = config.defaultHostname || 'localhost';
|
|
29
28
|
}
|
|
@@ -52,7 +51,7 @@ export class MiddlewareBase extends Middleware {
|
|
|
52
51
|
return (pathname.startsWith('/api/') || // Ignore Next.js API calls
|
|
53
52
|
pathname.startsWith('/sitecore/') || // Ignore Sitecore API calls
|
|
54
53
|
pathname.startsWith('/_next') || // Ignore next service calls
|
|
55
|
-
(this.config.
|
|
54
|
+
(this.config.skip && this.config.skip(req, res)));
|
|
56
55
|
}
|
|
57
56
|
/**
|
|
58
57
|
* Safely extract all headers for debug logging
|
|
@@ -71,7 +70,7 @@ export class MiddlewareBase extends Middleware {
|
|
|
71
70
|
* @returns {string} language
|
|
72
71
|
*/
|
|
73
72
|
getLanguage(req) {
|
|
74
|
-
return req.nextUrl.locale || req.nextUrl.defaultLocale || 'en';
|
|
73
|
+
return req.nextUrl.locale || req.nextUrl.defaultLocale || this.config.defaultLanguage || 'en';
|
|
75
74
|
}
|
|
76
75
|
/**
|
|
77
76
|
* Extract 'host' header
|
|
@@ -82,18 +81,27 @@ export class MiddlewareBase extends Middleware {
|
|
|
82
81
|
return (_a = req.headers.get('host')) === null || _a === void 0 ? void 0 : _a.split(':')[0];
|
|
83
82
|
}
|
|
84
83
|
/**
|
|
85
|
-
* Get site information.
|
|
86
|
-
*
|
|
84
|
+
* Get site information. If site name is stored in cookie, use it, otherwise resolve by hostname
|
|
85
|
+
* - If site can't be resolved by site name cookie use default site info based on provided parameters
|
|
86
|
+
* - If site can't be resolved by hostname throw an error
|
|
87
87
|
* @param {NextRequest} req request
|
|
88
88
|
* @param {NextResponse} [res] response
|
|
89
89
|
* @returns {SiteInfo} site information
|
|
90
90
|
*/
|
|
91
91
|
getSite(req, res) {
|
|
92
92
|
var _a;
|
|
93
|
-
const siteNameCookie = (_a = res === null || res === void 0 ? void 0 : res.cookies.get(
|
|
94
|
-
if (siteNameCookie)
|
|
95
|
-
return this.siteResolver.getByName(siteNameCookie);
|
|
93
|
+
const siteNameCookie = (_a = res === null || res === void 0 ? void 0 : res.cookies.get(SITE_KEY)) === null || _a === void 0 ? void 0 : _a.value;
|
|
96
94
|
const hostname = this.getHostHeader(req) || this.defaultHostname;
|
|
95
|
+
if (siteNameCookie) {
|
|
96
|
+
// Usually we should be able to resolve site by cookie
|
|
97
|
+
// in case of Sitecore Preview mode, there can be a case that new site was created
|
|
98
|
+
// but it's not present in the sitemap, so we fallback to default site info
|
|
99
|
+
return (this.siteResolver.getByName(siteNameCookie) || {
|
|
100
|
+
name: siteNameCookie,
|
|
101
|
+
language: this.getLanguage(req),
|
|
102
|
+
hostName: '*',
|
|
103
|
+
});
|
|
104
|
+
}
|
|
97
105
|
return this.siteResolver.getByHost(hostname);
|
|
98
106
|
}
|
|
99
107
|
/**
|
|
@@ -101,14 +109,17 @@ export class MiddlewareBase extends Middleware {
|
|
|
101
109
|
* @param {string} rewritePath the destionation path
|
|
102
110
|
* @param {NextRequest} req the current request
|
|
103
111
|
* @param {NextResponse} res the current response
|
|
112
|
+
* @param {boolean} [skipHeader] don't write 'x-sc-rewrite' header
|
|
104
113
|
*/
|
|
105
|
-
rewrite(rewritePath, req, res) {
|
|
114
|
+
rewrite(rewritePath, req, res, skipHeader) {
|
|
106
115
|
// Note an absolute URL is required: https://nextjs.org/docs/messages/middleware-relative-urls
|
|
107
116
|
const rewriteUrl = req.nextUrl.clone();
|
|
108
117
|
rewriteUrl.pathname = rewritePath;
|
|
109
118
|
const response = NextResponse.rewrite(rewriteUrl, res);
|
|
110
119
|
// Share rewrite path with following executed middlewares
|
|
111
|
-
|
|
120
|
+
if (!skipHeader) {
|
|
121
|
+
response.headers.set(REWRITE_HEADER_NAME, rewritePath);
|
|
122
|
+
}
|
|
112
123
|
return response;
|
|
113
124
|
}
|
|
114
125
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */
|
|
1
2
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
3
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
4
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -7,9 +8,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
9
|
});
|
|
9
10
|
};
|
|
10
|
-
import { getSiteRewrite } from '@sitecore-content-sdk/core/site';
|
|
11
|
+
import { getSiteRewrite, SITE_KEY } from '@sitecore-content-sdk/core/site';
|
|
11
12
|
import { debug } from '@sitecore-content-sdk/core';
|
|
12
13
|
import { MiddlewareBase } from './middleware';
|
|
14
|
+
import { PREVIEW_KEY } from '@sitecore-content-sdk/core/editing';
|
|
13
15
|
/**
|
|
14
16
|
* Middleware / handler for multisite support
|
|
15
17
|
*/
|
|
@@ -21,7 +23,7 @@ export class MultisiteMiddleware extends MiddlewareBase {
|
|
|
21
23
|
super(config);
|
|
22
24
|
this.config = config;
|
|
23
25
|
this.handle = (req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
24
|
-
var _a;
|
|
26
|
+
var _a, _b, _c;
|
|
25
27
|
if (!this.config.enabled) {
|
|
26
28
|
debug.multisite('skipped (multisite middleware is disabled globally)');
|
|
27
29
|
return res;
|
|
@@ -44,12 +46,21 @@ export class MultisiteMiddleware extends MiddlewareBase {
|
|
|
44
46
|
debug.multisite('skipped (preview)');
|
|
45
47
|
return res;
|
|
46
48
|
}
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
let siteName;
|
|
50
|
+
const isSitecorePreview = (_a = req.cookies.get(PREVIEW_KEY)) === null || _a === void 0 ? void 0 : _a.value;
|
|
51
|
+
if (isSitecorePreview) {
|
|
52
|
+
// This cookie is required to be set in the Sitecore Preview mode
|
|
53
|
+
siteName = (_b = req.cookies.get(SITE_KEY)) === null || _b === void 0 ? void 0 : _b.value;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
// Site name can be forced by query string parameter or cookie
|
|
57
|
+
siteName =
|
|
58
|
+
req.nextUrl.searchParams.get(SITE_KEY) ||
|
|
59
|
+
(this.config.useCookieResolution &&
|
|
60
|
+
this.config.useCookieResolution(req) &&
|
|
61
|
+
((_c = req.cookies.get(SITE_KEY)) === null || _c === void 0 ? void 0 : _c.value)) ||
|
|
62
|
+
this.siteResolver.getByHost(hostname).name;
|
|
63
|
+
}
|
|
53
64
|
// Rewrite to site specific path
|
|
54
65
|
const rewritePath = getSiteRewrite(pathname, {
|
|
55
66
|
siteName,
|
|
@@ -62,7 +73,7 @@ export class MultisiteMiddleware extends MiddlewareBase {
|
|
|
62
73
|
sameSite: 'none',
|
|
63
74
|
};
|
|
64
75
|
// Share site name with the following executed middlewares
|
|
65
|
-
response.cookies.set(
|
|
76
|
+
response.cookies.set(SITE_KEY, siteName, defaultCookieAttributes);
|
|
66
77
|
debug.multisite('multisite middleware end in %dms: %o', Date.now() - startTimestamp, {
|
|
67
78
|
rewritePath,
|
|
68
79
|
siteName,
|
|
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
};
|
|
10
10
|
import { GraphQLPersonalizeService, getPersonalizedRewrite, CdpHelper, DEFAULT_VARIANT, } from '@sitecore-content-sdk/core/personalize';
|
|
11
11
|
import { debug } from '@sitecore-content-sdk/core';
|
|
12
|
-
import { MiddlewareBase } from './middleware';
|
|
12
|
+
import { MiddlewareBase, REWRITE_HEADER_NAME } from './middleware';
|
|
13
13
|
import { CloudSDK } from '@sitecore-cloudsdk/core/server';
|
|
14
14
|
import { personalize } from '@sitecore-cloudsdk/personalize/server';
|
|
15
15
|
/**
|
|
@@ -32,7 +32,7 @@ export class PersonalizeMiddleware extends MiddlewareBase {
|
|
|
32
32
|
const language = this.getLanguage(req);
|
|
33
33
|
const hostname = this.getHostHeader(req) || this.defaultHostname;
|
|
34
34
|
const startTimestamp = Date.now();
|
|
35
|
-
const
|
|
35
|
+
const cdpTimeout = this.config.cdpTimeout;
|
|
36
36
|
debug.personalize('personalize middleware start: %o', {
|
|
37
37
|
pathname,
|
|
38
38
|
language,
|
|
@@ -84,7 +84,7 @@ export class PersonalizeMiddleware extends MiddlewareBase {
|
|
|
84
84
|
variantIds: execution.variantIds,
|
|
85
85
|
params,
|
|
86
86
|
language,
|
|
87
|
-
timeout,
|
|
87
|
+
timeout: cdpTimeout,
|
|
88
88
|
}, req).then((personalization) => {
|
|
89
89
|
const variantId = personalization.variantId;
|
|
90
90
|
if (variantId) {
|
|
@@ -101,7 +101,7 @@ export class PersonalizeMiddleware extends MiddlewareBase {
|
|
|
101
101
|
return res;
|
|
102
102
|
}
|
|
103
103
|
// Path can be rewritten by previously executed middleware
|
|
104
|
-
const basePath = (res === null || res === void 0 ? void 0 : res.headers.get(
|
|
104
|
+
const basePath = (res === null || res === void 0 ? void 0 : res.headers.get(REWRITE_HEADER_NAME)) || pathname;
|
|
105
105
|
// Rewrite to persononalized path
|
|
106
106
|
const rewritePath = getPersonalizedRewrite(basePath, identifiedVariantIds);
|
|
107
107
|
const response = this.rewrite(rewritePath, req, res);
|
|
@@ -12,7 +12,7 @@ import { GraphQLRedirectsService, REDIRECT_TYPE_301, REDIRECT_TYPE_302, REDIRECT
|
|
|
12
12
|
import { areURLSearchParamsEqual, escapeNonSpecialQuestionMarks, isRegexOrUrl, mergeURLSearchParams, } from '@sitecore-content-sdk/core/utils';
|
|
13
13
|
import { NextResponse } from 'next/server';
|
|
14
14
|
import regexParser from 'regex-parser';
|
|
15
|
-
import { MiddlewareBase } from './middleware';
|
|
15
|
+
import { MiddlewareBase, REWRITE_HEADER_NAME } from './middleware';
|
|
16
16
|
const REGEXP_CONTEXT_SITE_LANG = new RegExp(/\$siteLang/, 'i');
|
|
17
17
|
const REGEXP_ABSOLUTE_URL = new RegExp('^(?:[a-z]+:)?//', 'i');
|
|
18
18
|
/**
|
|
@@ -66,6 +66,7 @@ export class RedirectsMiddleware extends MiddlewareBase {
|
|
|
66
66
|
debug.redirects('skipped (redirect does not exist)');
|
|
67
67
|
return res;
|
|
68
68
|
}
|
|
69
|
+
debug.redirects('Matched redirect rule: %o', { existsRedirect });
|
|
69
70
|
// Find context site language and replace token
|
|
70
71
|
if (REGEXP_CONTEXT_SITE_LANG.test(existsRedirect.target) &&
|
|
71
72
|
!(REGEXP_ABSOLUTE_URL.test(existsRedirect.target) &&
|
|
@@ -112,7 +113,7 @@ export class RedirectsMiddleware extends MiddlewareBase {
|
|
|
112
113
|
return this.createRedirectResponse(url, res, 302, 'Found');
|
|
113
114
|
}
|
|
114
115
|
case REDIRECT_TYPE_SERVER_TRANSFER: {
|
|
115
|
-
return this.rewrite(url.href, req, res);
|
|
116
|
+
return this.rewrite(url.href, req, res, true);
|
|
116
117
|
}
|
|
117
118
|
default:
|
|
118
119
|
return res;
|
|
@@ -147,23 +148,35 @@ export class RedirectsMiddleware extends MiddlewareBase {
|
|
|
147
148
|
*/
|
|
148
149
|
getExistsRedirect(req, siteName) {
|
|
149
150
|
return __awaiter(this, void 0, void 0, function* () {
|
|
150
|
-
const { pathname:
|
|
151
|
-
const
|
|
151
|
+
const { pathname: incomingURL, search: incomingQS = '' } = this.normalizeUrl(req.nextUrl.clone());
|
|
152
|
+
const locale = this.getLanguage(req);
|
|
153
|
+
const normalizedPath = incomingURL.replace(/\/*$/gi, '');
|
|
152
154
|
const redirects = yield this.redirectsService.fetchRedirects(siteName);
|
|
153
155
|
const language = this.getLanguage(req);
|
|
154
156
|
const modifyRedirects = structuredClone(redirects);
|
|
155
157
|
let matchedQueryString;
|
|
158
|
+
const localePath = `/${locale.toLowerCase()}${normalizedPath}`;
|
|
156
159
|
return modifyRedirects.length
|
|
157
160
|
? modifyRedirects.find((redirect) => {
|
|
158
|
-
|
|
161
|
+
// process static URL (non-regex) rules
|
|
159
162
|
if (isRegexOrUrl(redirect.pattern) === 'url') {
|
|
160
|
-
const
|
|
163
|
+
const urlArray = redirect.pattern.endsWith('/')
|
|
161
164
|
? redirect.pattern.slice(0, -1).split('?')
|
|
162
165
|
: redirect.pattern.split('?');
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
+
const patternQS = urlArray[1];
|
|
167
|
+
let patternPath = urlArray[0];
|
|
168
|
+
// nextjs routes are case-sensitive, but locales should be compared case-insensitively
|
|
169
|
+
const patternParts = patternPath.split('/');
|
|
170
|
+
const maybeLocale = patternParts[1].toLowerCase();
|
|
171
|
+
// case insensitive lookup of locales
|
|
172
|
+
if (new RegExp(this.locales.join('|'), 'i').test(maybeLocale)) {
|
|
173
|
+
patternPath = patternPath.replace(`/${patternParts[1]}`, `/${maybeLocale}`);
|
|
174
|
+
}
|
|
175
|
+
return ((patternPath === localePath || patternPath === normalizedPath) &&
|
|
176
|
+
(!patternQS ||
|
|
177
|
+
areURLSearchParamsEqual(new URLSearchParams(patternQS), new URLSearchParams(incomingQS))));
|
|
166
178
|
}
|
|
179
|
+
// process regex rules
|
|
167
180
|
// Modify the redirect pattern to ignore the language prefix in the path
|
|
168
181
|
// And escapes non-special "?" characters in a string or regex.
|
|
169
182
|
redirect.pattern = escapeNonSpecialQuestionMarks(redirect.pattern.replace(new RegExp(`^[^]?/${language}/`, 'gi'), ''));
|
|
@@ -173,16 +186,17 @@ export class RedirectsMiddleware extends MiddlewareBase {
|
|
|
173
186
|
.replace(/^\^\/|\/\$$/g, '') // Removes unnecessary start (^) and end ($) anchors
|
|
174
187
|
.replace(/^\^|\$$/g, '') // Further cleans up anchors
|
|
175
188
|
.replace(/\$\/gi$/g, '')}[\/]?$/i`; // Ensures the pattern allows an optional trailing slash
|
|
189
|
+
// Redirect pattern matches the full incoming URL with query string present
|
|
176
190
|
matchedQueryString = [
|
|
177
|
-
regexParser(redirect.pattern).test(
|
|
178
|
-
regexParser(redirect.pattern).test(
|
|
191
|
+
regexParser(redirect.pattern).test(`/${localePath}${incomingQS}`),
|
|
192
|
+
regexParser(redirect.pattern).test(`${normalizedPath}${incomingQS}`),
|
|
179
193
|
].some(Boolean)
|
|
180
|
-
?
|
|
194
|
+
? incomingQS
|
|
181
195
|
: undefined;
|
|
182
196
|
// Save the matched query string (if found) into the redirect object
|
|
183
197
|
redirect.matchedQueryString = matchedQueryString || '';
|
|
184
|
-
return (!!(regexParser(redirect.pattern).test(
|
|
185
|
-
regexParser(redirect.pattern).test(
|
|
198
|
+
return (!!(regexParser(redirect.pattern).test(`/${req.nextUrl.locale}${incomingURL}`) ||
|
|
199
|
+
regexParser(redirect.pattern).test(incomingURL) ||
|
|
186
200
|
matchedQueryString) && (redirect.locale ? redirect.locale.toLowerCase() === locale.toLowerCase() : true));
|
|
187
201
|
})
|
|
188
202
|
: undefined;
|
|
@@ -245,6 +259,7 @@ export class RedirectsMiddleware extends MiddlewareBase {
|
|
|
245
259
|
if (res === null || res === void 0 ? void 0 : res.headers) {
|
|
246
260
|
redirect.headers.delete('x-middleware-next');
|
|
247
261
|
redirect.headers.delete('x-middleware-rewrite');
|
|
262
|
+
redirect.headers.delete(REWRITE_HEADER_NAME);
|
|
248
263
|
}
|
|
249
264
|
return redirect;
|
|
250
265
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Middleware for handling sitemap requests in a Next.js application.
|
|
12
|
+
* Encapsulates all HTTP-related logic for sitemap generation and delivery.
|
|
13
|
+
*/
|
|
14
|
+
export class SitemapMiddleware {
|
|
15
|
+
constructor(client) {
|
|
16
|
+
this.client = client;
|
|
17
|
+
}
|
|
18
|
+
getHandler() {
|
|
19
|
+
return this.handler.bind(this);
|
|
20
|
+
}
|
|
21
|
+
handler(req, res) {
|
|
22
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
23
|
+
const id = Array.isArray(req.query.id) ? req.query.id[0] : req.query.id;
|
|
24
|
+
const reqHost = req.headers.host || '';
|
|
25
|
+
const reqProtocol = req.headers['x-forwarded-proto'] || 'https';
|
|
26
|
+
const site = this.client.resolveSite(reqHost);
|
|
27
|
+
const options = { reqHost, reqProtocol, id, siteName: site.name };
|
|
28
|
+
try {
|
|
29
|
+
const xmlContent = yield this.client.getSiteMap(options);
|
|
30
|
+
res.setHeader('Content-Type', 'text/xml;charset=utf-8');
|
|
31
|
+
res.send(xmlContent);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
if (error instanceof Error && error.message === 'REDIRECT_404') {
|
|
35
|
+
res.redirect('/404');
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
res.status(500).send('Internal Server Error');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|