@cmssy/next 2.7.0 → 4.0.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/dist/index.cjs +101 -21
- package/dist/index.d.cts +61 -4
- package/dist/index.d.ts +61 -4
- package/dist/index.js +97 -23
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -6,8 +6,8 @@ var react = require('@cmssy/react');
|
|
|
6
6
|
var client = require('@cmssy/react/client');
|
|
7
7
|
var jose = require('jose');
|
|
8
8
|
var jsxRuntime = require('react/jsx-runtime');
|
|
9
|
-
var crypto$1 = require('crypto');
|
|
10
9
|
var server = require('next/server');
|
|
10
|
+
var crypto$1 = require('crypto');
|
|
11
11
|
|
|
12
12
|
// src/create-cmssy-page.tsx
|
|
13
13
|
var CMSSY_SESSION_COOKIE = "cmssy_session";
|
|
@@ -204,11 +204,63 @@ function applyCmssyCsp(response, options) {
|
|
|
204
204
|
response.headers.delete("X-Frame-Options");
|
|
205
205
|
return response;
|
|
206
206
|
}
|
|
207
|
-
|
|
207
|
+
|
|
208
|
+
// src/secret-match.ts
|
|
209
|
+
var MAX_SECRET_LENGTH = 256;
|
|
210
|
+
async function cmssySecretsMatch(a, b) {
|
|
211
|
+
if (a.length > MAX_SECRET_LENGTH || b.length > MAX_SECRET_LENGTH) {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
const encoder = new TextEncoder();
|
|
215
|
+
const [ha, hb] = await Promise.all([
|
|
216
|
+
crypto.subtle.digest("SHA-256", encoder.encode(a)),
|
|
217
|
+
crypto.subtle.digest("SHA-256", encoder.encode(b))
|
|
218
|
+
]);
|
|
219
|
+
const va = new Uint8Array(ha);
|
|
220
|
+
const vb = new Uint8Array(hb);
|
|
221
|
+
let diff = 0;
|
|
222
|
+
for (let i = 0; i < va.length; i += 1) {
|
|
223
|
+
diff |= (va[i] ?? 0) ^ (vb[i] ?? 0);
|
|
224
|
+
}
|
|
225
|
+
return diff === 0;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/edit-mode.ts
|
|
229
|
+
var CMSSY_EDIT_HEADER = "x-cmssy-edit";
|
|
230
|
+
var CMSSY_EDIT_QUERY_PARAM = "cmssyEdit";
|
|
231
|
+
var CMSSY_SECRET_QUERY_PARAM = "cmssySecret";
|
|
232
|
+
async function isCmssyEditRequest(request2, config) {
|
|
233
|
+
if (request2.cookies.has("__prerender_bypass")) return true;
|
|
234
|
+
if (!request2.nextUrl.searchParams.getAll(CMSSY_EDIT_QUERY_PARAM).includes("1")) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
const provided = request2.nextUrl.searchParams.get(CMSSY_SECRET_QUERY_PARAM);
|
|
238
|
+
if (!provided || !config.draftSecret) return false;
|
|
239
|
+
return cmssySecretsMatch(provided, config.draftSecret);
|
|
240
|
+
}
|
|
241
|
+
async function isCmssyEditMode() {
|
|
242
|
+
const h = await headers.headers();
|
|
243
|
+
return h.get(CMSSY_EDIT_HEADER) === "1";
|
|
244
|
+
}
|
|
208
245
|
function hasEditFlag(value) {
|
|
209
246
|
return Array.isArray(value) ? value.includes("1") : value === "1";
|
|
210
247
|
}
|
|
248
|
+
function firstValue(value) {
|
|
249
|
+
return Array.isArray(value) ? value[0] : value;
|
|
250
|
+
}
|
|
251
|
+
async function resolveEditorRequest(query, draftSecret) {
|
|
252
|
+
if (!hasEditFlag(query[CMSSY_EDIT_QUERY_PARAM])) return false;
|
|
253
|
+
const provided = firstValue(query[CMSSY_SECRET_QUERY_PARAM]);
|
|
254
|
+
if (!provided || !draftSecret) return false;
|
|
255
|
+
return cmssySecretsMatch(provided, draftSecret);
|
|
256
|
+
}
|
|
211
257
|
function createCmssyPage(config, blocks, options) {
|
|
258
|
+
return buildCmssyPageRenderer(config, blocks, options, false);
|
|
259
|
+
}
|
|
260
|
+
function createCmssyEditPage(config, blocks, options) {
|
|
261
|
+
return buildCmssyPageRenderer(config, blocks, options, true);
|
|
262
|
+
}
|
|
263
|
+
function buildCmssyPageRenderer(config, blocks, options, editRoute) {
|
|
212
264
|
if (!Array.isArray(blocks)) {
|
|
213
265
|
throw new Error(
|
|
214
266
|
"cmssy: createCmssyPage(config, blocks) requires a blocks array \u2014 pass your defineBlock(...) array"
|
|
@@ -228,10 +280,16 @@ function createCmssyPage(config, blocks, options) {
|
|
|
228
280
|
}) {
|
|
229
281
|
const path = fixedPath ?? (params ? (await params).path ?? void 0 : void 0);
|
|
230
282
|
const { isEnabled } = await headers.draftMode();
|
|
231
|
-
|
|
232
|
-
|
|
283
|
+
let editorActive = false;
|
|
284
|
+
if (editRoute) {
|
|
285
|
+
const query = searchParams ? await searchParams : {};
|
|
286
|
+
editorActive = await resolveEditorRequest(query, config.draftSecret);
|
|
287
|
+
if (!editorActive) {
|
|
288
|
+
navigation.notFound();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
const editMode = isEnabled || editorActive;
|
|
233
292
|
const devAllowed = isDevelopment() && Boolean(config.devToken?.trim());
|
|
234
|
-
const editorActive = editMode;
|
|
235
293
|
let locale;
|
|
236
294
|
let pagePath = path;
|
|
237
295
|
let defaultLocale;
|
|
@@ -340,6 +398,23 @@ function resolveBridgeOrigin(editorOrigin) {
|
|
|
340
398
|
}
|
|
341
399
|
return origins.length === 1 ? origins[0] : origins;
|
|
342
400
|
}
|
|
401
|
+
var CMSSY_EDIT_PATH_PREFIX = "/__cmssy/edit";
|
|
402
|
+
async function cmssyEditRewrite(request2, config) {
|
|
403
|
+
const { pathname, searchParams } = request2.nextUrl;
|
|
404
|
+
if (pathname.startsWith(CMSSY_EDIT_PATH_PREFIX)) return null;
|
|
405
|
+
if (!searchParams.getAll(CMSSY_EDIT_QUERY_PARAM).includes("1")) return null;
|
|
406
|
+
const provided = searchParams.get(CMSSY_SECRET_QUERY_PARAM);
|
|
407
|
+
if (!provided || !config.draftSecret) return null;
|
|
408
|
+
if (!await cmssySecretsMatch(provided, config.draftSecret)) return null;
|
|
409
|
+
const url = request2.nextUrl.clone();
|
|
410
|
+
url.pathname = `${CMSSY_EDIT_PATH_PREFIX}${pathname === "/" ? "" : pathname}`;
|
|
411
|
+
return server.NextResponse.rewrite(url);
|
|
412
|
+
}
|
|
413
|
+
function createCmssyEditMiddleware(config) {
|
|
414
|
+
return async function cmssyEditMiddleware(request2) {
|
|
415
|
+
return await cmssyEditRewrite(request2, config) ?? server.NextResponse.next();
|
|
416
|
+
};
|
|
417
|
+
}
|
|
343
418
|
function DefaultNotFound() {
|
|
344
419
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
345
420
|
"main",
|
|
@@ -595,11 +670,6 @@ async function buildCmssyMetadata(config, path, options = {}) {
|
|
|
595
670
|
};
|
|
596
671
|
}
|
|
597
672
|
var MIN_SECRET_LENGTH = 16;
|
|
598
|
-
function secretsMatch(a, b) {
|
|
599
|
-
const ha = crypto$1.createHash("sha256").update(a).digest();
|
|
600
|
-
const hb = crypto$1.createHash("sha256").update(b).digest();
|
|
601
|
-
return crypto$1.timingSafeEqual(ha, hb);
|
|
602
|
-
}
|
|
603
673
|
function safeRedirect(redirect2, fallback) {
|
|
604
674
|
if (!redirect2 || !redirect2.startsWith("/")) return fallback;
|
|
605
675
|
if (redirect2.startsWith("//") || redirect2.includes("\\")) return fallback;
|
|
@@ -620,15 +690,22 @@ function createDraftRoute(config) {
|
|
|
620
690
|
);
|
|
621
691
|
}
|
|
622
692
|
return async function GET(request2) {
|
|
693
|
+
const url = new URL(request2.url);
|
|
694
|
+
if (url.searchParams.get("disable") === "1") {
|
|
695
|
+
const draft2 = await headers.draftMode();
|
|
696
|
+
draft2.disable();
|
|
697
|
+
navigation.redirect(
|
|
698
|
+
safeRedirect(url.searchParams.get("redirect"), fallbackRedirect)
|
|
699
|
+
);
|
|
700
|
+
}
|
|
623
701
|
if (config.draftSecret.length < MIN_SECRET_LENGTH) {
|
|
624
702
|
return new Response(
|
|
625
703
|
`cmssy: draftSecret must be at least ${MIN_SECRET_LENGTH} characters`,
|
|
626
704
|
{ status: 500 }
|
|
627
705
|
);
|
|
628
706
|
}
|
|
629
|
-
const url = new URL(request2.url);
|
|
630
707
|
const secret = url.searchParams.get("secret");
|
|
631
|
-
if (!secret || !
|
|
708
|
+
if (!secret || !await cmssySecretsMatch(secret, config.draftSecret)) {
|
|
632
709
|
return new Response("Invalid draft secret", { status: 401 });
|
|
633
710
|
}
|
|
634
711
|
const location = safeRedirect(
|
|
@@ -640,14 +717,6 @@ function createDraftRoute(config) {
|
|
|
640
717
|
navigation.redirect(location);
|
|
641
718
|
};
|
|
642
719
|
}
|
|
643
|
-
var CMSSY_EDIT_HEADER = "x-cmssy-edit";
|
|
644
|
-
function isCmssyEditRequest(request2) {
|
|
645
|
-
return request2.cookies.has("__prerender_bypass") || request2.nextUrl.searchParams.getAll("cmssyEdit").includes("1");
|
|
646
|
-
}
|
|
647
|
-
async function isCmssyEditMode() {
|
|
648
|
-
const h = await headers.headers();
|
|
649
|
-
return h.get(CMSSY_EDIT_HEADER) === "1";
|
|
650
|
-
}
|
|
651
720
|
var CMSSY_LOCALE_HEADER = "x-cmssy-locale";
|
|
652
721
|
async function localeForPathname(config, pathname) {
|
|
653
722
|
const siteLocales = await react.resolveSiteLocales(config);
|
|
@@ -658,7 +727,12 @@ async function splitCmssyLocale(config, path) {
|
|
|
658
727
|
const siteLocales = await react.resolveSiteLocales(config);
|
|
659
728
|
return react.splitLocaleFromPath(path, siteLocales);
|
|
660
729
|
}
|
|
661
|
-
async function getCmssyLocale(config) {
|
|
730
|
+
async function getCmssyLocale(config, options) {
|
|
731
|
+
if (options?.path !== void 0) {
|
|
732
|
+
const siteLocales = await react.resolveSiteLocales(config);
|
|
733
|
+
const segments = Array.isArray(options.path) ? options.path.filter(Boolean) : options.path.split("/").filter(Boolean);
|
|
734
|
+
return react.splitLocaleFromPath(segments, siteLocales).locale;
|
|
735
|
+
}
|
|
662
736
|
const { headers: headers3 } = await import('next/headers');
|
|
663
737
|
const headerList = await headers3();
|
|
664
738
|
const fromHeader = headerList.get(CMSSY_LOCALE_HEADER);
|
|
@@ -1918,7 +1992,10 @@ Object.defineProperty(exports, "resolveApiUrl", {
|
|
|
1918
1992
|
});
|
|
1919
1993
|
exports.CMSSY_CART_COOKIE = CMSSY_CART_COOKIE;
|
|
1920
1994
|
exports.CMSSY_EDIT_HEADER = CMSSY_EDIT_HEADER;
|
|
1995
|
+
exports.CMSSY_EDIT_PATH_PREFIX = CMSSY_EDIT_PATH_PREFIX;
|
|
1996
|
+
exports.CMSSY_EDIT_QUERY_PARAM = CMSSY_EDIT_QUERY_PARAM;
|
|
1921
1997
|
exports.CMSSY_LOCALE_HEADER = CMSSY_LOCALE_HEADER;
|
|
1998
|
+
exports.CMSSY_SECRET_QUERY_PARAM = CMSSY_SECRET_QUERY_PARAM;
|
|
1922
1999
|
exports.CMSSY_SESSION_COOKIE = CMSSY_SESSION_COOKIE;
|
|
1923
2000
|
exports.CmssyWebhookError = CmssyWebhookError;
|
|
1924
2001
|
exports.DEFAULT_CMSSY_EDITOR_ORIGINS = DEFAULT_CMSSY_EDITOR_ORIGINS;
|
|
@@ -1927,9 +2004,12 @@ exports.applyCmssyCsp = applyCmssyCsp;
|
|
|
1927
2004
|
exports.assertAuthConfig = assertAuthConfig;
|
|
1928
2005
|
exports.buildCmssyMetadata = buildCmssyMetadata;
|
|
1929
2006
|
exports.cmssyCspHeaders = cmssyCspHeaders;
|
|
2007
|
+
exports.cmssyEditRewrite = cmssyEditRewrite;
|
|
1930
2008
|
exports.createCmssyAuthMiddleware = createCmssyAuthMiddleware;
|
|
1931
2009
|
exports.createCmssyAuthRoute = createCmssyAuthRoute;
|
|
1932
2010
|
exports.createCmssyCartRoute = createCmssyCartRoute;
|
|
2011
|
+
exports.createCmssyEditMiddleware = createCmssyEditMiddleware;
|
|
2012
|
+
exports.createCmssyEditPage = createCmssyEditPage;
|
|
1933
2013
|
exports.createCmssyLocaleMiddleware = createCmssyLocaleMiddleware;
|
|
1934
2014
|
exports.createCmssyNotFound = createCmssyNotFound;
|
|
1935
2015
|
exports.createCmssyOrdersRoute = createCmssyOrdersRoute;
|
package/dist/index.d.cts
CHANGED
|
@@ -5,8 +5,8 @@ export { DEFAULT_CMSSY_API_URL, FieldCondition, FieldConditionGroup, FieldCondit
|
|
|
5
5
|
import { EditBridgeConfig } from '@cmssy/react/client';
|
|
6
6
|
import { CmssyAuthConfig, CmssySessionPayload, SessionCookieOptions, CmssySessionUser, FetchProductOptions, CmssyProduct, FetchProductsOptions, CmssyProductPage, FetchOrderByTokenOptions, CmssyOrder, VerifyCmssyWebhookOptions, CmssyWebhookEvent } from '@cmssy/types';
|
|
7
7
|
export { CmssyAuthConfig, CmssyProductPage, CmssySessionPayload, CmssySessionUser, CmssyStockState, CmssyWebhookEvent, CmssyWebhookOrder, FetchOrderByTokenOptions, FetchProductOptions, FetchProductsOptions, MyOrdersResult, SessionCookieOptions, VerifyCmssyWebhookOptions } from '@cmssy/types';
|
|
8
|
-
import { MetadataRoute, Metadata } from 'next';
|
|
9
8
|
import { NextRequest, NextResponse } from 'next/server';
|
|
9
|
+
import { MetadataRoute, Metadata } from 'next';
|
|
10
10
|
|
|
11
11
|
declare const DEFAULT_CMSSY_EDITOR_ORIGINS: string[];
|
|
12
12
|
declare function resolveEditorOrigin(editorOrigin: string | string[] | undefined): string | string[];
|
|
@@ -88,7 +88,51 @@ interface CatchAllProps {
|
|
|
88
88
|
params?: Promise<CatchAllParams>;
|
|
89
89
|
searchParams?: Promise<SearchParams>;
|
|
90
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Public catch-all page. Statically renderable: it never reads
|
|
93
|
+
* `searchParams` or `headers()` - awaiting either forces every route
|
|
94
|
+
* dynamic and kills ISR (CMS-952). Draft-mode preview (the authenticated
|
|
95
|
+
* /api/draft cookie) still works per-request via `draftMode()`, which is
|
|
96
|
+
* static-safe, and renders draft CONTENT without the editor (CMS-948).
|
|
97
|
+
* The editor-iframe flow (`?cmssyEdit=1&cmssySecret=...`) is served by
|
|
98
|
+
* the middleware rewrite (`cmssyEditRewrite`) onto the dynamic edit route
|
|
99
|
+
* mounted with `createCmssyEditPage`.
|
|
100
|
+
*/
|
|
91
101
|
declare function createCmssyPage(config: CmssyNextConfig, blocks: BlockDefinition[], options?: CreateCmssyPageOptions): ({ params, searchParams, }: CatchAllProps) => Promise<react_jsx_runtime.JSX.Element>;
|
|
102
|
+
/**
|
|
103
|
+
* Editor page for the middleware-rewritten edit route
|
|
104
|
+
* (`app/__cmssy/edit/[[...path]]/page.tsx`, `dynamic = "force-dynamic"`).
|
|
105
|
+
* Re-verifies the `cmssyEdit` + `cmssySecret` pair itself, so a direct hit
|
|
106
|
+
* on the route path (bypassing the middleware) cannot mount the editor.
|
|
107
|
+
*/
|
|
108
|
+
declare function createCmssyEditPage(config: CmssyNextConfig, blocks: BlockDefinition[], options?: CreateCmssyPageOptions): ({ params, searchParams, }: CatchAllProps) => Promise<react_jsx_runtime.JSX.Element>;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Where the middleware rewrites editor-preview traffic. Mount the matching
|
|
112
|
+
* dynamic route in the consumer app:
|
|
113
|
+
*
|
|
114
|
+
* app/__cmssy/edit/[[...path]]/page.tsx
|
|
115
|
+
* export const dynamic = "force-dynamic";
|
|
116
|
+
* export default createCmssyEditPage(cmssy, blocks, { editor: Editor });
|
|
117
|
+
*/
|
|
118
|
+
declare const CMSSY_EDIT_PATH_PREFIX = "/__cmssy/edit";
|
|
119
|
+
/**
|
|
120
|
+
* Rewrite VERIFIED editor requests (`cmssyEdit=1` + a `cmssySecret` matching
|
|
121
|
+
* the site's draft secret, CMS-948) onto the dedicated dynamic edit route, so
|
|
122
|
+
* the public catch-all can stay fully static. A static page never sees its
|
|
123
|
+
* query string - this rewrite is what makes the editor iframe work at all
|
|
124
|
+
* once ISR is on. Draft-mode preview (the authenticated /api/draft cookie) is
|
|
125
|
+
* NOT rewritten: it renders draft content on the public route via
|
|
126
|
+
* `draftMode()`, without the editor. Returns null for normal traffic so it
|
|
127
|
+
* composes with other middleware.
|
|
128
|
+
*/
|
|
129
|
+
declare function cmssyEditRewrite(request: NextRequest, config: {
|
|
130
|
+
draftSecret: string;
|
|
131
|
+
}): Promise<NextResponse | null>;
|
|
132
|
+
/** Standalone middleware when the consumer has no other middleware to compose. */
|
|
133
|
+
declare function createCmssyEditMiddleware(config: {
|
|
134
|
+
draftSecret: string;
|
|
135
|
+
}): (request: NextRequest) => Promise<NextResponse>;
|
|
92
136
|
|
|
93
137
|
interface CreateCmssyNotFoundOptions {
|
|
94
138
|
/**
|
|
@@ -196,6 +240,8 @@ declare function cmssyCspHeaders(options: CmssyCspOptions): Record<string, strin
|
|
|
196
240
|
declare function applyCmssyCsp<T extends MutableHeaders>(response: T, options: CmssyCspOptions): T;
|
|
197
241
|
|
|
198
242
|
declare const CMSSY_EDIT_HEADER = "x-cmssy-edit";
|
|
243
|
+
declare const CMSSY_EDIT_QUERY_PARAM = "cmssyEdit";
|
|
244
|
+
declare const CMSSY_SECRET_QUERY_PARAM = "cmssySecret";
|
|
199
245
|
interface EditRequestLike {
|
|
200
246
|
cookies: {
|
|
201
247
|
has: (name: string) => boolean;
|
|
@@ -203,10 +249,13 @@ interface EditRequestLike {
|
|
|
203
249
|
nextUrl: {
|
|
204
250
|
searchParams: {
|
|
205
251
|
getAll: (name: string) => string[];
|
|
252
|
+
get: (name: string) => string | null;
|
|
206
253
|
};
|
|
207
254
|
};
|
|
208
255
|
}
|
|
209
|
-
declare function isCmssyEditRequest(request: EditRequestLike
|
|
256
|
+
declare function isCmssyEditRequest(request: EditRequestLike, config: {
|
|
257
|
+
draftSecret: string;
|
|
258
|
+
}): Promise<boolean>;
|
|
210
259
|
declare function isCmssyEditMode(): Promise<boolean>;
|
|
211
260
|
|
|
212
261
|
declare const CMSSY_LOCALE_HEADER = "x-cmssy-locale";
|
|
@@ -215,7 +264,15 @@ declare function splitCmssyLocale(config: CmssyClientConfig, path: string[] | un
|
|
|
215
264
|
locale: string;
|
|
216
265
|
path: string[] | undefined;
|
|
217
266
|
}>;
|
|
218
|
-
|
|
267
|
+
/**
|
|
268
|
+
* Resolve the active locale. Pass `options.path` (route params) wherever
|
|
269
|
+
* possible - that path is static-safe. The no-argument form falls back to the
|
|
270
|
+
* `x-cmssy-locale` request header, which reads `headers()` and forces the
|
|
271
|
+
* calling route into dynamic rendering.
|
|
272
|
+
*/
|
|
273
|
+
declare function getCmssyLocale(config: CmssyClientConfig, options?: {
|
|
274
|
+
path?: string | string[];
|
|
275
|
+
}): Promise<string>;
|
|
219
276
|
|
|
220
277
|
/**
|
|
221
278
|
* Resolves the active locale from a pathname's leading segment. Uses the
|
|
@@ -299,4 +356,4 @@ declare class CmssyWebhookError extends Error {
|
|
|
299
356
|
*/
|
|
300
357
|
declare function verifyCmssyWebhook(options: VerifyCmssyWebhookOptions): CmssyWebhookEvent;
|
|
301
358
|
|
|
302
|
-
export { type BuildCmssyMetadataOptions, CMSSY_CART_COOKIE, CMSSY_EDIT_HEADER, CMSSY_LOCALE_HEADER, CMSSY_SESSION_COOKIE, type CmssyAuthMiddleware, type CmssyAuthRouteHandlers, type CmssyCartRouteHandlers, type CmssyCspOptions, type CmssyDraftRouteConfig, type CmssyEditorProps, type CmssyEnvConfig, type CmssyNextConfig, type CmssyOrdersRouteHandlers, CmssyWebhookError, type CreateCmssyNotFoundOptions, type CreateCmssyPageOptions, type CreateCmssyRobotsOptions, type CreateCmssySitemapOptions, DEFAULT_CMSSY_EDITOR_ORIGINS, SESSION_MAX_AGE_SECONDS, applyCmssyCsp, assertAuthConfig, buildCmssyMetadata, cmssyCspHeaders, createCmssyAuthMiddleware, createCmssyAuthRoute, createCmssyCartRoute, createCmssyLocaleMiddleware, createCmssyNotFound, createCmssyOrdersRoute, createCmssyPage, createCmssyRobots, createCmssySitemap, createDraftRoute, defineCmssyConfig, fetchOrderByToken, fetchProduct, fetchProducts, getCmssyAccessToken, getCmssyLocale, getCmssyUser, isAccessExpired, isCmssyEditMode, isCmssyEditRequest, localeForPathname, openSession, resolveEditorOrigin, resolveLocaleFromPathname, sealSession, sessionCookieOptions, splitCmssyLocale, verifyCmssyWebhook };
|
|
359
|
+
export { type BuildCmssyMetadataOptions, CMSSY_CART_COOKIE, CMSSY_EDIT_HEADER, CMSSY_EDIT_PATH_PREFIX, CMSSY_EDIT_QUERY_PARAM, CMSSY_LOCALE_HEADER, CMSSY_SECRET_QUERY_PARAM, CMSSY_SESSION_COOKIE, type CmssyAuthMiddleware, type CmssyAuthRouteHandlers, type CmssyCartRouteHandlers, type CmssyCspOptions, type CmssyDraftRouteConfig, type CmssyEditorProps, type CmssyEnvConfig, type CmssyNextConfig, type CmssyOrdersRouteHandlers, CmssyWebhookError, type CreateCmssyNotFoundOptions, type CreateCmssyPageOptions, type CreateCmssyRobotsOptions, type CreateCmssySitemapOptions, DEFAULT_CMSSY_EDITOR_ORIGINS, SESSION_MAX_AGE_SECONDS, applyCmssyCsp, assertAuthConfig, buildCmssyMetadata, cmssyCspHeaders, cmssyEditRewrite, createCmssyAuthMiddleware, createCmssyAuthRoute, createCmssyCartRoute, createCmssyEditMiddleware, createCmssyEditPage, createCmssyLocaleMiddleware, createCmssyNotFound, createCmssyOrdersRoute, createCmssyPage, createCmssyRobots, createCmssySitemap, createDraftRoute, defineCmssyConfig, fetchOrderByToken, fetchProduct, fetchProducts, getCmssyAccessToken, getCmssyLocale, getCmssyUser, isAccessExpired, isCmssyEditMode, isCmssyEditRequest, localeForPathname, openSession, resolveEditorOrigin, resolveLocaleFromPathname, sealSession, sessionCookieOptions, splitCmssyLocale, verifyCmssyWebhook };
|
package/dist/index.d.ts
CHANGED
|
@@ -5,8 +5,8 @@ export { DEFAULT_CMSSY_API_URL, FieldCondition, FieldConditionGroup, FieldCondit
|
|
|
5
5
|
import { EditBridgeConfig } from '@cmssy/react/client';
|
|
6
6
|
import { CmssyAuthConfig, CmssySessionPayload, SessionCookieOptions, CmssySessionUser, FetchProductOptions, CmssyProduct, FetchProductsOptions, CmssyProductPage, FetchOrderByTokenOptions, CmssyOrder, VerifyCmssyWebhookOptions, CmssyWebhookEvent } from '@cmssy/types';
|
|
7
7
|
export { CmssyAuthConfig, CmssyProductPage, CmssySessionPayload, CmssySessionUser, CmssyStockState, CmssyWebhookEvent, CmssyWebhookOrder, FetchOrderByTokenOptions, FetchProductOptions, FetchProductsOptions, MyOrdersResult, SessionCookieOptions, VerifyCmssyWebhookOptions } from '@cmssy/types';
|
|
8
|
-
import { MetadataRoute, Metadata } from 'next';
|
|
9
8
|
import { NextRequest, NextResponse } from 'next/server';
|
|
9
|
+
import { MetadataRoute, Metadata } from 'next';
|
|
10
10
|
|
|
11
11
|
declare const DEFAULT_CMSSY_EDITOR_ORIGINS: string[];
|
|
12
12
|
declare function resolveEditorOrigin(editorOrigin: string | string[] | undefined): string | string[];
|
|
@@ -88,7 +88,51 @@ interface CatchAllProps {
|
|
|
88
88
|
params?: Promise<CatchAllParams>;
|
|
89
89
|
searchParams?: Promise<SearchParams>;
|
|
90
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Public catch-all page. Statically renderable: it never reads
|
|
93
|
+
* `searchParams` or `headers()` - awaiting either forces every route
|
|
94
|
+
* dynamic and kills ISR (CMS-952). Draft-mode preview (the authenticated
|
|
95
|
+
* /api/draft cookie) still works per-request via `draftMode()`, which is
|
|
96
|
+
* static-safe, and renders draft CONTENT without the editor (CMS-948).
|
|
97
|
+
* The editor-iframe flow (`?cmssyEdit=1&cmssySecret=...`) is served by
|
|
98
|
+
* the middleware rewrite (`cmssyEditRewrite`) onto the dynamic edit route
|
|
99
|
+
* mounted with `createCmssyEditPage`.
|
|
100
|
+
*/
|
|
91
101
|
declare function createCmssyPage(config: CmssyNextConfig, blocks: BlockDefinition[], options?: CreateCmssyPageOptions): ({ params, searchParams, }: CatchAllProps) => Promise<react_jsx_runtime.JSX.Element>;
|
|
102
|
+
/**
|
|
103
|
+
* Editor page for the middleware-rewritten edit route
|
|
104
|
+
* (`app/__cmssy/edit/[[...path]]/page.tsx`, `dynamic = "force-dynamic"`).
|
|
105
|
+
* Re-verifies the `cmssyEdit` + `cmssySecret` pair itself, so a direct hit
|
|
106
|
+
* on the route path (bypassing the middleware) cannot mount the editor.
|
|
107
|
+
*/
|
|
108
|
+
declare function createCmssyEditPage(config: CmssyNextConfig, blocks: BlockDefinition[], options?: CreateCmssyPageOptions): ({ params, searchParams, }: CatchAllProps) => Promise<react_jsx_runtime.JSX.Element>;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Where the middleware rewrites editor-preview traffic. Mount the matching
|
|
112
|
+
* dynamic route in the consumer app:
|
|
113
|
+
*
|
|
114
|
+
* app/__cmssy/edit/[[...path]]/page.tsx
|
|
115
|
+
* export const dynamic = "force-dynamic";
|
|
116
|
+
* export default createCmssyEditPage(cmssy, blocks, { editor: Editor });
|
|
117
|
+
*/
|
|
118
|
+
declare const CMSSY_EDIT_PATH_PREFIX = "/__cmssy/edit";
|
|
119
|
+
/**
|
|
120
|
+
* Rewrite VERIFIED editor requests (`cmssyEdit=1` + a `cmssySecret` matching
|
|
121
|
+
* the site's draft secret, CMS-948) onto the dedicated dynamic edit route, so
|
|
122
|
+
* the public catch-all can stay fully static. A static page never sees its
|
|
123
|
+
* query string - this rewrite is what makes the editor iframe work at all
|
|
124
|
+
* once ISR is on. Draft-mode preview (the authenticated /api/draft cookie) is
|
|
125
|
+
* NOT rewritten: it renders draft content on the public route via
|
|
126
|
+
* `draftMode()`, without the editor. Returns null for normal traffic so it
|
|
127
|
+
* composes with other middleware.
|
|
128
|
+
*/
|
|
129
|
+
declare function cmssyEditRewrite(request: NextRequest, config: {
|
|
130
|
+
draftSecret: string;
|
|
131
|
+
}): Promise<NextResponse | null>;
|
|
132
|
+
/** Standalone middleware when the consumer has no other middleware to compose. */
|
|
133
|
+
declare function createCmssyEditMiddleware(config: {
|
|
134
|
+
draftSecret: string;
|
|
135
|
+
}): (request: NextRequest) => Promise<NextResponse>;
|
|
92
136
|
|
|
93
137
|
interface CreateCmssyNotFoundOptions {
|
|
94
138
|
/**
|
|
@@ -196,6 +240,8 @@ declare function cmssyCspHeaders(options: CmssyCspOptions): Record<string, strin
|
|
|
196
240
|
declare function applyCmssyCsp<T extends MutableHeaders>(response: T, options: CmssyCspOptions): T;
|
|
197
241
|
|
|
198
242
|
declare const CMSSY_EDIT_HEADER = "x-cmssy-edit";
|
|
243
|
+
declare const CMSSY_EDIT_QUERY_PARAM = "cmssyEdit";
|
|
244
|
+
declare const CMSSY_SECRET_QUERY_PARAM = "cmssySecret";
|
|
199
245
|
interface EditRequestLike {
|
|
200
246
|
cookies: {
|
|
201
247
|
has: (name: string) => boolean;
|
|
@@ -203,10 +249,13 @@ interface EditRequestLike {
|
|
|
203
249
|
nextUrl: {
|
|
204
250
|
searchParams: {
|
|
205
251
|
getAll: (name: string) => string[];
|
|
252
|
+
get: (name: string) => string | null;
|
|
206
253
|
};
|
|
207
254
|
};
|
|
208
255
|
}
|
|
209
|
-
declare function isCmssyEditRequest(request: EditRequestLike
|
|
256
|
+
declare function isCmssyEditRequest(request: EditRequestLike, config: {
|
|
257
|
+
draftSecret: string;
|
|
258
|
+
}): Promise<boolean>;
|
|
210
259
|
declare function isCmssyEditMode(): Promise<boolean>;
|
|
211
260
|
|
|
212
261
|
declare const CMSSY_LOCALE_HEADER = "x-cmssy-locale";
|
|
@@ -215,7 +264,15 @@ declare function splitCmssyLocale(config: CmssyClientConfig, path: string[] | un
|
|
|
215
264
|
locale: string;
|
|
216
265
|
path: string[] | undefined;
|
|
217
266
|
}>;
|
|
218
|
-
|
|
267
|
+
/**
|
|
268
|
+
* Resolve the active locale. Pass `options.path` (route params) wherever
|
|
269
|
+
* possible - that path is static-safe. The no-argument form falls back to the
|
|
270
|
+
* `x-cmssy-locale` request header, which reads `headers()` and forces the
|
|
271
|
+
* calling route into dynamic rendering.
|
|
272
|
+
*/
|
|
273
|
+
declare function getCmssyLocale(config: CmssyClientConfig, options?: {
|
|
274
|
+
path?: string | string[];
|
|
275
|
+
}): Promise<string>;
|
|
219
276
|
|
|
220
277
|
/**
|
|
221
278
|
* Resolves the active locale from a pathname's leading segment. Uses the
|
|
@@ -299,4 +356,4 @@ declare class CmssyWebhookError extends Error {
|
|
|
299
356
|
*/
|
|
300
357
|
declare function verifyCmssyWebhook(options: VerifyCmssyWebhookOptions): CmssyWebhookEvent;
|
|
301
358
|
|
|
302
|
-
export { type BuildCmssyMetadataOptions, CMSSY_CART_COOKIE, CMSSY_EDIT_HEADER, CMSSY_LOCALE_HEADER, CMSSY_SESSION_COOKIE, type CmssyAuthMiddleware, type CmssyAuthRouteHandlers, type CmssyCartRouteHandlers, type CmssyCspOptions, type CmssyDraftRouteConfig, type CmssyEditorProps, type CmssyEnvConfig, type CmssyNextConfig, type CmssyOrdersRouteHandlers, CmssyWebhookError, type CreateCmssyNotFoundOptions, type CreateCmssyPageOptions, type CreateCmssyRobotsOptions, type CreateCmssySitemapOptions, DEFAULT_CMSSY_EDITOR_ORIGINS, SESSION_MAX_AGE_SECONDS, applyCmssyCsp, assertAuthConfig, buildCmssyMetadata, cmssyCspHeaders, createCmssyAuthMiddleware, createCmssyAuthRoute, createCmssyCartRoute, createCmssyLocaleMiddleware, createCmssyNotFound, createCmssyOrdersRoute, createCmssyPage, createCmssyRobots, createCmssySitemap, createDraftRoute, defineCmssyConfig, fetchOrderByToken, fetchProduct, fetchProducts, getCmssyAccessToken, getCmssyLocale, getCmssyUser, isAccessExpired, isCmssyEditMode, isCmssyEditRequest, localeForPathname, openSession, resolveEditorOrigin, resolveLocaleFromPathname, sealSession, sessionCookieOptions, splitCmssyLocale, verifyCmssyWebhook };
|
|
359
|
+
export { type BuildCmssyMetadataOptions, CMSSY_CART_COOKIE, CMSSY_EDIT_HEADER, CMSSY_EDIT_PATH_PREFIX, CMSSY_EDIT_QUERY_PARAM, CMSSY_LOCALE_HEADER, CMSSY_SECRET_QUERY_PARAM, CMSSY_SESSION_COOKIE, type CmssyAuthMiddleware, type CmssyAuthRouteHandlers, type CmssyCartRouteHandlers, type CmssyCspOptions, type CmssyDraftRouteConfig, type CmssyEditorProps, type CmssyEnvConfig, type CmssyNextConfig, type CmssyOrdersRouteHandlers, CmssyWebhookError, type CreateCmssyNotFoundOptions, type CreateCmssyPageOptions, type CreateCmssyRobotsOptions, type CreateCmssySitemapOptions, DEFAULT_CMSSY_EDITOR_ORIGINS, SESSION_MAX_AGE_SECONDS, applyCmssyCsp, assertAuthConfig, buildCmssyMetadata, cmssyCspHeaders, cmssyEditRewrite, createCmssyAuthMiddleware, createCmssyAuthRoute, createCmssyCartRoute, createCmssyEditMiddleware, createCmssyEditPage, createCmssyLocaleMiddleware, createCmssyNotFound, createCmssyOrdersRoute, createCmssyPage, createCmssyRobots, createCmssySitemap, createDraftRoute, defineCmssyConfig, fetchOrderByToken, fetchProduct, fetchProducts, getCmssyAccessToken, getCmssyLocale, getCmssyUser, isAccessExpired, isCmssyEditMode, isCmssyEditRequest, localeForPathname, openSession, resolveEditorOrigin, resolveLocaleFromPathname, sealSession, sessionCookieOptions, splitCmssyLocale, verifyCmssyWebhook };
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { headers, draftMode, cookies } from 'next/headers';
|
|
2
2
|
import { notFound, redirect } from 'next/navigation';
|
|
3
3
|
import { createCmssyClient, resolveSiteLocales, splitLocaleFromPath, fetchPage, resolveForms, CmssyServerPage, fetchSiteConfig, fetchPageById, fetchPages, fetchPageMeta, normalizeSlug as normalizeSlug$1, resolveWorkspaceId, graphqlRequest, resolveApiUrl } from '@cmssy/react';
|
|
4
4
|
export { DEFAULT_CMSSY_API_URL, evaluateFieldConditionGroup, resolveApiUrl } from '@cmssy/react';
|
|
5
5
|
import { CmssyLocaleProvider } from '@cmssy/react/client';
|
|
6
6
|
import { EncryptJWT, jwtDecrypt } from 'jose';
|
|
7
7
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
8
|
-
import { createHmac, createHash, timingSafeEqual } from 'crypto';
|
|
9
8
|
import { NextResponse } from 'next/server';
|
|
9
|
+
import { createHmac, timingSafeEqual } from 'crypto';
|
|
10
10
|
|
|
11
11
|
// src/create-cmssy-page.tsx
|
|
12
12
|
var CMSSY_SESSION_COOKIE = "cmssy_session";
|
|
@@ -203,11 +203,63 @@ function applyCmssyCsp(response, options) {
|
|
|
203
203
|
response.headers.delete("X-Frame-Options");
|
|
204
204
|
return response;
|
|
205
205
|
}
|
|
206
|
-
|
|
206
|
+
|
|
207
|
+
// src/secret-match.ts
|
|
208
|
+
var MAX_SECRET_LENGTH = 256;
|
|
209
|
+
async function cmssySecretsMatch(a, b) {
|
|
210
|
+
if (a.length > MAX_SECRET_LENGTH || b.length > MAX_SECRET_LENGTH) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
const encoder = new TextEncoder();
|
|
214
|
+
const [ha, hb] = await Promise.all([
|
|
215
|
+
crypto.subtle.digest("SHA-256", encoder.encode(a)),
|
|
216
|
+
crypto.subtle.digest("SHA-256", encoder.encode(b))
|
|
217
|
+
]);
|
|
218
|
+
const va = new Uint8Array(ha);
|
|
219
|
+
const vb = new Uint8Array(hb);
|
|
220
|
+
let diff = 0;
|
|
221
|
+
for (let i = 0; i < va.length; i += 1) {
|
|
222
|
+
diff |= (va[i] ?? 0) ^ (vb[i] ?? 0);
|
|
223
|
+
}
|
|
224
|
+
return diff === 0;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// src/edit-mode.ts
|
|
228
|
+
var CMSSY_EDIT_HEADER = "x-cmssy-edit";
|
|
229
|
+
var CMSSY_EDIT_QUERY_PARAM = "cmssyEdit";
|
|
230
|
+
var CMSSY_SECRET_QUERY_PARAM = "cmssySecret";
|
|
231
|
+
async function isCmssyEditRequest(request2, config) {
|
|
232
|
+
if (request2.cookies.has("__prerender_bypass")) return true;
|
|
233
|
+
if (!request2.nextUrl.searchParams.getAll(CMSSY_EDIT_QUERY_PARAM).includes("1")) {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
const provided = request2.nextUrl.searchParams.get(CMSSY_SECRET_QUERY_PARAM);
|
|
237
|
+
if (!provided || !config.draftSecret) return false;
|
|
238
|
+
return cmssySecretsMatch(provided, config.draftSecret);
|
|
239
|
+
}
|
|
240
|
+
async function isCmssyEditMode() {
|
|
241
|
+
const h = await headers();
|
|
242
|
+
return h.get(CMSSY_EDIT_HEADER) === "1";
|
|
243
|
+
}
|
|
207
244
|
function hasEditFlag(value) {
|
|
208
245
|
return Array.isArray(value) ? value.includes("1") : value === "1";
|
|
209
246
|
}
|
|
247
|
+
function firstValue(value) {
|
|
248
|
+
return Array.isArray(value) ? value[0] : value;
|
|
249
|
+
}
|
|
250
|
+
async function resolveEditorRequest(query, draftSecret) {
|
|
251
|
+
if (!hasEditFlag(query[CMSSY_EDIT_QUERY_PARAM])) return false;
|
|
252
|
+
const provided = firstValue(query[CMSSY_SECRET_QUERY_PARAM]);
|
|
253
|
+
if (!provided || !draftSecret) return false;
|
|
254
|
+
return cmssySecretsMatch(provided, draftSecret);
|
|
255
|
+
}
|
|
210
256
|
function createCmssyPage(config, blocks, options) {
|
|
257
|
+
return buildCmssyPageRenderer(config, blocks, options, false);
|
|
258
|
+
}
|
|
259
|
+
function createCmssyEditPage(config, blocks, options) {
|
|
260
|
+
return buildCmssyPageRenderer(config, blocks, options, true);
|
|
261
|
+
}
|
|
262
|
+
function buildCmssyPageRenderer(config, blocks, options, editRoute) {
|
|
211
263
|
if (!Array.isArray(blocks)) {
|
|
212
264
|
throw new Error(
|
|
213
265
|
"cmssy: createCmssyPage(config, blocks) requires a blocks array \u2014 pass your defineBlock(...) array"
|
|
@@ -227,10 +279,16 @@ function createCmssyPage(config, blocks, options) {
|
|
|
227
279
|
}) {
|
|
228
280
|
const path = fixedPath ?? (params ? (await params).path ?? void 0 : void 0);
|
|
229
281
|
const { isEnabled } = await draftMode();
|
|
230
|
-
|
|
231
|
-
|
|
282
|
+
let editorActive = false;
|
|
283
|
+
if (editRoute) {
|
|
284
|
+
const query = searchParams ? await searchParams : {};
|
|
285
|
+
editorActive = await resolveEditorRequest(query, config.draftSecret);
|
|
286
|
+
if (!editorActive) {
|
|
287
|
+
notFound();
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const editMode = isEnabled || editorActive;
|
|
232
291
|
const devAllowed = isDevelopment() && Boolean(config.devToken?.trim());
|
|
233
|
-
const editorActive = editMode;
|
|
234
292
|
let locale;
|
|
235
293
|
let pagePath = path;
|
|
236
294
|
let defaultLocale;
|
|
@@ -339,6 +397,23 @@ function resolveBridgeOrigin(editorOrigin) {
|
|
|
339
397
|
}
|
|
340
398
|
return origins.length === 1 ? origins[0] : origins;
|
|
341
399
|
}
|
|
400
|
+
var CMSSY_EDIT_PATH_PREFIX = "/__cmssy/edit";
|
|
401
|
+
async function cmssyEditRewrite(request2, config) {
|
|
402
|
+
const { pathname, searchParams } = request2.nextUrl;
|
|
403
|
+
if (pathname.startsWith(CMSSY_EDIT_PATH_PREFIX)) return null;
|
|
404
|
+
if (!searchParams.getAll(CMSSY_EDIT_QUERY_PARAM).includes("1")) return null;
|
|
405
|
+
const provided = searchParams.get(CMSSY_SECRET_QUERY_PARAM);
|
|
406
|
+
if (!provided || !config.draftSecret) return null;
|
|
407
|
+
if (!await cmssySecretsMatch(provided, config.draftSecret)) return null;
|
|
408
|
+
const url = request2.nextUrl.clone();
|
|
409
|
+
url.pathname = `${CMSSY_EDIT_PATH_PREFIX}${pathname === "/" ? "" : pathname}`;
|
|
410
|
+
return NextResponse.rewrite(url);
|
|
411
|
+
}
|
|
412
|
+
function createCmssyEditMiddleware(config) {
|
|
413
|
+
return async function cmssyEditMiddleware(request2) {
|
|
414
|
+
return await cmssyEditRewrite(request2, config) ?? NextResponse.next();
|
|
415
|
+
};
|
|
416
|
+
}
|
|
342
417
|
function DefaultNotFound() {
|
|
343
418
|
return /* @__PURE__ */ jsxs(
|
|
344
419
|
"main",
|
|
@@ -594,11 +669,6 @@ async function buildCmssyMetadata(config, path, options = {}) {
|
|
|
594
669
|
};
|
|
595
670
|
}
|
|
596
671
|
var MIN_SECRET_LENGTH = 16;
|
|
597
|
-
function secretsMatch(a, b) {
|
|
598
|
-
const ha = createHash("sha256").update(a).digest();
|
|
599
|
-
const hb = createHash("sha256").update(b).digest();
|
|
600
|
-
return timingSafeEqual(ha, hb);
|
|
601
|
-
}
|
|
602
672
|
function safeRedirect(redirect2, fallback) {
|
|
603
673
|
if (!redirect2 || !redirect2.startsWith("/")) return fallback;
|
|
604
674
|
if (redirect2.startsWith("//") || redirect2.includes("\\")) return fallback;
|
|
@@ -619,15 +689,22 @@ function createDraftRoute(config) {
|
|
|
619
689
|
);
|
|
620
690
|
}
|
|
621
691
|
return async function GET(request2) {
|
|
692
|
+
const url = new URL(request2.url);
|
|
693
|
+
if (url.searchParams.get("disable") === "1") {
|
|
694
|
+
const draft2 = await draftMode();
|
|
695
|
+
draft2.disable();
|
|
696
|
+
redirect(
|
|
697
|
+
safeRedirect(url.searchParams.get("redirect"), fallbackRedirect)
|
|
698
|
+
);
|
|
699
|
+
}
|
|
622
700
|
if (config.draftSecret.length < MIN_SECRET_LENGTH) {
|
|
623
701
|
return new Response(
|
|
624
702
|
`cmssy: draftSecret must be at least ${MIN_SECRET_LENGTH} characters`,
|
|
625
703
|
{ status: 500 }
|
|
626
704
|
);
|
|
627
705
|
}
|
|
628
|
-
const url = new URL(request2.url);
|
|
629
706
|
const secret = url.searchParams.get("secret");
|
|
630
|
-
if (!secret || !
|
|
707
|
+
if (!secret || !await cmssySecretsMatch(secret, config.draftSecret)) {
|
|
631
708
|
return new Response("Invalid draft secret", { status: 401 });
|
|
632
709
|
}
|
|
633
710
|
const location = safeRedirect(
|
|
@@ -639,14 +716,6 @@ function createDraftRoute(config) {
|
|
|
639
716
|
redirect(location);
|
|
640
717
|
};
|
|
641
718
|
}
|
|
642
|
-
var CMSSY_EDIT_HEADER = "x-cmssy-edit";
|
|
643
|
-
function isCmssyEditRequest(request2) {
|
|
644
|
-
return request2.cookies.has("__prerender_bypass") || request2.nextUrl.searchParams.getAll("cmssyEdit").includes("1");
|
|
645
|
-
}
|
|
646
|
-
async function isCmssyEditMode() {
|
|
647
|
-
const h = await headers();
|
|
648
|
-
return h.get(CMSSY_EDIT_HEADER) === "1";
|
|
649
|
-
}
|
|
650
719
|
var CMSSY_LOCALE_HEADER = "x-cmssy-locale";
|
|
651
720
|
async function localeForPathname(config, pathname) {
|
|
652
721
|
const siteLocales = await resolveSiteLocales(config);
|
|
@@ -657,7 +726,12 @@ async function splitCmssyLocale(config, path) {
|
|
|
657
726
|
const siteLocales = await resolveSiteLocales(config);
|
|
658
727
|
return splitLocaleFromPath(path, siteLocales);
|
|
659
728
|
}
|
|
660
|
-
async function getCmssyLocale(config) {
|
|
729
|
+
async function getCmssyLocale(config, options) {
|
|
730
|
+
if (options?.path !== void 0) {
|
|
731
|
+
const siteLocales = await resolveSiteLocales(config);
|
|
732
|
+
const segments = Array.isArray(options.path) ? options.path.filter(Boolean) : options.path.split("/").filter(Boolean);
|
|
733
|
+
return splitLocaleFromPath(segments, siteLocales).locale;
|
|
734
|
+
}
|
|
661
735
|
const { headers: headers3 } = await import('next/headers');
|
|
662
736
|
const headerList = await headers3();
|
|
663
737
|
const fromHeader = headerList.get(CMSSY_LOCALE_HEADER);
|
|
@@ -1903,4 +1977,4 @@ function verifyCmssyWebhook(options) {
|
|
|
1903
1977
|
return parsed;
|
|
1904
1978
|
}
|
|
1905
1979
|
|
|
1906
|
-
export { CMSSY_CART_COOKIE, CMSSY_EDIT_HEADER, CMSSY_LOCALE_HEADER, CMSSY_SESSION_COOKIE, CmssyWebhookError, DEFAULT_CMSSY_EDITOR_ORIGINS, SESSION_MAX_AGE_SECONDS, applyCmssyCsp, assertAuthConfig, buildCmssyMetadata, cmssyCspHeaders, createCmssyAuthMiddleware, createCmssyAuthRoute, createCmssyCartRoute, createCmssyLocaleMiddleware, createCmssyNotFound, createCmssyOrdersRoute, createCmssyPage, createCmssyRobots, createCmssySitemap, createDraftRoute, defineCmssyConfig, fetchOrderByToken, fetchProduct, fetchProducts, getCmssyAccessToken, getCmssyLocale, getCmssyUser, isAccessExpired, isCmssyEditMode, isCmssyEditRequest, localeForPathname, openSession, resolveEditorOrigin, resolveLocaleFromPathname, sealSession, sessionCookieOptions, splitCmssyLocale, verifyCmssyWebhook };
|
|
1980
|
+
export { CMSSY_CART_COOKIE, CMSSY_EDIT_HEADER, CMSSY_EDIT_PATH_PREFIX, CMSSY_EDIT_QUERY_PARAM, CMSSY_LOCALE_HEADER, CMSSY_SECRET_QUERY_PARAM, CMSSY_SESSION_COOKIE, CmssyWebhookError, DEFAULT_CMSSY_EDITOR_ORIGINS, SESSION_MAX_AGE_SECONDS, applyCmssyCsp, assertAuthConfig, buildCmssyMetadata, cmssyCspHeaders, cmssyEditRewrite, createCmssyAuthMiddleware, createCmssyAuthRoute, createCmssyCartRoute, createCmssyEditMiddleware, createCmssyEditPage, createCmssyLocaleMiddleware, createCmssyNotFound, createCmssyOrdersRoute, createCmssyPage, createCmssyRobots, createCmssySitemap, createDraftRoute, defineCmssyConfig, fetchOrderByToken, fetchProduct, fetchProducts, getCmssyAccessToken, getCmssyLocale, getCmssyUser, isAccessExpired, isCmssyEditMode, isCmssyEditRequest, localeForPathname, openSession, resolveEditorOrigin, resolveLocaleFromPathname, sealSession, sessionCookieOptions, splitCmssyLocale, verifyCmssyWebhook };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cmssy/next",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Next.js App Router bindings for cmssy headless sites (createCmssyPage + draft preview)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cmssy",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"dist"
|
|
42
42
|
],
|
|
43
43
|
"peerDependencies": {
|
|
44
|
-
"@cmssy/react": "^
|
|
44
|
+
"@cmssy/react": "^4.0.0",
|
|
45
45
|
"next": ">=15",
|
|
46
46
|
"react": "^18.2.0 || ^19.0.0",
|
|
47
47
|
"react-dom": "^18.2.0 || ^19.0.0"
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"tsup": "^8.3.0",
|
|
56
56
|
"typescript": "^5.6.0",
|
|
57
57
|
"vitest": "^2.1.0",
|
|
58
|
-
"@cmssy/react": "
|
|
58
|
+
"@cmssy/react": "4.0.0"
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"jose": "^6.2.3",
|