@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 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
- var EDIT_QUERY_PARAM = "cmssyEdit";
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 editMode = isEnabled || hasEditFlag(query[EDIT_QUERY_PARAM]);
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 || !secretsMatch(secret, config.draftSecret)) {
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): boolean;
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): boolean;
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 { draftMode, headers, cookies } from 'next/headers';
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
- var EDIT_QUERY_PARAM = "cmssyEdit";
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 editMode = isEnabled || hasEditFlag(query[EDIT_QUERY_PARAM]);
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 || !secretsMatch(secret, config.draftSecret)) {
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": "2.6.0",
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": "^2.1.0",
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": "2.6.0"
58
+ "@cmssy/react": "3.0.0"
59
59
  },
60
60
  "dependencies": {
61
61
  "jose": "^6.2.3",
62
- "@cmssy/types": "0.26.0"
62
+ "@cmssy/types": "0.27.0"
63
63
  },
64
64
  "scripts": {
65
65
  "build": "tsup",