@cmssy/next 2.6.0 → 3.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 +73 -21
- package/dist/index.d.cts +7 -2
- package/dist/index.d.ts +7 -2
- package/dist/index.js +73 -23
- package/package.json +4 -4
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,10 +204,56 @@ 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) {
|
|
212
258
|
if (!Array.isArray(blocks)) {
|
|
213
259
|
throw new Error(
|
|
@@ -229,9 +275,9 @@ function createCmssyPage(config, blocks, options) {
|
|
|
229
275
|
const path = fixedPath ?? (params ? (await params).path ?? void 0 : void 0);
|
|
230
276
|
const { isEnabled } = await headers.draftMode();
|
|
231
277
|
const query = searchParams ? await searchParams : {};
|
|
232
|
-
const
|
|
278
|
+
const editorActive = await resolveEditorRequest(query, config.draftSecret);
|
|
279
|
+
const editMode = isEnabled || editorActive;
|
|
233
280
|
const devAllowed = isDevelopment() && Boolean(config.devToken?.trim());
|
|
234
|
-
const editorActive = editMode;
|
|
235
281
|
let locale;
|
|
236
282
|
let pagePath = path;
|
|
237
283
|
let defaultLocale;
|
|
@@ -595,11 +641,6 @@ async function buildCmssyMetadata(config, path, options = {}) {
|
|
|
595
641
|
};
|
|
596
642
|
}
|
|
597
643
|
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
644
|
function safeRedirect(redirect2, fallback) {
|
|
604
645
|
if (!redirect2 || !redirect2.startsWith("/")) return fallback;
|
|
605
646
|
if (redirect2.startsWith("//") || redirect2.includes("\\")) return fallback;
|
|
@@ -620,15 +661,22 @@ function createDraftRoute(config) {
|
|
|
620
661
|
);
|
|
621
662
|
}
|
|
622
663
|
return async function GET(request2) {
|
|
664
|
+
const url = new URL(request2.url);
|
|
665
|
+
if (url.searchParams.get("disable") === "1") {
|
|
666
|
+
const draft2 = await headers.draftMode();
|
|
667
|
+
draft2.disable();
|
|
668
|
+
navigation.redirect(
|
|
669
|
+
safeRedirect(url.searchParams.get("redirect"), fallbackRedirect)
|
|
670
|
+
);
|
|
671
|
+
}
|
|
623
672
|
if (config.draftSecret.length < MIN_SECRET_LENGTH) {
|
|
624
673
|
return new Response(
|
|
625
674
|
`cmssy: draftSecret must be at least ${MIN_SECRET_LENGTH} characters`,
|
|
626
675
|
{ status: 500 }
|
|
627
676
|
);
|
|
628
677
|
}
|
|
629
|
-
const url = new URL(request2.url);
|
|
630
678
|
const secret = url.searchParams.get("secret");
|
|
631
|
-
if (!secret || !
|
|
679
|
+
if (!secret || !await cmssySecretsMatch(secret, config.draftSecret)) {
|
|
632
680
|
return new Response("Invalid draft secret", { status: 401 });
|
|
633
681
|
}
|
|
634
682
|
const location = safeRedirect(
|
|
@@ -640,14 +688,6 @@ function createDraftRoute(config) {
|
|
|
640
688
|
navigation.redirect(location);
|
|
641
689
|
};
|
|
642
690
|
}
|
|
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
691
|
var CMSSY_LOCALE_HEADER = "x-cmssy-locale";
|
|
652
692
|
async function localeForPathname(config, pathname) {
|
|
653
693
|
const siteLocales = await react.resolveSiteLocales(config);
|
|
@@ -1587,10 +1627,10 @@ function createCmssyAuthMiddleware(config) {
|
|
|
1587
1627
|
return refreshed;
|
|
1588
1628
|
};
|
|
1589
1629
|
}
|
|
1590
|
-
var PRODUCTS_QUERY = `query Products($workspaceId: String!, $modelSlug: String!, $filter: JSON, $stockState: String, $limit: Int, $offset: Int, $sort: String) {
|
|
1630
|
+
var PRODUCTS_QUERY = `query Products($workspaceId: String!, $modelSlug: String!, $filter: JSON, $stockState: String, $locale: String, $limit: Int, $offset: Int, $sort: String) {
|
|
1591
1631
|
public {
|
|
1592
1632
|
model {
|
|
1593
|
-
records(workspaceId: $workspaceId, modelSlug: $modelSlug, filter: $filter, stockState: $stockState, limit: $limit, offset: $offset, sort: $sort) {
|
|
1633
|
+
records(workspaceId: $workspaceId, modelSlug: $modelSlug, filter: $filter, stockState: $stockState, locale: $locale, limit: $limit, offset: $offset, sort: $sort) {
|
|
1594
1634
|
total
|
|
1595
1635
|
hasMore
|
|
1596
1636
|
items {
|
|
@@ -1603,8 +1643,16 @@ var PRODUCTS_QUERY = `query Products($workspaceId: String!, $modelSlug: String!,
|
|
|
1603
1643
|
}
|
|
1604
1644
|
}
|
|
1605
1645
|
}`;
|
|
1646
|
+
async function requestLocale(config) {
|
|
1647
|
+
try {
|
|
1648
|
+
return await getCmssyLocale(config);
|
|
1649
|
+
} catch {
|
|
1650
|
+
return null;
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1606
1653
|
async function fetchProducts(config, options) {
|
|
1607
1654
|
const workspaceId = await react.resolveWorkspaceId(config);
|
|
1655
|
+
const locale = options.locale ?? await requestLocale(config);
|
|
1608
1656
|
const data = await react.graphqlRequest(
|
|
1609
1657
|
config,
|
|
1610
1658
|
PRODUCTS_QUERY,
|
|
@@ -1613,6 +1661,7 @@ async function fetchProducts(config, options) {
|
|
|
1613
1661
|
modelSlug: options.modelSlug,
|
|
1614
1662
|
filter: options.filter ?? {},
|
|
1615
1663
|
stockState: options.stockState ?? null,
|
|
1664
|
+
locale,
|
|
1616
1665
|
limit: options.limit ?? 50,
|
|
1617
1666
|
offset: options.offset ?? 0,
|
|
1618
1667
|
sort: options.sort ?? null
|
|
@@ -1626,6 +1675,7 @@ async function fetchProduct(config, options) {
|
|
|
1626
1675
|
const page = await fetchProducts(config, {
|
|
1627
1676
|
modelSlug: options.modelSlug,
|
|
1628
1677
|
filter: { [options.slugField ?? "slug"]: options.slug },
|
|
1678
|
+
locale: options.locale,
|
|
1629
1679
|
limit: 1
|
|
1630
1680
|
});
|
|
1631
1681
|
return page.items[0] ?? null;
|
|
@@ -1908,7 +1958,9 @@ Object.defineProperty(exports, "resolveApiUrl", {
|
|
|
1908
1958
|
});
|
|
1909
1959
|
exports.CMSSY_CART_COOKIE = CMSSY_CART_COOKIE;
|
|
1910
1960
|
exports.CMSSY_EDIT_HEADER = CMSSY_EDIT_HEADER;
|
|
1961
|
+
exports.CMSSY_EDIT_QUERY_PARAM = CMSSY_EDIT_QUERY_PARAM;
|
|
1911
1962
|
exports.CMSSY_LOCALE_HEADER = CMSSY_LOCALE_HEADER;
|
|
1963
|
+
exports.CMSSY_SECRET_QUERY_PARAM = CMSSY_SECRET_QUERY_PARAM;
|
|
1912
1964
|
exports.CMSSY_SESSION_COOKIE = CMSSY_SESSION_COOKIE;
|
|
1913
1965
|
exports.CmssyWebhookError = CmssyWebhookError;
|
|
1914
1966
|
exports.DEFAULT_CMSSY_EDITOR_ORIGINS = DEFAULT_CMSSY_EDITOR_ORIGINS;
|
package/dist/index.d.cts
CHANGED
|
@@ -196,6 +196,8 @@ declare function cmssyCspHeaders(options: CmssyCspOptions): Record<string, strin
|
|
|
196
196
|
declare function applyCmssyCsp<T extends MutableHeaders>(response: T, options: CmssyCspOptions): T;
|
|
197
197
|
|
|
198
198
|
declare const CMSSY_EDIT_HEADER = "x-cmssy-edit";
|
|
199
|
+
declare const CMSSY_EDIT_QUERY_PARAM = "cmssyEdit";
|
|
200
|
+
declare const CMSSY_SECRET_QUERY_PARAM = "cmssySecret";
|
|
199
201
|
interface EditRequestLike {
|
|
200
202
|
cookies: {
|
|
201
203
|
has: (name: string) => boolean;
|
|
@@ -203,10 +205,13 @@ interface EditRequestLike {
|
|
|
203
205
|
nextUrl: {
|
|
204
206
|
searchParams: {
|
|
205
207
|
getAll: (name: string) => string[];
|
|
208
|
+
get: (name: string) => string | null;
|
|
206
209
|
};
|
|
207
210
|
};
|
|
208
211
|
}
|
|
209
|
-
declare function isCmssyEditRequest(request: EditRequestLike
|
|
212
|
+
declare function isCmssyEditRequest(request: EditRequestLike, config: {
|
|
213
|
+
draftSecret: string;
|
|
214
|
+
}): Promise<boolean>;
|
|
210
215
|
declare function isCmssyEditMode(): Promise<boolean>;
|
|
211
216
|
|
|
212
217
|
declare const CMSSY_LOCALE_HEADER = "x-cmssy-locale";
|
|
@@ -299,4 +304,4 @@ declare class CmssyWebhookError extends Error {
|
|
|
299
304
|
*/
|
|
300
305
|
declare function verifyCmssyWebhook(options: VerifyCmssyWebhookOptions): CmssyWebhookEvent;
|
|
301
306
|
|
|
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 };
|
|
307
|
+
export { type BuildCmssyMetadataOptions, CMSSY_CART_COOKIE, CMSSY_EDIT_HEADER, 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, 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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -196,6 +196,8 @@ declare function cmssyCspHeaders(options: CmssyCspOptions): Record<string, strin
|
|
|
196
196
|
declare function applyCmssyCsp<T extends MutableHeaders>(response: T, options: CmssyCspOptions): T;
|
|
197
197
|
|
|
198
198
|
declare const CMSSY_EDIT_HEADER = "x-cmssy-edit";
|
|
199
|
+
declare const CMSSY_EDIT_QUERY_PARAM = "cmssyEdit";
|
|
200
|
+
declare const CMSSY_SECRET_QUERY_PARAM = "cmssySecret";
|
|
199
201
|
interface EditRequestLike {
|
|
200
202
|
cookies: {
|
|
201
203
|
has: (name: string) => boolean;
|
|
@@ -203,10 +205,13 @@ interface EditRequestLike {
|
|
|
203
205
|
nextUrl: {
|
|
204
206
|
searchParams: {
|
|
205
207
|
getAll: (name: string) => string[];
|
|
208
|
+
get: (name: string) => string | null;
|
|
206
209
|
};
|
|
207
210
|
};
|
|
208
211
|
}
|
|
209
|
-
declare function isCmssyEditRequest(request: EditRequestLike
|
|
212
|
+
declare function isCmssyEditRequest(request: EditRequestLike, config: {
|
|
213
|
+
draftSecret: string;
|
|
214
|
+
}): Promise<boolean>;
|
|
210
215
|
declare function isCmssyEditMode(): Promise<boolean>;
|
|
211
216
|
|
|
212
217
|
declare const CMSSY_LOCALE_HEADER = "x-cmssy-locale";
|
|
@@ -299,4 +304,4 @@ declare class CmssyWebhookError extends Error {
|
|
|
299
304
|
*/
|
|
300
305
|
declare function verifyCmssyWebhook(options: VerifyCmssyWebhookOptions): CmssyWebhookEvent;
|
|
301
306
|
|
|
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 };
|
|
307
|
+
export { type BuildCmssyMetadataOptions, CMSSY_CART_COOKIE, CMSSY_EDIT_HEADER, 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, 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 };
|
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,10 +203,56 @@ 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) {
|
|
211
257
|
if (!Array.isArray(blocks)) {
|
|
212
258
|
throw new Error(
|
|
@@ -228,9 +274,9 @@ function createCmssyPage(config, blocks, options) {
|
|
|
228
274
|
const path = fixedPath ?? (params ? (await params).path ?? void 0 : void 0);
|
|
229
275
|
const { isEnabled } = await draftMode();
|
|
230
276
|
const query = searchParams ? await searchParams : {};
|
|
231
|
-
const
|
|
277
|
+
const editorActive = await resolveEditorRequest(query, config.draftSecret);
|
|
278
|
+
const editMode = isEnabled || editorActive;
|
|
232
279
|
const devAllowed = isDevelopment() && Boolean(config.devToken?.trim());
|
|
233
|
-
const editorActive = editMode;
|
|
234
280
|
let locale;
|
|
235
281
|
let pagePath = path;
|
|
236
282
|
let defaultLocale;
|
|
@@ -594,11 +640,6 @@ async function buildCmssyMetadata(config, path, options = {}) {
|
|
|
594
640
|
};
|
|
595
641
|
}
|
|
596
642
|
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
643
|
function safeRedirect(redirect2, fallback) {
|
|
603
644
|
if (!redirect2 || !redirect2.startsWith("/")) return fallback;
|
|
604
645
|
if (redirect2.startsWith("//") || redirect2.includes("\\")) return fallback;
|
|
@@ -619,15 +660,22 @@ function createDraftRoute(config) {
|
|
|
619
660
|
);
|
|
620
661
|
}
|
|
621
662
|
return async function GET(request2) {
|
|
663
|
+
const url = new URL(request2.url);
|
|
664
|
+
if (url.searchParams.get("disable") === "1") {
|
|
665
|
+
const draft2 = await draftMode();
|
|
666
|
+
draft2.disable();
|
|
667
|
+
redirect(
|
|
668
|
+
safeRedirect(url.searchParams.get("redirect"), fallbackRedirect)
|
|
669
|
+
);
|
|
670
|
+
}
|
|
622
671
|
if (config.draftSecret.length < MIN_SECRET_LENGTH) {
|
|
623
672
|
return new Response(
|
|
624
673
|
`cmssy: draftSecret must be at least ${MIN_SECRET_LENGTH} characters`,
|
|
625
674
|
{ status: 500 }
|
|
626
675
|
);
|
|
627
676
|
}
|
|
628
|
-
const url = new URL(request2.url);
|
|
629
677
|
const secret = url.searchParams.get("secret");
|
|
630
|
-
if (!secret || !
|
|
678
|
+
if (!secret || !await cmssySecretsMatch(secret, config.draftSecret)) {
|
|
631
679
|
return new Response("Invalid draft secret", { status: 401 });
|
|
632
680
|
}
|
|
633
681
|
const location = safeRedirect(
|
|
@@ -639,14 +687,6 @@ function createDraftRoute(config) {
|
|
|
639
687
|
redirect(location);
|
|
640
688
|
};
|
|
641
689
|
}
|
|
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
690
|
var CMSSY_LOCALE_HEADER = "x-cmssy-locale";
|
|
651
691
|
async function localeForPathname(config, pathname) {
|
|
652
692
|
const siteLocales = await resolveSiteLocales(config);
|
|
@@ -1586,10 +1626,10 @@ function createCmssyAuthMiddleware(config) {
|
|
|
1586
1626
|
return refreshed;
|
|
1587
1627
|
};
|
|
1588
1628
|
}
|
|
1589
|
-
var PRODUCTS_QUERY = `query Products($workspaceId: String!, $modelSlug: String!, $filter: JSON, $stockState: String, $limit: Int, $offset: Int, $sort: String) {
|
|
1629
|
+
var PRODUCTS_QUERY = `query Products($workspaceId: String!, $modelSlug: String!, $filter: JSON, $stockState: String, $locale: String, $limit: Int, $offset: Int, $sort: String) {
|
|
1590
1630
|
public {
|
|
1591
1631
|
model {
|
|
1592
|
-
records(workspaceId: $workspaceId, modelSlug: $modelSlug, filter: $filter, stockState: $stockState, limit: $limit, offset: $offset, sort: $sort) {
|
|
1632
|
+
records(workspaceId: $workspaceId, modelSlug: $modelSlug, filter: $filter, stockState: $stockState, locale: $locale, limit: $limit, offset: $offset, sort: $sort) {
|
|
1593
1633
|
total
|
|
1594
1634
|
hasMore
|
|
1595
1635
|
items {
|
|
@@ -1602,8 +1642,16 @@ var PRODUCTS_QUERY = `query Products($workspaceId: String!, $modelSlug: String!,
|
|
|
1602
1642
|
}
|
|
1603
1643
|
}
|
|
1604
1644
|
}`;
|
|
1645
|
+
async function requestLocale(config) {
|
|
1646
|
+
try {
|
|
1647
|
+
return await getCmssyLocale(config);
|
|
1648
|
+
} catch {
|
|
1649
|
+
return null;
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1605
1652
|
async function fetchProducts(config, options) {
|
|
1606
1653
|
const workspaceId = await resolveWorkspaceId(config);
|
|
1654
|
+
const locale = options.locale ?? await requestLocale(config);
|
|
1607
1655
|
const data = await graphqlRequest(
|
|
1608
1656
|
config,
|
|
1609
1657
|
PRODUCTS_QUERY,
|
|
@@ -1612,6 +1660,7 @@ async function fetchProducts(config, options) {
|
|
|
1612
1660
|
modelSlug: options.modelSlug,
|
|
1613
1661
|
filter: options.filter ?? {},
|
|
1614
1662
|
stockState: options.stockState ?? null,
|
|
1663
|
+
locale,
|
|
1615
1664
|
limit: options.limit ?? 50,
|
|
1616
1665
|
offset: options.offset ?? 0,
|
|
1617
1666
|
sort: options.sort ?? null
|
|
@@ -1625,6 +1674,7 @@ async function fetchProduct(config, options) {
|
|
|
1625
1674
|
const page = await fetchProducts(config, {
|
|
1626
1675
|
modelSlug: options.modelSlug,
|
|
1627
1676
|
filter: { [options.slugField ?? "slug"]: options.slug },
|
|
1677
|
+
locale: options.locale,
|
|
1628
1678
|
limit: 1
|
|
1629
1679
|
});
|
|
1630
1680
|
return page.items[0] ?? null;
|
|
@@ -1893,4 +1943,4 @@ function verifyCmssyWebhook(options) {
|
|
|
1893
1943
|
return parsed;
|
|
1894
1944
|
}
|
|
1895
1945
|
|
|
1896
|
-
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 };
|
|
1946
|
+
export { CMSSY_CART_COOKIE, CMSSY_EDIT_HEADER, 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, 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 };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cmssy/next",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.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": "^3.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,11 +55,11 @@
|
|
|
55
55
|
"tsup": "^8.3.0",
|
|
56
56
|
"typescript": "^5.6.0",
|
|
57
57
|
"vitest": "^2.1.0",
|
|
58
|
-
"@cmssy/react": "
|
|
58
|
+
"@cmssy/react": "3.0.0"
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"jose": "^6.2.3",
|
|
62
|
-
"@cmssy/types": "0.
|
|
62
|
+
"@cmssy/types": "0.27.0"
|
|
63
63
|
},
|
|
64
64
|
"scripts": {
|
|
65
65
|
"build": "tsup",
|