@cmssy/next 4.7.2 → 5.0.1

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
@@ -1,2081 +1,42 @@
1
1
  'use strict';
2
2
 
3
- var headers = require('next/headers');
4
- var navigation = require('next/navigation');
5
- var react = require('@cmssy/react');
6
- var client = require('@cmssy/react/client');
7
- var jose = require('jose');
8
- var jsxRuntime = require('react/jsx-runtime');
9
- var server = require('next/server');
10
- var crypto$1 = require('crypto');
3
+ var core = require('@cmssy/core');
11
4
 
12
- // src/create-cmssy-page.tsx
13
- var CMSSY_SESSION_COOKIE = "cmssy_session";
14
- var SESSION_MAX_AGE_SECONDS = 30 * 24 * 60 * 60;
15
- var MIN_SESSION_SECRET_LENGTH = 32;
16
- var ACCESS_EXPIRY_SKEW_MS = 3e4;
17
- async function deriveSessionKey(secret) {
18
- if (typeof secret !== "string" || secret.length < MIN_SESSION_SECRET_LENGTH) {
19
- throw new Error(
20
- `cmssy auth sessionSecret must be at least ${MIN_SESSION_SECRET_LENGTH} characters`
21
- );
22
- }
23
- const digest = await crypto.subtle.digest(
24
- "SHA-256",
25
- new TextEncoder().encode(secret)
26
- );
27
- return new Uint8Array(digest);
28
- }
29
- async function sealSession(payload, secret, audience) {
30
- const key = await deriveSessionKey(secret);
31
- const jwt = new jose.EncryptJWT({ ...payload }).setProtectedHeader({ alg: "dir", enc: "A256GCM" }).setIssuedAt().setExpirationTime(`${SESSION_MAX_AGE_SECONDS}s`);
32
- if (audience) jwt.setAudience(audience);
33
- return jwt.encrypt(key);
34
- }
35
- async function openSession(token, secret, audience) {
36
- const key = await deriveSessionKey(secret);
37
- try {
38
- const { payload } = await jose.jwtDecrypt(token, key, {
39
- keyManagementAlgorithms: ["dir"],
40
- contentEncryptionAlgorithms: ["A256GCM"],
41
- ...audience ? { audience } : {}
42
- });
43
- const { accessToken, refreshToken, accessExpiresAt, user } = payload;
44
- if (typeof accessToken !== "string" || typeof refreshToken !== "string" || !Number.isFinite(accessExpiresAt) || !user || typeof user !== "object" || typeof user.recordId !== "string" || typeof user.email !== "string") {
45
- return null;
46
- }
47
- return {
48
- accessToken,
49
- refreshToken,
50
- accessExpiresAt,
51
- user: {
52
- recordId: user.recordId,
53
- email: user.email
54
- }
55
- };
56
- } catch {
57
- return null;
58
- }
59
- }
60
- function isAccessExpired(payload, now = Date.now()) {
61
- return payload.accessExpiresAt <= now + ACCESS_EXPIRY_SKEW_MS;
62
- }
63
- function sessionCookieOptions() {
64
- return {
65
- httpOnly: true,
66
- secure: process.env.NODE_ENV !== "development",
67
- sameSite: "lax",
68
- path: "/",
69
- maxAge: SESSION_MAX_AGE_SECONDS
70
- };
71
- }
72
5
 
73
- // src/config.ts
74
- var DEFAULT_CMSSY_EDITOR_ORIGINS = [
75
- "https://cmssy.io",
76
- "https://www.cmssy.io"
77
- ];
78
- function parseEditorOriginEnv(raw) {
79
- if (!raw) return void 0;
80
- const parts = raw.split(",").map((o) => o.trim()).filter((o) => o.length > 0);
81
- if (parts.length === 0) return void 0;
82
- return parts.length === 1 ? parts[0] : parts;
83
- }
84
- function isDevelopment() {
85
- return typeof process !== "undefined" && process.env.NODE_ENV === "development";
86
- }
87
- function resolveEditorOrigin(editorOrigin) {
88
- const value = editorOrigin ?? (typeof process !== "undefined" ? parseEditorOriginEnv(process.env.CMSSY_EDITOR_ORIGIN) : void 0);
89
- if (value === void 0) {
90
- return isDevelopment() ? "*" : DEFAULT_CMSSY_EDITOR_ORIGINS;
91
- }
92
- if (Array.isArray(value)) {
93
- const cleaned = value.filter((o) => o && o.trim().length > 0);
94
- return cleaned.length > 0 ? cleaned : DEFAULT_CMSSY_EDITOR_ORIGINS;
95
- }
96
- return value.trim().length > 0 ? value : DEFAULT_CMSSY_EDITOR_ORIGINS;
97
- }
98
- var REQUIRED_CONFIG_ENV = [
99
- ["org", "CMSSY_ORG_SLUG"],
100
- ["workspaceSlug", "CMSSY_WORKSPACE_SLUG"],
101
- ["draftSecret", "CMSSY_DRAFT_SECRET"]
102
- ];
103
- function defineCmssyConfig(config) {
104
- const resolved = { ...config };
105
- const missing = [];
106
- for (const [key, env] of REQUIRED_CONFIG_ENV) {
107
- const value = config[key];
108
- const trimmed = typeof value === "string" ? value.trim() : "";
109
- if (trimmed) {
110
- resolved[key] = trimmed;
111
- } else {
112
- missing.push(`${env} (config.${key})`);
113
- }
114
- }
115
- if (missing.length > 0) {
116
- if (typeof window !== "undefined") {
117
- throw new Error(
118
- `cmssy: the config was evaluated in the browser, so it cannot see the server's environment variables.
119
6
 
120
- This is an import problem, not a config problem: a client component ("use client") imported a VALUE from a module that reads the cmssy config - directly, or through a helper next to one.
121
-
122
- Fix it by importing types only (they are erased at build time), or by moving the value into a module that does not touch the config.`
123
- );
124
- }
125
- throw new Error(
126
- `cmssy: missing required configuration:
127
- - ${missing.join(
128
- "\n - "
129
- )}
130
- Set the listed environment variables (e.g. in .env.local) and restart the dev server.`
131
- );
132
- }
133
- return resolved;
134
- }
135
- function assertAuthConfig(config) {
136
- const auth = config.auth;
137
- if (!auth || typeof auth.modelSlug !== "string" || !auth.modelSlug) {
138
- throw new Error("cmssy: config.auth.modelSlug is required for auth routes");
139
- }
140
- if (typeof auth.sessionSecret !== "string" || auth.sessionSecret.length < MIN_SESSION_SECRET_LENGTH) {
141
- throw new Error(
142
- `cmssy: config.auth.sessionSecret must be at least ${MIN_SESSION_SECRET_LENGTH} characters`
143
- );
144
- }
145
- return auth;
146
- }
147
-
148
- // src/auth-server.ts
149
- async function readValidSession(config) {
150
- const auth = assertAuthConfig(config);
151
- const jar = await headers.cookies();
152
- const raw = jar.get(CMSSY_SESSION_COOKIE)?.value;
153
- if (!raw) return null;
154
- const session = await openSession(
155
- raw,
156
- auth.sessionSecret,
157
- config.workspaceSlug
158
- );
159
- if (!session || isAccessExpired(session)) return null;
160
- return session;
161
- }
162
- async function getCmssyUser(config) {
163
- const session = await readValidSession(config);
164
- return session?.user ?? null;
165
- }
166
- async function getCmssyAccessToken(config) {
167
- const session = await readValidSession(config);
168
- return session?.accessToken ?? null;
169
- }
170
-
171
- // src/csp.ts
172
- function toCspOrigin(origin) {
173
- if (origin === "*") return "*";
174
- let parsed;
175
- try {
176
- parsed = new URL(origin);
177
- } catch {
178
- throw new Error(`cmssy: invalid editorOrigin "${origin}"`);
179
- }
180
- if (parsed.origin === "null") {
181
- throw new Error(`cmssy: editorOrigin "${origin}" has no usable origin`);
182
- }
183
- return parsed.origin;
184
- }
185
- function frameAncestors(editorOrigin) {
186
- const resolved = resolveEditorOrigin(editorOrigin);
187
- const origins = Array.isArray(resolved) ? resolved : [resolved];
188
- if (origins.length === 0) {
189
- throw new Error(
190
- "cmssy: editorOrigin must contain at least one valid origin"
191
- );
192
- }
193
- const normalized = origins.map((origin) => toCspOrigin(origin.trim()));
194
- return `frame-ancestors ${normalized.join(" ")}`;
195
- }
196
- function cmssyCspHeaders(options) {
197
- return {
198
- "Content-Security-Policy": frameAncestors(options.editorOrigin)
199
- };
200
- }
201
- function mergeFrameAncestors(existing, directive) {
202
- if (!existing) return directive;
203
- const kept = existing.split(";").map((part) => part.trim()).filter((part) => part.length > 0 && !/^frame-ancestors\b/i.test(part));
204
- kept.push(directive);
205
- return kept.join("; ");
206
- }
207
- function applyCmssyCsp(response, options) {
208
- const merged = mergeFrameAncestors(
209
- response.headers.get("Content-Security-Policy"),
210
- frameAncestors(options.editorOrigin)
211
- );
212
- response.headers.set("Content-Security-Policy", merged);
213
- response.headers.delete("X-Frame-Options");
214
- return response;
215
- }
216
-
217
- // src/secret-match.ts
218
- var MAX_SECRET_LENGTH = 256;
219
- async function cmssySecretsMatch(a, b) {
220
- if (a.length > MAX_SECRET_LENGTH || b.length > MAX_SECRET_LENGTH) {
221
- return false;
222
- }
223
- const encoder = new TextEncoder();
224
- const [ha, hb] = await Promise.all([
225
- crypto.subtle.digest("SHA-256", encoder.encode(a)),
226
- crypto.subtle.digest("SHA-256", encoder.encode(b))
227
- ]);
228
- const va = new Uint8Array(ha);
229
- const vb = new Uint8Array(hb);
230
- let diff = 0;
231
- for (let i = 0; i < va.length; i += 1) {
232
- diff |= (va[i] ?? 0) ^ (vb[i] ?? 0);
233
- }
234
- return diff === 0;
235
- }
236
-
237
- // src/edit-mode.ts
238
- var CMSSY_EDIT_HEADER = "x-cmssy-edit";
239
- var CMSSY_EDIT_QUERY_PARAM = "cmssyEdit";
240
- var CMSSY_SECRET_QUERY_PARAM = "cmssySecret";
241
- async function isCmssyEditRequest(request2, config) {
242
- if (request2.cookies.has("__prerender_bypass")) return true;
243
- if (!request2.nextUrl.searchParams.getAll(CMSSY_EDIT_QUERY_PARAM).includes("1")) {
244
- return false;
245
- }
246
- const provided = request2.nextUrl.searchParams.get(CMSSY_SECRET_QUERY_PARAM);
247
- if (!provided || !config.draftSecret) return false;
248
- return cmssySecretsMatch(provided, config.draftSecret);
249
- }
250
- async function isCmssyEditMode() {
251
- const h = await headers.headers();
252
- return h.get(CMSSY_EDIT_HEADER) === "1";
253
- }
254
- function hasEditFlag(value) {
255
- return Array.isArray(value) ? value.includes("1") : value === "1";
256
- }
257
- function firstValue(value) {
258
- return Array.isArray(value) ? value[0] : value;
259
- }
260
- async function resolveEditorRequest(query, draftSecret) {
261
- if (!hasEditFlag(query[CMSSY_EDIT_QUERY_PARAM])) return false;
262
- const provided = firstValue(query[CMSSY_SECRET_QUERY_PARAM]);
263
- if (!provided || !draftSecret) return false;
264
- return cmssySecretsMatch(provided, draftSecret);
265
- }
266
- function createCmssyPage(config, blocks, options) {
267
- return buildCmssyPageRenderer(config, blocks, options, false);
268
- }
269
- function createCmssyEditPage(config, blocks, options) {
270
- return buildCmssyPageRenderer(config, blocks, options, true);
271
- }
272
- function buildCmssyPageRenderer(config, blocks, options, editRoute) {
273
- if (!Array.isArray(blocks)) {
274
- throw new Error(
275
- "cmssy: createCmssyPage(config, blocks) requires a blocks array \u2014 pass your defineBlock(...) array"
276
- );
277
- }
278
- const Editor = options?.editor;
279
- const clientConfig = {
280
- apiUrl: config.apiUrl,
281
- org: config.org,
282
- workspaceSlug: config.workspaceSlug
283
- };
284
- const client$1 = react.createCmssyClient(clientConfig);
285
- const fixedPath = options?.path?.split("/").map((segment) => segment.trim()).filter(Boolean);
286
- return async function CmssyCatchAllPage({
287
- params,
288
- searchParams
289
- }) {
290
- const path = fixedPath ?? (params ? (await params).path ?? void 0 : void 0);
291
- const { isEnabled } = await headers.draftMode();
292
- let editorActive = false;
293
- if (editRoute) {
294
- const query = searchParams ? await searchParams : {};
295
- editorActive = await resolveEditorRequest(query, config.draftSecret);
296
- if (!editorActive) {
297
- navigation.notFound();
298
- }
299
- }
300
- const editMode = isEnabled || editorActive;
301
- const devAllowed = isDevelopment() && Boolean(config.devToken?.trim());
302
- let locale;
303
- let pagePath = path;
304
- let defaultLocale;
305
- let enabledLocales = config.enabledLocales;
306
- if (config.resolveLocale) {
307
- defaultLocale = config.defaultLocale ?? (await react.resolveSiteLocales(clientConfig)).defaultLocale;
308
- locale = await config.resolveLocale();
309
- } else {
310
- const siteLocales = await react.resolveSiteLocales(clientConfig);
311
- defaultLocale = config.defaultLocale ?? siteLocales.defaultLocale;
312
- const locales = config.enabledLocales ?? siteLocales.locales;
313
- enabledLocales = locales;
314
- const split = react.splitLocaleFromPath(path, { defaultLocale, locales });
315
- locale = split.locale;
316
- pagePath = split.path;
317
- }
318
- const devWorkspaceId = devAllowed ? await client$1.resolveWorkspaceId() : void 0;
319
- const page = await react.fetchPage(clientConfig, pagePath, {
320
- previewSecret: editMode ? config.draftSecret : void 0,
321
- devPreview: devAllowed || void 0,
322
- devToken: devAllowed ? config.devToken : void 0,
323
- workspaceId: devWorkspaceId
324
- });
325
- if (!page) {
326
- navigation.notFound();
327
- }
328
- if (editorActive && !Editor) {
329
- throw new Error(
330
- 'cmssy: edit/dev mode requires options.editor \u2014 pass a "use client" editor that imports your blocks and renders <CmssyEditablePage blocks={blocks} \u2026 />'
331
- );
332
- }
333
- const resolvedForms = await react.resolveForms(
334
- clientConfig,
335
- page.blocks,
336
- locale,
337
- defaultLocale
338
- );
339
- const forms = Object.keys(resolvedForms).length > 0 ? resolvedForms : void 0;
340
- const localeContext = {
341
- current: locale,
342
- default: defaultLocale,
343
- enabled: enabledLocales && enabledLocales.length > 0 ? enabledLocales : Array.from(/* @__PURE__ */ new Set([defaultLocale, locale]))
344
- };
345
- if (editorActive && Editor) {
346
- const bridgeOrigin = resolveBridgeOrigin(config.editorOrigin);
347
- return /* @__PURE__ */ jsxRuntime.jsx(client.CmssyLocaleProvider, { value: localeContext, children: /* @__PURE__ */ jsxRuntime.jsx(
348
- Editor,
349
- {
350
- page,
351
- locale,
352
- defaultLocale,
353
- enabledLocales,
354
- edit: { editorOrigin: bridgeOrigin },
355
- forms
356
- }
357
- ) });
358
- }
359
- let auth;
360
- if (config.auth) {
361
- try {
362
- const user = await getCmssyUser(config);
363
- auth = {
364
- isAuthenticated: !!user,
365
- member: user ? { recordId: user.recordId, email: user.email } : null
366
- };
367
- } catch {
368
- auth = void 0;
369
- }
370
- }
371
- let workspace;
372
- try {
373
- workspace = {
374
- id: await client$1.resolveWorkspaceId(),
375
- slug: config.workspaceSlug
376
- };
377
- } catch {
378
- workspace = void 0;
379
- }
380
- return /* @__PURE__ */ jsxRuntime.jsx(client.CmssyLocaleProvider, { value: localeContext, children: /* @__PURE__ */ jsxRuntime.jsx(
381
- react.CmssyServerPage,
382
- {
383
- page,
384
- blocks,
385
- locale,
386
- defaultLocale,
387
- enabledLocales,
388
- forms,
389
- auth,
390
- workspace
391
- }
392
- ) });
393
- };
394
- }
395
- function resolveBridgeOrigin(editorOrigin) {
396
- const resolved = resolveEditorOrigin(editorOrigin);
397
- const origins = (Array.isArray(resolved) ? resolved : [resolved]).map(
398
- (origin) => toCspOrigin(origin.trim())
399
- );
400
- if (origins.length === 0) {
401
- throw new Error("cmssy: editorOrigin must be set to frame the editor");
402
- }
403
- if (origins.includes("*") && !isDevelopment()) {
404
- throw new Error(
405
- "cmssy: editorOrigin '*' is only allowed in development; set a concrete editor origin (e.g. https://cmssy.io) for production"
406
- );
407
- }
408
- return origins.length === 1 ? origins[0] : origins;
409
- }
410
- var CMSSY_EDIT_PATH_PREFIX = "/cmssy-edit";
411
- async function cmssyEditRewrite(request2, config, options = {}) {
412
- const { pathname, searchParams } = request2.nextUrl;
413
- if (pathname.startsWith(CMSSY_EDIT_PATH_PREFIX)) return null;
414
- if (!searchParams.getAll(CMSSY_EDIT_QUERY_PARAM).includes("1")) return null;
415
- const provided = searchParams.get(CMSSY_SECRET_QUERY_PARAM);
416
- if (!provided || !config.draftSecret) return null;
417
- if (!await cmssySecretsMatch(provided, config.draftSecret)) return null;
418
- const url = request2.nextUrl.clone();
419
- url.pathname = `${CMSSY_EDIT_PATH_PREFIX}${pathname === "/" ? "" : pathname}`;
420
- warnIfEditRouteMissing(url);
421
- return server.NextResponse.rewrite(
422
- url,
423
- options.requestHeaders ? { request: { headers: options.requestHeaders } } : void 0
424
- );
425
- }
426
- function createCmssyEditMiddleware(config) {
427
- return async function cmssyEditMiddleware(request2) {
428
- return await cmssyEditRewrite(request2, config) ?? server.NextResponse.next();
429
- };
430
- }
431
- var probed = false;
432
- function warnIfEditRouteMissing(url) {
433
- if (process.env.NODE_ENV === "production" || probed) return;
434
- probed = true;
435
- void fetch(url, { method: "HEAD" }).then((response) => {
436
- if (response.status !== 404) return;
437
- console.error(
438
- `[cmssy] The editor request was rewritten to ${url.pathname}, but nothing is mounted there (404). Add the edit route:
439
-
440
- // app/cmssy-edit/[[...path]]/page.tsx
441
- export const dynamic = "force-dynamic";
442
- export default createCmssyEditPage(cmssy, blocks, { editor: CmssyEditor });
443
-
444
- Until then the editor preview stays blank.`
445
- );
446
- }).catch(() => {
447
- });
448
- }
449
- function DefaultNotFound() {
450
- return /* @__PURE__ */ jsxRuntime.jsxs(
451
- "main",
452
- {
453
- style: {
454
- minHeight: "60vh",
455
- display: "flex",
456
- flexDirection: "column",
457
- alignItems: "center",
458
- justifyContent: "center",
459
- gap: "0.5rem",
460
- textAlign: "center",
461
- padding: "2rem"
462
- },
463
- children: [
464
- /* @__PURE__ */ jsxRuntime.jsx("h1", { style: { fontSize: "2rem", fontWeight: 700, margin: 0 }, children: "404" }),
465
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: { margin: 0, opacity: 0.7 }, children: "Page not found" })
466
- ]
467
- }
468
- );
469
- }
470
- function createCmssyNotFound(config, blocks, options) {
471
- if (!Array.isArray(blocks)) {
472
- throw new Error(
473
- "cmssy: createCmssyNotFound(config, blocks) requires a blocks array \u2014 pass your defineBlock(...) array"
474
- );
475
- }
476
- const clientConfig = {
477
- apiUrl: config.apiUrl,
478
- org: config.org,
479
- workspaceSlug: config.workspaceSlug
480
- };
481
- const fallback = options?.fallback ?? /* @__PURE__ */ jsxRuntime.jsx(DefaultNotFound, {});
482
- return async function CmssyNotFound() {
483
- try {
484
- const siteConfig = await react.fetchSiteConfig(clientConfig);
485
- const notFoundPageId = siteConfig?.notFoundPageId;
486
- if (!notFoundPageId) return fallback;
487
- const page = await react.fetchPageById(clientConfig, notFoundPageId);
488
- if (!page || page.blocks.length === 0) return fallback;
489
- let locale;
490
- let defaultLocale;
491
- let enabledLocales = config.enabledLocales;
492
- if (config.resolveLocale) {
493
- defaultLocale = config.defaultLocale ?? "en";
494
- locale = await config.resolveLocale();
495
- } else {
496
- const siteLocales = await react.resolveSiteLocales(clientConfig);
497
- defaultLocale = config.defaultLocale ?? siteLocales.defaultLocale;
498
- enabledLocales = config.enabledLocales ?? siteLocales.locales;
499
- locale = defaultLocale;
500
- }
501
- const resolvedForms = await react.resolveForms(
502
- clientConfig,
503
- page.blocks,
504
- locale,
505
- defaultLocale
506
- );
507
- const forms = Object.keys(resolvedForms).length > 0 ? resolvedForms : void 0;
508
- const localeContext = {
509
- current: locale,
510
- default: defaultLocale,
511
- enabled: enabledLocales && enabledLocales.length > 0 ? enabledLocales : Array.from(/* @__PURE__ */ new Set([defaultLocale, locale]))
512
- };
513
- return /* @__PURE__ */ jsxRuntime.jsx(client.CmssyLocaleProvider, { value: localeContext, children: /* @__PURE__ */ jsxRuntime.jsx(
514
- react.CmssyServerPage,
515
- {
516
- page,
517
- blocks,
518
- locale,
519
- defaultLocale,
520
- enabledLocales,
521
- forms
522
- }
523
- ) });
524
- } catch (err) {
525
- if (typeof console !== "undefined") {
526
- console.warn("[cmssy] not-found page render failed", err);
527
- }
528
- return fallback;
529
- }
530
- };
531
- }
532
-
533
- // src/seo-base-url.ts
534
- function trimTrailingSlash(url) {
535
- return url.replace(/\/+$/, "");
536
- }
537
- async function resolveSeoBaseUrl(config, option) {
538
- if (typeof option === "function") return trimTrailingSlash(await option());
539
- if (typeof option === "string" && option) return trimTrailingSlash(option);
540
- if (config.siteUrl) return trimTrailingSlash(config.siteUrl);
541
- const { headers: headers3 } = await import('next/headers');
542
- const h = await headers3();
543
- const host = h.get("host");
544
- if (!host) return "";
545
- const protocol = isLocalHost(host) ? "http" : "https";
546
- return `${protocol}://${host}`;
547
- }
548
- function isLocalHost(host) {
549
- const hostname = host.replace(/:\d+$/, "").replace(/^\[|\]$/g, "");
550
- if (hostname === "localhost" || hostname.endsWith(".localhost") || hostname.endsWith(".local") || hostname === "::1") {
551
- return true;
552
- }
553
- const ipv4 = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(hostname);
554
- if (!ipv4) return false;
555
- const [a, b] = [Number(ipv4[1]), Number(ipv4[2])];
556
- return a === 127 || a === 0 || a === 10 || a === 192 && b === 168 || a === 172 && b >= 16 && b <= 31;
557
- }
558
-
559
- // src/create-cmssy-robots.ts
560
- function createCmssyRobots(config, options = {}) {
561
- return async function robots() {
562
- const baseUrl = await resolveSeoBaseUrl(config, options.baseUrl);
563
- const rules = options.rules ?? {
564
- userAgent: "*",
565
- allow: "/",
566
- disallow: options.disallow ?? ["/api/"]
567
- };
568
- const includeSitemap = options.sitemap !== false && Boolean(baseUrl);
569
- return {
570
- rules,
571
- ...includeSitemap ? { sitemap: `${baseUrl}/sitemap.xml` } : {},
572
- ...options.host && baseUrl ? { host: baseUrl } : {}
573
- };
574
- };
575
- }
576
-
577
- // src/seo-paths.ts
578
- function resolveSeoLocales(config, siteConfig) {
579
- const defaultLocale = config.defaultLocale ?? siteConfig?.defaultLanguage ?? "en";
580
- const locales = config.enabledLocales && config.enabledLocales.length > 0 ? config.enabledLocales : siteConfig?.enabledLanguages && siteConfig.enabledLanguages.length > 0 ? siteConfig.enabledLanguages : [defaultLocale];
581
- return { defaultLocale, locales };
582
- }
583
- function normalizeSlug(slug) {
584
- if (slug === "/" || slug === "") return "/";
585
- return slug.startsWith("/") ? slug : `/${slug}`;
586
- }
587
- function localizedPath(slug, locale, defaultLocale) {
588
- const normalized = normalizeSlug(slug);
589
- const base = normalized === "/" ? "" : normalized;
590
- return locale === defaultLocale ? base || "/" : `/${locale}${base}`;
591
- }
592
-
593
- // src/create-cmssy-sitemap.ts
594
- function createCmssySitemap(config, options = {}) {
595
- const clientConfig = {
596
- apiUrl: config.apiUrl,
597
- org: config.org,
598
- workspaceSlug: config.workspaceSlug
599
- };
600
- return async function sitemap() {
601
- let pages = [];
602
- try {
603
- pages = await react.fetchPages(clientConfig);
604
- } catch (err) {
605
- if (typeof console !== "undefined") {
606
- console.warn("[cmssy] sitemap page fetch failed", err);
607
- }
608
- pages = [];
609
- }
610
- let siteConfig = null;
611
- try {
612
- siteConfig = await react.fetchSiteConfig(clientConfig);
613
- } catch {
614
- siteConfig = null;
615
- }
616
- const notFoundPageId = siteConfig?.notFoundPageId ?? null;
617
- const baseUrl = await resolveSeoBaseUrl(config, options.baseUrl);
618
- const { defaultLocale, locales } = resolveSeoLocales(config, siteConfig);
619
- const excluded = new Set((options.excludeSlugs ?? []).map(normalizeSlug));
620
- const languagesFor = (slug) => locales.length > 1 ? {
621
- languages: {
622
- ...Object.fromEntries(
623
- locales.map((locale) => [
624
- locale,
625
- `${baseUrl}${localizedPath(slug, locale, defaultLocale)}`
626
- ])
627
- ),
628
- "x-default": `${baseUrl}${localizedPath(slug, defaultLocale, defaultLocale)}`
629
- }
630
- } : void 0;
631
- const entries = pages.map((page) => ({ ...page, slug: normalizeSlug(page.slug) })).filter((page) => page.id !== notFoundPageId && !excluded.has(page.slug)).flatMap((page) => {
632
- const lastModified = page.updatedAt ?? page.publishedAt ?? void 0;
633
- const alternates = languagesFor(page.slug);
634
- return locales.map((locale) => ({
635
- url: `${baseUrl}${localizedPath(page.slug, locale, defaultLocale)}`,
636
- ...lastModified ? { lastModified: new Date(lastModified) } : {},
637
- ...alternates ? { alternates } : {}
638
- }));
639
- });
640
- if (!options.extra) return entries;
641
- const extra = typeof options.extra === "function" ? await options.extra({ baseUrl, defaultLocale, locales }) : options.extra;
642
- return [...entries, ...extra];
643
- };
644
- }
645
- function pick(value, locale, defaultLocale) {
646
- if (!value) return "";
647
- if (typeof value === "string") return value;
648
- return value[locale] || value[defaultLocale] || Object.values(value)[0] || "";
649
- }
650
- async function buildCmssyMetadata(config, path, options = {}) {
651
- const clientConfig = {
652
- apiUrl: config.apiUrl,
653
- org: config.org,
654
- workspaceSlug: config.workspaceSlug
655
- };
656
- const [siteConfig, baseUrl] = await Promise.all([
657
- react.fetchSiteConfig(clientConfig).catch(() => null),
658
- resolveSeoBaseUrl(config, options.baseUrl)
659
- ]);
660
- const { defaultLocale, locales: enabledLocales } = resolveSeoLocales(
661
- config,
662
- siteConfig
663
- );
664
- const segments = Array.isArray(path) ? path : path ? path.split("/").filter(Boolean) : void 0;
665
- const fromPath = react.splitLocaleFromPath(segments, {
666
- defaultLocale,
667
- locales: enabledLocales
668
- });
669
- const locale = options.locale ?? (fromPath.locale !== defaultLocale ? fromPath.locale : await config.resolveLocale?.() ?? defaultLocale);
670
- const slug = react.normalizeSlug(fromPath.path);
671
- const meta = await react.fetchPageMeta(clientConfig, slug).catch(() => null);
672
- const siteName = pick(siteConfig?.siteName, locale, defaultLocale) || siteConfig?.branding?.brandName || void 0;
673
- const title = pick(meta?.seoTitle, locale, defaultLocale) || pick(meta?.displayName, locale, defaultLocale) || siteName || "";
674
- const description = pick(meta?.seoDescription, locale, defaultLocale);
675
- const keywords = meta?.seoKeywords?.length ? meta.seoKeywords : void 0;
676
- const image = options.image ?? siteConfig?.branding?.ogImageUrl ?? void 0;
677
- const canonical = baseUrl ? `${baseUrl}${localizedPath(slug, locale, defaultLocale)}` : void 0;
678
- const languages = baseUrl && enabledLocales.length > 1 ? {
679
- ...Object.fromEntries(
680
- enabledLocales.map((l) => [
681
- l,
682
- `${baseUrl}${localizedPath(slug, l, defaultLocale)}`
683
- ])
684
- ),
685
- // What to serve a reader whose language none of these match.
686
- "x-default": `${baseUrl}${localizedPath(slug, defaultLocale, defaultLocale)}`
687
- } : void 0;
688
- return {
689
- ...baseUrl ? { metadataBase: new URL(baseUrl) } : {},
690
- ...title ? { title } : {},
691
- ...description ? { description } : {},
692
- ...keywords ? { keywords } : {},
693
- ...canonical || languages ? {
694
- alternates: {
695
- ...canonical ? { canonical } : {},
696
- ...languages ? { languages } : {}
697
- }
698
- } : {},
699
- openGraph: {
700
- ...title ? { title } : {},
701
- ...description ? { description } : {},
702
- ...canonical ? { url: canonical } : {},
703
- ...siteName ? { siteName } : {},
704
- type: options.ogType ?? "website",
705
- locale,
706
- ...image ? { images: [{ url: image }] } : {}
707
- },
708
- twitter: {
709
- card: image ? options.twitterCard ?? "summary_large_image" : "summary",
710
- ...title ? { title } : {},
711
- ...description ? { description } : {},
712
- ...image ? { images: [image] } : {}
713
- }
714
- };
715
- }
716
- var MIN_SECRET_LENGTH = 16;
717
- function safeRedirect(redirect2, fallback) {
718
- if (!redirect2 || !redirect2.startsWith("/")) return fallback;
719
- if (redirect2.startsWith("//") || redirect2.includes("\\")) return fallback;
720
- try {
721
- if (new URL(redirect2, "https://cmssy.invalid").origin !== "https://cmssy.invalid") {
722
- return fallback;
723
- }
724
- } catch {
725
- return fallback;
726
- }
727
- return redirect2;
728
- }
729
- function createDraftRoute(config) {
730
- const fallbackRedirect = config.defaultRedirect ?? "/";
731
- if (safeRedirect(fallbackRedirect, "/") !== fallbackRedirect) {
732
- throw new Error(
733
- "cmssy: defaultRedirect must be a same-origin path starting with '/'"
734
- );
735
- }
736
- return async function GET(request2) {
737
- const url = new URL(request2.url);
738
- if (url.searchParams.get("disable") === "1") {
739
- const draft2 = await headers.draftMode();
740
- draft2.disable();
741
- navigation.redirect(
742
- safeRedirect(url.searchParams.get("redirect"), fallbackRedirect)
743
- );
744
- }
745
- if (config.draftSecret.length < MIN_SECRET_LENGTH) {
746
- return new Response(
747
- `cmssy: draftSecret must be at least ${MIN_SECRET_LENGTH} characters`,
748
- { status: 500 }
749
- );
750
- }
751
- const secret = url.searchParams.get("secret");
752
- if (!secret || !await cmssySecretsMatch(secret, config.draftSecret)) {
753
- return new Response("Invalid draft secret", { status: 401 });
754
- }
755
- const location = safeRedirect(
756
- url.searchParams.get("redirect"),
757
- fallbackRedirect
758
- );
759
- const draft = await headers.draftMode();
760
- draft.enable();
761
- navigation.redirect(location);
762
- };
763
- }
764
- var CMSSY_LOCALE_HEADER = "x-cmssy-locale";
765
- async function localeForPathname(config, pathname) {
766
- const siteLocales = await react.resolveSiteLocales(config);
767
- const segments = pathname.split("/").filter(Boolean);
768
- return react.splitLocaleFromPath(segments, siteLocales).locale;
769
- }
770
- async function splitCmssyLocale(config, path) {
771
- const siteLocales = await react.resolveSiteLocales(config);
772
- return react.splitLocaleFromPath(path, siteLocales);
773
- }
774
- async function getCmssyLocale(config, options) {
775
- if (options?.path !== void 0) {
776
- const siteLocales = await react.resolveSiteLocales(config);
777
- const segments = Array.isArray(options.path) ? options.path.filter(Boolean) : options.path.split("/").filter(Boolean);
778
- return react.splitLocaleFromPath(segments, siteLocales).locale;
779
- }
780
- const { headers: headers3 } = await import('next/headers');
781
- const headerList = await headers3();
782
- const fromHeader = headerList.get(CMSSY_LOCALE_HEADER);
783
- if (fromHeader) return fromHeader;
784
- const { defaultLocale } = await react.resolveSiteLocales(config);
785
- return defaultLocale;
786
- }
787
- async function resolveLocaleFromPathname(config, pathname) {
788
- const segments = pathname.split("/").filter(Boolean);
789
- const hasStaticLocales = !!config.enabledLocales?.length;
790
- const fetched = config.defaultLocale && hasStaticLocales ? null : await react.resolveSiteLocales({
791
- apiUrl: config.apiUrl,
792
- org: config.org,
793
- workspaceSlug: config.workspaceSlug
794
- });
795
- const siteLocales = {
796
- defaultLocale: config.defaultLocale ?? fetched.defaultLocale,
797
- locales: hasStaticLocales ? config.enabledLocales : fetched.locales
798
- };
799
- return react.splitLocaleFromPath(segments, siteLocales).locale;
800
- }
801
- function createCmssyLocaleMiddleware(config) {
802
- return async function cmssyLocaleMiddleware(request2) {
803
- const locale = await resolveLocaleFromPathname(
804
- config,
805
- request2.nextUrl.pathname
806
- );
807
- const headers3 = new Headers(request2.headers);
808
- headers3.set(CMSSY_LOCALE_HEADER, locale);
809
- return server.NextResponse.next({ request: { headers: headers3 } });
810
- };
811
- }
812
- var LOGIN_MUTATION = `mutation SiteMemberLogin($input: SiteMemberLoginInput!) {
813
- siteMember {
814
- login(input: $input) {
815
- success message accessToken refreshToken accessTokenExpiresIn
816
- }
817
- }
818
- }`;
819
- var REGISTER_MUTATION = `mutation SiteMemberRegister($input: SiteMemberRegisterInput!) {
820
- siteMember {
821
- register(input: $input) { success message }
822
- }
823
- }`;
824
- var REFRESH_MUTATION = `mutation SiteMemberRefresh($refreshToken: String!) {
825
- siteMember {
826
- refresh(refreshToken: $refreshToken) {
827
- success message accessToken refreshToken accessTokenExpiresIn
828
- }
829
- }
830
- }`;
831
- var LOGOUT_MUTATION = `mutation SiteMemberLogout($refreshToken: String!) {
832
- siteMember {
833
- logout(refreshToken: $refreshToken) { success message }
834
- }
835
- }`;
836
- var LOGOUT_EVERYWHERE_MUTATION = `mutation SiteMemberLogoutEverywhere {
837
- siteMember {
838
- logoutEverywhere { success message }
839
- }
840
- }`;
841
- var FORGOT_MUTATION = `mutation SiteMemberForgotPassword($modelSlug: String!, $identity: String!) {
842
- siteMember {
843
- forgotPassword(modelSlug: $modelSlug, identity: $identity) { success message }
844
- }
845
- }`;
846
- var RESET_MUTATION = `mutation SiteMemberResetPassword($token: String!, $newPassword: String!) {
847
- siteMember {
848
- resetPassword(token: $token, newPassword: $newPassword) { success message }
849
- }
850
- }`;
851
- var VERIFY_MUTATION = `mutation SiteMemberVerifyEmail($token: String!) {
852
- siteMember {
853
- verifyEmail(token: $token) { success message }
854
- }
855
- }`;
856
- var workspaceIdCache = /* @__PURE__ */ new Map();
857
- function workspaceIdFor(config) {
858
- const key = `${react.resolveApiUrl(config.apiUrl)}::${config.workspaceSlug}`;
859
- const existing = workspaceIdCache.get(key);
860
- if (existing) return existing;
861
- const fresh = react.resolveWorkspaceId(config).catch((err) => {
862
- workspaceIdCache.delete(key);
863
- throw err;
864
- });
865
- workspaceIdCache.set(key, fresh);
866
- return fresh;
867
- }
868
- async function authRequest(config, query, variables, label, accessToken) {
869
- const workspaceId = await workspaceIdFor(config);
870
- return react.graphqlRequest(
871
- config,
872
- query,
873
- variables,
874
- {
875
- headers: {
876
- "x-workspace-id": workspaceId,
877
- ...accessToken ? { authorization: `Bearer ${accessToken}` } : {}
878
- }
879
- },
880
- label
881
- );
882
- }
883
- function decodeAccessClaims(accessToken) {
884
- const parts = accessToken.split(".");
885
- if (parts.length !== 3) return null;
886
- try {
887
- const base64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
888
- const bytes = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
889
- const json4 = JSON.parse(new TextDecoder().decode(bytes));
890
- if (typeof json4.recordId !== "string" || typeof json4.email !== "string" || json4.type !== "site_member") {
891
- return null;
892
- }
893
- return { recordId: json4.recordId, email: json4.email };
894
- } catch {
895
- return null;
896
- }
897
- }
898
- function toSessionPayload(result) {
899
- if (!result.success || !result.accessToken || !result.refreshToken || !result.accessTokenExpiresIn) {
900
- return null;
901
- }
902
- const user = decodeAccessClaims(result.accessToken);
903
- if (!user) return null;
904
- return {
905
- accessToken: result.accessToken,
906
- refreshToken: result.refreshToken,
907
- accessExpiresAt: Date.now() + result.accessTokenExpiresIn * 1e3,
908
- user
909
- };
910
- }
911
- async function backendSignIn(config, modelSlug, identity, password) {
912
- const data = await authRequest(
913
- config,
914
- LOGIN_MUTATION,
915
- {
916
- input: { modelSlug, identity, password }
917
- },
918
- "site member login"
919
- );
920
- return data.siteMember.login;
921
- }
922
- async function backendRegister(config, modelSlug, identity, password, fields) {
923
- const data = await authRequest(
924
- config,
925
- REGISTER_MUTATION,
926
- {
927
- input: { modelSlug, identity, password, fields }
928
- },
929
- "site member register"
930
- );
931
- return data.siteMember.register;
932
- }
933
- async function backendRefresh(config, refreshToken) {
934
- const data = await authRequest(
935
- config,
936
- REFRESH_MUTATION,
937
- { refreshToken },
938
- "site member refresh"
939
- );
940
- return data.siteMember.refresh;
941
- }
942
- async function backendSignOut(config, refreshToken) {
943
- try {
944
- await authRequest(
945
- config,
946
- LOGOUT_MUTATION,
947
- { refreshToken },
948
- "site member logout"
949
- );
950
- } catch {
951
- return;
952
- }
953
- }
954
- async function backendSignOutEverywhere(config, accessToken) {
955
- try {
956
- await authRequest(
957
- config,
958
- LOGOUT_EVERYWHERE_MUTATION,
959
- {},
960
- "site member logout everywhere",
961
- accessToken
962
- );
963
- } catch {
964
- return;
965
- }
966
- }
967
- async function backendForgotPassword(config, modelSlug, identity) {
968
- const data = await authRequest(
969
- config,
970
- FORGOT_MUTATION,
971
- { modelSlug, identity },
972
- "site member forgot password"
973
- );
974
- return data.siteMember.forgotPassword;
975
- }
976
- async function backendResetPassword(config, token, newPassword) {
977
- const data = await authRequest(
978
- config,
979
- RESET_MUTATION,
980
- { token, newPassword },
981
- "site member reset password"
982
- );
983
- return data.siteMember.resetPassword;
984
- }
985
- async function backendVerifyEmail(config, token) {
986
- const data = await authRequest(
987
- config,
988
- VERIFY_MUTATION,
989
- { token },
990
- "site member verify email"
991
- );
992
- return data.siteMember.verifyEmail;
993
- }
994
-
995
- // src/create-auth-route.ts
996
- var MAX_BODY_CHARS = 16 * 1024;
997
- function json(body, status = 200) {
998
- return new Response(JSON.stringify(body), {
999
- status,
1000
- headers: {
1001
- "content-type": "application/json",
1002
- "cache-control": "no-store"
1003
- }
1004
- });
1005
- }
1006
- async function readBody(request2) {
1007
- const contentType = request2.headers.get("content-type") ?? "";
1008
- if (!contentType.toLowerCase().includes("application/json")) {
1009
- throw new Error("content-type must be application/json");
1010
- }
1011
- const text = await request2.text();
1012
- if (text.length > MAX_BODY_CHARS) {
1013
- throw new Error("body too large");
1014
- }
1015
- if (!text) return {};
1016
- const parsed = JSON.parse(text);
1017
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1018
- throw new Error("body must be a JSON object");
1019
- }
1020
- return parsed;
1021
- }
1022
- function str(value) {
1023
- return typeof value === "string" ? value : "";
1024
- }
1025
- function plainObject(value) {
1026
- if (!value || typeof value !== "object" || Array.isArray(value)) return {};
1027
- return value;
1028
- }
1029
- async function readSession(config, auth) {
1030
- const jar = await headers.cookies();
1031
- const raw = jar.get(CMSSY_SESSION_COOKIE)?.value;
1032
- if (!raw) return null;
1033
- return openSession(raw, auth.sessionSecret, config.workspaceSlug);
1034
- }
1035
- async function writeSession(config, auth, payload) {
1036
- const sealed = await sealSession(
1037
- payload,
1038
- auth.sessionSecret,
1039
- config.workspaceSlug
1040
- );
1041
- const jar = await headers.cookies();
1042
- jar.set(CMSSY_SESSION_COOKIE, sealed, sessionCookieOptions());
1043
- }
1044
- async function clearSession() {
1045
- const jar = await headers.cookies();
1046
- jar.set(CMSSY_SESSION_COOKIE, "", {
1047
- ...sessionCookieOptions(),
1048
- maxAge: 0
1049
- });
1050
- }
1051
- async function refreshSession(config, auth, session) {
1052
- const result = await backendRefresh(config, session.refreshToken);
1053
- const payload = toSessionPayload(result);
1054
- if (!payload) {
1055
- await clearSession();
1056
- return null;
1057
- }
1058
- await writeSession(config, auth, payload);
1059
- return payload;
1060
- }
1061
- function createCmssyAuthRoute(config) {
1062
- const auth = assertAuthConfig(config);
1063
- async function handleSignIn(body) {
1064
- const identity = str(body.identity);
1065
- const password = str(body.password);
1066
- if (!identity || !password) {
1067
- return json({ ok: false, message: "Invalid credentials." }, 400);
1068
- }
1069
- const result = await backendSignIn(
1070
- config,
1071
- auth.modelSlug,
1072
- identity,
1073
- password
1074
- );
1075
- const payload = toSessionPayload(result);
1076
- if (!payload) {
1077
- return json({ ok: false, message: result.message }, 401);
1078
- }
1079
- await writeSession(config, auth, payload);
1080
- return json({ ok: true, user: payload.user });
1081
- }
1082
- async function handleRegister(body) {
1083
- const identity = str(body.identity);
1084
- const password = str(body.password);
1085
- if (!identity || !password) {
1086
- return json({ ok: false, message: "Invalid input." }, 400);
1087
- }
1088
- const result = await backendRegister(
1089
- config,
1090
- auth.modelSlug,
1091
- identity,
1092
- password,
1093
- plainObject(body.fields)
1094
- );
1095
- return json({ ok: result.success, message: result.message });
1096
- }
1097
- async function handleSignOut() {
1098
- const session = await readSession(config, auth);
1099
- if (session) {
1100
- await backendSignOut(config, session.refreshToken);
1101
- }
1102
- await clearSession();
1103
- return json({ ok: true });
1104
- }
1105
- async function handleSignOutEverywhere() {
1106
- let session = await readSession(config, auth);
1107
- if (session && isAccessExpired(session)) {
1108
- session = await refreshSession(config, auth, session);
1109
- }
1110
- if (session) {
1111
- await backendSignOutEverywhere(config, session.accessToken);
1112
- }
1113
- await clearSession();
1114
- return json({ ok: true });
1115
- }
1116
- async function handleRefresh() {
1117
- const session = await readSession(config, auth);
1118
- if (!session) {
1119
- return json({ ok: false, user: null }, 401);
1120
- }
1121
- const refreshed = await refreshSession(config, auth, session);
1122
- if (!refreshed) {
1123
- return json({ ok: false, user: null }, 401);
1124
- }
1125
- return json({ ok: true, user: refreshed.user });
1126
- }
1127
- async function handleMe() {
1128
- let session = await readSession(config, auth);
1129
- if (session && isAccessExpired(session)) {
1130
- session = await refreshSession(config, auth, session);
1131
- }
1132
- return json({ user: session?.user ?? null });
1133
- }
1134
- async function handleForgotPassword(body) {
1135
- const identity = str(body.identity);
1136
- if (!identity) {
1137
- return json({ ok: false, message: "Invalid input." }, 400);
1138
- }
1139
- const result = await backendForgotPassword(
1140
- config,
1141
- auth.modelSlug,
1142
- identity
1143
- );
1144
- return json({ ok: result.success, message: result.message });
1145
- }
1146
- async function handleResetPassword(body) {
1147
- const token = str(body.token);
1148
- const newPassword = str(body.newPassword);
1149
- if (!token || !newPassword) {
1150
- return json({ ok: false, message: "Invalid input." }, 400);
1151
- }
1152
- const result = await backendResetPassword(config, token, newPassword);
1153
- return json({ ok: result.success, message: result.message });
1154
- }
1155
- async function handleVerifyEmail(body) {
1156
- const token = str(body.token);
1157
- if (!token) {
1158
- return json({ ok: false, message: "Invalid input." }, 400);
1159
- }
1160
- const result = await backendVerifyEmail(config, token);
1161
- return json({ ok: result.success, message: result.message });
1162
- }
1163
- return {
1164
- async POST(request2, context) {
1165
- const { action } = await context.params;
1166
- let body;
1167
- try {
1168
- body = await readBody(request2);
1169
- } catch {
1170
- return json({ ok: false, message: "Invalid request body." }, 400);
1171
- }
1172
- try {
1173
- switch (action) {
1174
- case "sign-in":
1175
- return await handleSignIn(body);
1176
- case "register":
1177
- return await handleRegister(body);
1178
- case "sign-out":
1179
- return await handleSignOut();
1180
- case "sign-out-everywhere":
1181
- return await handleSignOutEverywhere();
1182
- case "refresh":
1183
- return await handleRefresh();
1184
- case "forgot-password":
1185
- return await handleForgotPassword(body);
1186
- case "reset-password":
1187
- return await handleResetPassword(body);
1188
- case "verify-email":
1189
- return await handleVerifyEmail(body);
1190
- default:
1191
- return json({ ok: false, message: "Not found." }, 404);
1192
- }
1193
- } catch {
1194
- return json({ ok: false, message: "Something went wrong." }, 500);
1195
- }
1196
- },
1197
- async GET(_request, context) {
1198
- const { action } = await context.params;
1199
- if (action !== "me") {
1200
- return json({ ok: false, message: "Not found." }, 404);
1201
- }
1202
- try {
1203
- return await handleMe();
1204
- } catch {
1205
- return json({ user: null });
1206
- }
1207
- }
1208
- };
1209
- }
1210
- var CART_FIELDS = `
1211
- id
1212
- status
1213
- itemCount
1214
- subtotal
1215
- currency
1216
- discountedTotal
1217
- tax
1218
- totalGross
1219
- pricesIncludeTax
1220
- shippingTotal
1221
- taxSummary { rateId name rate base amount }
1222
- shippingMethod { id label price etaLabel }
1223
- availableShippingMethods { id label price etaLabel }
1224
- appliedDiscount { code type value computedAmount }
1225
- items {
1226
- id
1227
- recordId
1228
- quantity
1229
- variantSelections
1230
- unitPrice
1231
- currentPrice
1232
- priceMismatch
1233
- snapshot { name price currency imageUrl sku tiers { minQty price } }
1234
- }
1235
- `;
1236
- var CART_QUERY = `query Cart($workspaceId: ID!) { cart { get(workspaceId: $workspaceId) { ${CART_FIELDS} } } }`;
1237
- var ADD_TO_CART = `mutation AddToCart($input: AddToCartInput!) { cart { addItem(input: $input) { ${CART_FIELDS} } } }`;
1238
- var UPDATE_ITEM = `mutation UpdateCartItem($input: UpdateCartItemInput!) { cart { updateItem(input: $input) { ${CART_FIELDS} } } }`;
1239
- var REMOVE_ITEM = `mutation RemoveCartItem($workspaceId: ID!, $itemId: ID!) { cart { removeItem(workspaceId: $workspaceId, itemId: $itemId) { ${CART_FIELDS} } } }`;
1240
- var CLEAR_CART = `mutation ClearCart($workspaceId: ID!) { cart { clear(workspaceId: $workspaceId) { ${CART_FIELDS} } } }`;
1241
- var APPLY_DISCOUNT = `mutation ApplyDiscount($workspaceId: ID!, $code: String!) { cart { applyDiscount(workspaceId: $workspaceId, code: $code) { ${CART_FIELDS} } } }`;
1242
- var REMOVE_DISCOUNT = `mutation RemoveDiscount($workspaceId: ID!) { cart { removeDiscount(workspaceId: $workspaceId) { ${CART_FIELDS} } } }`;
1243
- var SET_SHIPPING_METHOD = `mutation SetShippingMethod($workspaceId: ID!, $shippingMethodId: String) {
1244
- cart { setShippingMethod(workspaceId: $workspaceId, shippingMethodId: $shippingMethodId) { ${CART_FIELDS} } }
1245
- }`;
1246
- var MERGE_CART = `mutation MergeCart($workspaceId: ID!) { cart { merge(workspaceId: $workspaceId) { ${CART_FIELDS} } } }`;
1247
- var CHECKOUT = `mutation Checkout($input: CheckoutInput!) {
1248
- cart {
1249
- checkout(input: $input) {
1250
- id
1251
- orderNumber
1252
- status
1253
- subtotal
1254
- discount
1255
- appliedDiscount { code type value amount }
1256
- tax
1257
- total
1258
- currency
1259
- customerEmail
1260
- accessToken
1261
- poNumber
1262
- customerNote
1263
- shippingTotal
1264
- pricesIncludeTax
1265
- shippingMethod { id label price }
1266
- shippingAddress { name company line1 line2 postalCode city region country phone vatId }
1267
- taxSummary { rateId name rate base amount }
1268
- items { name sku quantity price listPrice tierMinQty currency taxRate taxAmount }
1269
- }
1270
- }
1271
- }`;
1272
- var PRODUCT = `query Product($workspaceId: String!, $modelSlug: String!, $filter: JSON) {
1273
- public {
1274
- model {
1275
- records(workspaceId: $workspaceId, modelSlug: $modelSlug, filter: $filter, limit: 1) {
1276
- items {
1277
- id
1278
- data
1279
- priceTiers { minQty price }
1280
- variants { id sku price inventory tiers { minQty price } selectedOptions { name value } }
1281
- }
1282
- }
1283
- }
1284
- }
1285
- }`;
1286
- var workspaceIdCache2 = /* @__PURE__ */ new Map();
1287
- function workspaceIdFor2(config) {
1288
- const key = `${react.resolveApiUrl(config.apiUrl)}::${config.workspaceSlug}`;
1289
- const existing = workspaceIdCache2.get(key);
1290
- if (existing) return existing;
1291
- const fresh = react.resolveWorkspaceId(config).catch((err) => {
1292
- workspaceIdCache2.delete(key);
1293
- throw err;
1294
- });
1295
- workspaceIdCache2.set(key, fresh);
1296
- return fresh;
1297
- }
1298
- async function request(config, ctx, workspaceId, query, variables, label) {
1299
- return react.graphqlRequest(
1300
- config,
1301
- query,
1302
- variables,
1303
- {
1304
- headers: {
1305
- "x-workspace-id": workspaceId,
1306
- "x-cart-session": ctx.cartToken,
1307
- ...ctx.accessToken ? { authorization: `Bearer ${ctx.accessToken}` } : {}
1308
- }
1309
- },
1310
- label
1311
- );
1312
- }
1313
- async function backendGetCart(config, ctx) {
1314
- const workspaceId = await workspaceIdFor2(config);
1315
- const data = await request(
1316
- config,
1317
- ctx,
1318
- workspaceId,
1319
- CART_QUERY,
1320
- { workspaceId },
1321
- "cart query"
1322
- );
1323
- return data.cart.get;
1324
- }
1325
- async function backendAddToCart(config, ctx, input) {
1326
- const workspaceId = await workspaceIdFor2(config);
1327
- const data = await request(
1328
- config,
1329
- ctx,
1330
- workspaceId,
1331
- ADD_TO_CART,
1332
- { input: { workspaceId, ...input } },
1333
- "add to cart"
1334
- );
1335
- return data.cart.addItem;
1336
- }
1337
- async function backendUpdateItem(config, ctx, input) {
1338
- const workspaceId = await workspaceIdFor2(config);
1339
- const data = await request(
1340
- config,
1341
- ctx,
1342
- workspaceId,
1343
- UPDATE_ITEM,
1344
- { input: { workspaceId, ...input } },
1345
- "update cart item"
1346
- );
1347
- return data.cart.updateItem;
1348
- }
1349
- async function backendRemoveItem(config, ctx, itemId) {
1350
- const workspaceId = await workspaceIdFor2(config);
1351
- const data = await request(
1352
- config,
1353
- ctx,
1354
- workspaceId,
1355
- REMOVE_ITEM,
1356
- { workspaceId, itemId },
1357
- "remove cart item"
1358
- );
1359
- return data.cart.removeItem;
1360
- }
1361
- async function backendClearCart(config, ctx) {
1362
- const workspaceId = await workspaceIdFor2(config);
1363
- const data = await request(
1364
- config,
1365
- ctx,
1366
- workspaceId,
1367
- CLEAR_CART,
1368
- { workspaceId },
1369
- "clear cart"
1370
- );
1371
- return data.cart.clear;
1372
- }
1373
- async function backendApplyDiscount(config, ctx, code) {
1374
- const workspaceId = await workspaceIdFor2(config);
1375
- const data = await request(
1376
- config,
1377
- ctx,
1378
- workspaceId,
1379
- APPLY_DISCOUNT,
1380
- { workspaceId, code },
1381
- "apply discount"
1382
- );
1383
- return data.cart.applyDiscount;
1384
- }
1385
- async function backendRemoveDiscount(config, ctx) {
1386
- const workspaceId = await workspaceIdFor2(config);
1387
- const data = await request(
1388
- config,
1389
- ctx,
1390
- workspaceId,
1391
- REMOVE_DISCOUNT,
1392
- { workspaceId },
1393
- "remove discount"
1394
- );
1395
- return data.cart.removeDiscount;
1396
- }
1397
- async function backendSetShippingMethod(config, ctx, shippingMethodId) {
1398
- const workspaceId = await workspaceIdFor2(config);
1399
- const data = await request(
1400
- config,
1401
- ctx,
1402
- workspaceId,
1403
- SET_SHIPPING_METHOD,
1404
- { workspaceId, shippingMethodId },
1405
- "set shipping method"
1406
- );
1407
- return data.cart.setShippingMethod;
1408
- }
1409
- async function backendMergeCart(config, ctx) {
1410
- const workspaceId = await workspaceIdFor2(config);
1411
- const data = await request(
1412
- config,
1413
- ctx,
1414
- workspaceId,
1415
- MERGE_CART,
1416
- { workspaceId },
1417
- "merge cart"
1418
- );
1419
- return data.cart.merge;
1420
- }
1421
- async function backendCheckout(config, ctx, input) {
1422
- const workspaceId = await workspaceIdFor2(config);
1423
- const data = await request(
1424
- config,
1425
- ctx,
1426
- workspaceId,
1427
- CHECKOUT,
1428
- { input: { workspaceId, ...input } },
1429
- "checkout"
1430
- );
1431
- return data.cart.checkout;
1432
- }
1433
- async function backendProduct(config, ctx, modelSlug, filter) {
1434
- const workspaceId = await workspaceIdFor2(config);
1435
- const data = await request(
1436
- config,
1437
- ctx,
1438
- workspaceId,
1439
- PRODUCT,
1440
- { workspaceId, modelSlug, filter },
1441
- "product query"
1442
- );
1443
- return data.public.model.records.items[0] ?? null;
1444
- }
1445
-
1446
- // src/create-cart-route.ts
1447
- var CMSSY_CART_COOKIE = "cmssy_cart";
1448
- var CART_MAX_AGE_SECONDS = 30 * 24 * 60 * 60;
1449
- var CART_TOKEN_BYTES = 32;
1450
- var MAX_BODY_CHARS2 = 16 * 1024;
1451
- function json2(body, status = 200) {
1452
- return new Response(JSON.stringify(body), {
1453
- status,
1454
- headers: {
1455
- "content-type": "application/json",
1456
- "cache-control": "no-store"
1457
- }
1458
- });
1459
- }
1460
- function cartCookieOptions() {
1461
- return {
1462
- httpOnly: true,
1463
- secure: process.env.NODE_ENV !== "development",
1464
- sameSite: "lax",
1465
- path: "/",
1466
- maxAge: CART_MAX_AGE_SECONDS
1467
- };
1468
- }
1469
- function mintToken() {
1470
- const bytes = new Uint8Array(CART_TOKEN_BYTES);
1471
- crypto.getRandomValues(bytes);
1472
- let binary = "";
1473
- for (const byte of bytes) binary += String.fromCharCode(byte);
1474
- return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1475
- }
1476
- async function readBody2(request2) {
1477
- const contentType = request2.headers.get("content-type") ?? "";
1478
- if (!contentType.toLowerCase().includes("application/json")) {
1479
- throw new Error("content-type must be application/json");
1480
- }
1481
- const text = await request2.text();
1482
- if (text.length > MAX_BODY_CHARS2) throw new Error("body too large");
1483
- if (!text) return {};
1484
- const parsed = JSON.parse(text);
1485
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1486
- throw new Error("body must be a JSON object");
1487
- }
1488
- return parsed;
1489
- }
1490
- function str2(value) {
1491
- return typeof value === "string" ? value : "";
1492
- }
1493
- function plainObject2(value) {
1494
- if (!value || typeof value !== "object" || Array.isArray(value)) return {};
1495
- return value;
1496
- }
1497
- function optionalStr(value) {
1498
- return typeof value === "string" && value.trim() ? value.trim() : null;
1499
- }
1500
- var ADDRESS_REQUIRED = [
1501
- "name",
1502
- "line1",
1503
- "postalCode",
1504
- "city",
1505
- "country"
1506
- ];
1507
- var CmssyAddressError = class extends Error {
1508
- constructor(missing) {
1509
- super(
1510
- `cmssy: shippingAddress is missing required field(s): ${missing.join(
1511
- ", "
1512
- )}. Expected { name, line1, postalCode, city, country } (plus optional company, line2, region, phone, vatId).`
1513
- );
1514
- this.missing = missing;
1515
- this.name = "CmssyAddressError";
1516
- }
1517
- missing;
1518
- };
1519
- function shippingAddress(value) {
1520
- if (value === void 0 || value === null) return null;
1521
- if (typeof value !== "object" || Array.isArray(value)) {
1522
- throw new CmssyAddressError([...ADDRESS_REQUIRED]);
1523
- }
1524
- const raw = value;
1525
- const missing = ADDRESS_REQUIRED.filter((key) => !optionalStr(raw[key]));
1526
- if (missing.length > 0) throw new CmssyAddressError(missing);
1527
- return {
1528
- name: optionalStr(raw.name),
1529
- company: optionalStr(raw.company),
1530
- line1: optionalStr(raw.line1),
1531
- line2: optionalStr(raw.line2),
1532
- postalCode: optionalStr(raw.postalCode),
1533
- city: optionalStr(raw.city),
1534
- region: optionalStr(raw.region),
1535
- country: optionalStr(raw.country),
1536
- phone: optionalStr(raw.phone),
1537
- vatId: optionalStr(raw.vatId)
1538
- };
1539
- }
1540
- function createCmssyCartRoute(config) {
1541
- async function ensureCartToken() {
1542
- const jar = await headers.cookies();
1543
- const existing = jar.get(CMSSY_CART_COOKIE)?.value;
1544
- if (existing) return existing;
1545
- const token = mintToken();
1546
- jar.set(CMSSY_CART_COOKIE, token, cartCookieOptions());
1547
- return token;
1548
- }
1549
- async function clearCartToken() {
1550
- const jar = await headers.cookies();
1551
- jar.set(CMSSY_CART_COOKIE, "", { ...cartCookieOptions(), maxAge: 0 });
1552
- }
1553
- async function memberAccessToken() {
1554
- if (!config.auth) return void 0;
1555
- const jar = await headers.cookies();
1556
- const raw = jar.get(CMSSY_SESSION_COOKIE)?.value;
1557
- if (!raw) return void 0;
1558
- const session = await openSession(
1559
- raw,
1560
- config.auth.sessionSecret,
1561
- config.workspaceSlug
1562
- );
1563
- if (!session || isAccessExpired(session)) return void 0;
1564
- return session.accessToken;
1565
- }
1566
- async function buildContext() {
1567
- const cartToken = await ensureCartToken();
1568
- const accessToken = await memberAccessToken();
1569
- return accessToken ? { cartToken, accessToken } : { cartToken };
1570
- }
1571
- return {
1572
- async POST(request2, context) {
1573
- const { action } = await context.params;
1574
- let body;
1575
- try {
1576
- body = await readBody2(request2);
1577
- } catch {
1578
- return json2({ message: "Invalid request body." }, 400);
1579
- }
1580
- try {
1581
- const ctx = await buildContext();
1582
- switch (action) {
1583
- case "cart":
1584
- return json2({ cart: await backendGetCart(config, ctx) });
1585
- case "add":
1586
- return json2({
1587
- cart: await backendAddToCart(config, ctx, {
1588
- recordId: str2(body.recordId),
1589
- quantity: typeof body.quantity === "number" ? body.quantity : 1,
1590
- variantSelections: body.variantSelections,
1591
- notes: typeof body.notes === "string" ? body.notes : void 0
1592
- })
1593
- });
1594
- case "update":
1595
- return json2({
1596
- cart: await backendUpdateItem(config, ctx, {
1597
- itemId: str2(body.itemId),
1598
- quantity: typeof body.quantity === "number" ? body.quantity : 0
1599
- })
1600
- });
1601
- case "remove":
1602
- return json2({
1603
- cart: await backendRemoveItem(config, ctx, str2(body.itemId))
1604
- });
1605
- case "clear":
1606
- return json2({ cart: await backendClearCart(config, ctx) });
1607
- case "apply-discount":
1608
- return json2({
1609
- cart: await backendApplyDiscount(config, ctx, str2(body.code))
1610
- });
1611
- case "remove-discount":
1612
- return json2({ cart: await backendRemoveDiscount(config, ctx) });
1613
- case "set-shipping":
1614
- return json2({
1615
- cart: await backendSetShippingMethod(
1616
- config,
1617
- ctx,
1618
- optionalStr(body.shippingMethodId)
1619
- )
1620
- });
1621
- case "merge":
1622
- return json2({ cart: await backendMergeCart(config, ctx) });
1623
- case "checkout": {
1624
- const order = await backendCheckout(config, ctx, {
1625
- customerEmail: str2(body.customerEmail),
1626
- poNumber: optionalStr(body.poNumber),
1627
- customerNote: optionalStr(body.customerNote),
1628
- shippingAddress: shippingAddress(body.shippingAddress)
1629
- });
1630
- await clearCartToken();
1631
- return json2({ order });
1632
- }
1633
- case "product":
1634
- return json2({
1635
- product: await backendProduct(
1636
- config,
1637
- ctx,
1638
- str2(body.modelSlug),
1639
- plainObject2(body.filter)
1640
- )
1641
- });
1642
- default:
1643
- return json2({ message: "Not found." }, 404);
1644
- }
1645
- } catch (err) {
1646
- if (err instanceof CmssyAddressError) {
1647
- return json2({ message: err.message, missing: err.missing }, 400);
1648
- }
1649
- return json2(
1650
- {
1651
- message: err instanceof Error ? err.message : "Commerce request failed"
1652
- },
1653
- 502
1654
- );
1655
- }
1656
- }
1657
- };
1658
- }
1659
- function isPrefetch(request2) {
1660
- return request2.headers.get("next-router-prefetch") !== null || request2.headers.get("purpose") === "prefetch" || (request2.headers.get("sec-purpose") ?? "").includes("prefetch");
1661
- }
1662
- function createCmssyAuthMiddleware(config) {
1663
- const auth = assertAuthConfig(config);
1664
- return async function cmssyAuthMiddleware(request2) {
1665
- const raw = request2.cookies.get(CMSSY_SESSION_COOKIE)?.value;
1666
- if (!raw) return server.NextResponse.next();
1667
- const session = await openSession(
1668
- raw,
1669
- auth.sessionSecret,
1670
- config.workspaceSlug
1671
- );
1672
- if (!session) {
1673
- const response = server.NextResponse.next();
1674
- response.cookies.set(CMSSY_SESSION_COOKIE, "", {
1675
- ...sessionCookieOptions(),
1676
- maxAge: 0
1677
- });
1678
- return response;
1679
- }
1680
- if (!isAccessExpired(session)) return server.NextResponse.next();
1681
- if (isPrefetch(request2)) return server.NextResponse.next();
1682
- let payload = null;
1683
- try {
1684
- const result = await backendRefresh(config, session.refreshToken);
1685
- payload = toSessionPayload(result);
1686
- } catch {
1687
- return server.NextResponse.next();
1688
- }
1689
- if (!payload) {
1690
- const cleared = server.NextResponse.next();
1691
- cleared.cookies.set(CMSSY_SESSION_COOKIE, "", {
1692
- ...sessionCookieOptions(),
1693
- maxAge: 0
1694
- });
1695
- return cleared;
1696
- }
1697
- const sealed = await sealSession(
1698
- payload,
1699
- auth.sessionSecret,
1700
- config.workspaceSlug
1701
- );
1702
- request2.cookies.set(CMSSY_SESSION_COOKIE, sealed);
1703
- const refreshed = server.NextResponse.next({ request: request2 });
1704
- refreshed.cookies.set(CMSSY_SESSION_COOKIE, sealed, sessionCookieOptions());
1705
- return refreshed;
1706
- };
1707
- }
1708
- var PRODUCTS_QUERY = `query Products($workspaceId: String!, $modelSlug: String!, $filter: JSON, $stockState: String, $locale: String, $limit: Int, $offset: Int, $sort: String) {
1709
- public {
1710
- model {
1711
- records(workspaceId: $workspaceId, modelSlug: $modelSlug, filter: $filter, stockState: $stockState, locale: $locale, limit: $limit, offset: $offset, sort: $sort) {
1712
- total
1713
- hasMore
1714
- items {
1715
- id
1716
- data
1717
- priceTiers { minQty price }
1718
- variants { id sku price inventory tiers { minQty price } selectedOptions { name value } }
1719
- }
1720
- }
1721
- }
1722
- }
1723
- }`;
1724
- async function requestLocale(config) {
1725
- try {
1726
- return await getCmssyLocale(config);
1727
- } catch {
1728
- return null;
1729
- }
1730
- }
1731
- async function fetchProducts(config, options) {
1732
- const workspaceId = await react.resolveWorkspaceId(config);
1733
- const locale = options.locale ?? await requestLocale(config);
1734
- const data = await react.graphqlRequest(
1735
- config,
1736
- PRODUCTS_QUERY,
1737
- {
1738
- workspaceId,
1739
- modelSlug: options.modelSlug,
1740
- filter: options.filter ?? {},
1741
- stockState: options.stockState ?? null,
1742
- locale,
1743
- limit: options.limit ?? 50,
1744
- offset: options.offset ?? 0,
1745
- sort: options.sort ?? null
1746
- },
1747
- { headers: { "x-workspace-id": workspaceId } },
1748
- "products query"
1749
- );
1750
- return data.public.model.records;
1751
- }
1752
- async function fetchProduct(config, options) {
1753
- const page = await fetchProducts(config, {
1754
- modelSlug: options.modelSlug,
1755
- filter: { [options.slugField ?? "slug"]: options.slug },
1756
- locale: options.locale,
1757
- limit: 1
1758
- });
1759
- return page.items[0] ?? null;
1760
- }
1761
- var ORDER_FIELDS = `
1762
- id
1763
- status
1764
- subtotal
1765
- discount
1766
- appliedDiscount { code type value amount }
1767
- tax
1768
- total
1769
- pricesIncludeTax
1770
- taxSummary { rateId name rate base amount }
1771
- currency
1772
- customerEmail
1773
- refundedAmount
1774
- paymentProvider
1775
- paymentStatus
1776
- fulfillmentStatus
1777
- amountPaid
1778
- balanceDue
1779
- paymentReference
1780
- trackingNumber
1781
- trackingCarrier
1782
- invoiceNumber
1783
- invoiceUrl
1784
- invoiceProvider
1785
- paidAt
1786
- fulfilledAt
1787
- createdAt
1788
- orderNumber
1789
- poNumber
1790
- customerNote
1791
- shippingTotal
1792
- shippingMethod { id label price }
1793
- shippingAddress { name company line1 line2 postalCode city region country phone vatId }
1794
- items { name price listPrice tierMinQty currency quantity sku }
1795
- payments { amount reference provider at }
1796
- `;
1797
- var PUBLIC_ORDER_FIELDS = `
1798
- id
1799
- orderNumber
1800
- status
1801
- paymentStatus
1802
- fulfillmentStatus
1803
- subtotal
1804
- discount
1805
- appliedDiscount { code type value amount }
1806
- tax
1807
- total
1808
- pricesIncludeTax
1809
- taxSummary { rateId name rate base amount }
1810
- currency
1811
- customerEmail
1812
- poNumber
1813
- customerNote
1814
- shippingTotal
1815
- shippingMethod { id label price }
1816
- shippingAddress { name company line1 line2 postalCode city region country phone vatId }
1817
- amountPaid
1818
- balanceDue
1819
- refundedAmount
1820
- trackingNumber
1821
- trackingCarrier
1822
- invoiceNumber
1823
- invoiceUrl
1824
- paidAt
1825
- fulfilledAt
1826
- createdAt
1827
- items { name price listPrice tierMinQty currency quantity sku taxRate taxAmount }
1828
- `;
1829
- var MY_ORDERS = `query MyOrders($workspaceId: ID!, $skip: Int, $limit: Int) {
1830
- account {
1831
- orders(workspaceId: $workspaceId, skip: $skip, limit: $limit) {
1832
- total
1833
- hasMore
1834
- items { ${ORDER_FIELDS} }
1835
- }
1836
- }
1837
- }`;
1838
- var MY_ORDER = `query MyOrder($workspaceId: ID!, $id: ID!) {
1839
- account {
1840
- order(workspaceId: $workspaceId, id: $id) { ${ORDER_FIELDS} }
1841
- }
1842
- }`;
1843
- var PUBLIC_ORDER_BY_TOKEN = `query PublicOrder($workspaceId: ID!, $orderId: ID!, $accessToken: String!) {
1844
- public {
1845
- order {
1846
- byToken(workspaceId: $workspaceId, orderId: $orderId, accessToken: $accessToken) {
1847
- ${PUBLIC_ORDER_FIELDS}
1848
- }
1849
- }
1850
- }
1851
- }`;
1852
- var workspaceIdCache3 = /* @__PURE__ */ new Map();
1853
- function workspaceIdFor3(config) {
1854
- const key = `${react.resolveApiUrl(config.apiUrl)}::${config.workspaceSlug}`;
1855
- const existing = workspaceIdCache3.get(key);
1856
- if (existing) return existing;
1857
- const fresh = react.resolveWorkspaceId(config).catch((err) => {
1858
- workspaceIdCache3.delete(key);
1859
- throw err;
1860
- });
1861
- workspaceIdCache3.set(key, fresh);
1862
- return fresh;
1863
- }
1864
- function headers2(workspaceId, accessToken) {
1865
- return {
1866
- "x-workspace-id": workspaceId,
1867
- authorization: `Bearer ${accessToken}`
1868
- };
1869
- }
1870
- async function backendMyOrders(config, accessToken, options) {
1871
- const workspaceId = await workspaceIdFor3(config);
1872
- const data = await react.graphqlRequest(
1873
- config,
1874
- MY_ORDERS,
1875
- { workspaceId, skip: options.skip, limit: options.limit },
1876
- { headers: headers2(workspaceId, accessToken) },
1877
- "my orders"
1878
- );
1879
- return data.account.orders;
1880
- }
1881
- async function backendMyOrder(config, accessToken, id) {
1882
- const workspaceId = await workspaceIdFor3(config);
1883
- const data = await react.graphqlRequest(
1884
- config,
1885
- MY_ORDER,
1886
- { workspaceId, id },
1887
- { headers: headers2(workspaceId, accessToken) },
1888
- "my order"
1889
- );
1890
- return data.account.order;
1891
- }
1892
- async function backendOrderByToken(config, options) {
1893
- const workspaceId = await workspaceIdFor3(config);
1894
- const data = await react.graphqlRequest(
1895
- config,
1896
- PUBLIC_ORDER_BY_TOKEN,
1897
- {
1898
- workspaceId,
1899
- orderId: options.orderId,
1900
- accessToken: options.accessToken
1901
- },
1902
- { headers: { "x-workspace-id": workspaceId } },
1903
- "public order lookup"
1904
- );
1905
- return data.public.order.byToken;
1906
- }
1907
-
1908
- // src/order-server.ts
1909
- async function fetchOrderByToken(config, options) {
1910
- return backendOrderByToken(config, options);
1911
- }
1912
- var DEFAULT_LIMIT = 20;
1913
- var MAX_LIMIT = 100;
1914
- function json3(body, status = 200) {
1915
- return new Response(JSON.stringify(body), {
1916
- status,
1917
- headers: {
1918
- "content-type": "application/json",
1919
- "cache-control": "no-store"
1920
- }
1921
- });
1922
- }
1923
- function createCmssyOrdersRoute(config) {
1924
- async function memberAccessToken() {
1925
- if (!config.auth) return void 0;
1926
- const jar = await headers.cookies();
1927
- const raw = jar.get(CMSSY_SESSION_COOKIE)?.value;
1928
- if (!raw) return void 0;
1929
- const session = await openSession(
1930
- raw,
1931
- config.auth.sessionSecret,
1932
- config.workspaceSlug
1933
- );
1934
- if (!session || isAccessExpired(session)) return void 0;
1935
- return session.accessToken;
1936
- }
1937
- return {
1938
- async GET(request2) {
1939
- const accessToken = await memberAccessToken();
1940
- if (!accessToken) {
1941
- return json3({ message: "Not signed in." }, 401);
1942
- }
1943
- const url = new URL(request2.url);
1944
- try {
1945
- const id = url.searchParams.get("id");
1946
- if (id) {
1947
- return json3({ order: await backendMyOrder(config, accessToken, id) });
1948
- }
1949
- const skip = Math.max(
1950
- 0,
1951
- Math.floor(Number(url.searchParams.get("skip")) || 0)
1952
- );
1953
- const limitParam = Math.floor(Number(url.searchParams.get("limit")));
1954
- const limit = Number.isFinite(limitParam) && limitParam > 0 ? Math.min(limitParam, MAX_LIMIT) : DEFAULT_LIMIT;
1955
- const result = await backendMyOrders(config, accessToken, {
1956
- skip,
1957
- limit
1958
- });
1959
- return json3(result);
1960
- } catch (err) {
1961
- return json3(
1962
- { message: err instanceof Error ? err.message : "Orders error" },
1963
- 502
1964
- );
1965
- }
1966
- }
1967
- };
1968
- }
1969
- var CmssyWebhookError = class extends Error {
1970
- constructor(message) {
1971
- super(message);
1972
- this.name = "CmssyWebhookError";
1973
- }
1974
- };
1975
- var DEFAULT_TOLERANCE_SECONDS = 300;
1976
- function parseSignatureHeader(header) {
1977
- let timestamp = null;
1978
- let signature = null;
1979
- for (const part of header.split(",")) {
1980
- const idx = part.indexOf("=");
1981
- if (idx === -1) continue;
1982
- const key = part.slice(0, idx).trim();
1983
- const value = part.slice(idx + 1).trim();
1984
- if (key === "t") timestamp = Number(value);
1985
- else if (key === "v1") signature = value;
1986
- }
1987
- if (timestamp === null || !Number.isFinite(timestamp) || !signature) {
1988
- throw new CmssyWebhookError("Malformed X-Cmssy-Signature header");
1989
- }
1990
- return { timestamp, signature };
1991
- }
1992
- function timingSafeHexEqual(expectedHex, providedHex) {
1993
- const expected = Buffer.from(expectedHex, "hex");
1994
- const provided = Buffer.from(providedHex, "hex");
1995
- if (expected.length !== provided.length) return false;
1996
- return crypto$1.timingSafeEqual(expected, provided);
1997
- }
1998
- function verifyCmssyWebhook(options) {
1999
- const { body, signatureHeader, secret } = options;
2000
- if (!signatureHeader) {
2001
- throw new CmssyWebhookError("Missing X-Cmssy-Signature header");
2002
- }
2003
- if (!secret) {
2004
- throw new CmssyWebhookError("Missing webhook secret");
2005
- }
2006
- const { timestamp, signature } = parseSignatureHeader(signatureHeader);
2007
- const toleranceMs = (options.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS) * 1e3;
2008
- const now = options.now ?? Date.now();
2009
- if (Math.abs(now - timestamp) > toleranceMs) {
2010
- throw new CmssyWebhookError("Webhook timestamp outside tolerance");
2011
- }
2012
- const expected = crypto$1.createHmac("sha256", secret).update(`${timestamp}.${body}`).digest("hex");
2013
- if (!timingSafeHexEqual(expected, signature)) {
2014
- throw new CmssyWebhookError("Webhook signature mismatch");
2015
- }
2016
- let parsed;
2017
- try {
2018
- parsed = JSON.parse(body);
2019
- } catch {
2020
- throw new CmssyWebhookError("Webhook body is not valid JSON");
2021
- }
2022
- return parsed;
2023
- }
2024
-
2025
- Object.defineProperty(exports, "DEFAULT_CMSSY_API_URL", {
7
+ Object.defineProperty(exports, "CMSSY_EDIT_HEADER", {
8
+ enumerable: true,
9
+ get: function () { return core.CMSSY_EDIT_HEADER; }
10
+ });
11
+ Object.defineProperty(exports, "CMSSY_EDIT_QUERY_PARAM", {
12
+ enumerable: true,
13
+ get: function () { return core.CMSSY_EDIT_QUERY_PARAM; }
14
+ });
15
+ Object.defineProperty(exports, "CMSSY_LOCALE_HEADER", {
16
+ enumerable: true,
17
+ get: function () { return core.CMSSY_LOCALE_HEADER; }
18
+ });
19
+ Object.defineProperty(exports, "CMSSY_SECRET_QUERY_PARAM", {
20
+ enumerable: true,
21
+ get: function () { return core.CMSSY_SECRET_QUERY_PARAM; }
22
+ });
23
+ Object.defineProperty(exports, "CMSSY_SESSION_COOKIE", {
24
+ enumerable: true,
25
+ get: function () { return core.CMSSY_SESSION_COOKIE; }
26
+ });
27
+ Object.defineProperty(exports, "DEFAULT_CMSSY_EDITOR_ORIGINS", {
28
+ enumerable: true,
29
+ get: function () { return core.DEFAULT_CMSSY_EDITOR_ORIGINS; }
30
+ });
31
+ Object.defineProperty(exports, "assertAuthConfig", {
2026
32
  enumerable: true,
2027
- get: function () { return react.DEFAULT_CMSSY_API_URL; }
33
+ get: function () { return core.assertAuthConfig; }
2028
34
  });
2029
- Object.defineProperty(exports, "evaluateFieldConditionGroup", {
35
+ Object.defineProperty(exports, "defineCmssyConfig", {
2030
36
  enumerable: true,
2031
- get: function () { return react.evaluateFieldConditionGroup; }
37
+ get: function () { return core.defineCmssyConfig; }
2032
38
  });
2033
- Object.defineProperty(exports, "resolveApiUrl", {
39
+ Object.defineProperty(exports, "resolveEditorOrigin", {
2034
40
  enumerable: true,
2035
- get: function () { return react.resolveApiUrl; }
41
+ get: function () { return core.resolveEditorOrigin; }
2036
42
  });
2037
- exports.CMSSY_CART_COOKIE = CMSSY_CART_COOKIE;
2038
- exports.CMSSY_EDIT_HEADER = CMSSY_EDIT_HEADER;
2039
- exports.CMSSY_EDIT_PATH_PREFIX = CMSSY_EDIT_PATH_PREFIX;
2040
- exports.CMSSY_EDIT_QUERY_PARAM = CMSSY_EDIT_QUERY_PARAM;
2041
- exports.CMSSY_LOCALE_HEADER = CMSSY_LOCALE_HEADER;
2042
- exports.CMSSY_SECRET_QUERY_PARAM = CMSSY_SECRET_QUERY_PARAM;
2043
- exports.CMSSY_SESSION_COOKIE = CMSSY_SESSION_COOKIE;
2044
- exports.CmssyWebhookError = CmssyWebhookError;
2045
- exports.DEFAULT_CMSSY_EDITOR_ORIGINS = DEFAULT_CMSSY_EDITOR_ORIGINS;
2046
- exports.SESSION_MAX_AGE_SECONDS = SESSION_MAX_AGE_SECONDS;
2047
- exports.applyCmssyCsp = applyCmssyCsp;
2048
- exports.assertAuthConfig = assertAuthConfig;
2049
- exports.buildCmssyMetadata = buildCmssyMetadata;
2050
- exports.cmssyCspHeaders = cmssyCspHeaders;
2051
- exports.cmssyEditRewrite = cmssyEditRewrite;
2052
- exports.createCmssyAuthMiddleware = createCmssyAuthMiddleware;
2053
- exports.createCmssyAuthRoute = createCmssyAuthRoute;
2054
- exports.createCmssyCartRoute = createCmssyCartRoute;
2055
- exports.createCmssyEditMiddleware = createCmssyEditMiddleware;
2056
- exports.createCmssyEditPage = createCmssyEditPage;
2057
- exports.createCmssyLocaleMiddleware = createCmssyLocaleMiddleware;
2058
- exports.createCmssyNotFound = createCmssyNotFound;
2059
- exports.createCmssyOrdersRoute = createCmssyOrdersRoute;
2060
- exports.createCmssyPage = createCmssyPage;
2061
- exports.createCmssyRobots = createCmssyRobots;
2062
- exports.createCmssySitemap = createCmssySitemap;
2063
- exports.createDraftRoute = createDraftRoute;
2064
- exports.defineCmssyConfig = defineCmssyConfig;
2065
- exports.fetchOrderByToken = fetchOrderByToken;
2066
- exports.fetchProduct = fetchProduct;
2067
- exports.fetchProducts = fetchProducts;
2068
- exports.getCmssyAccessToken = getCmssyAccessToken;
2069
- exports.getCmssyLocale = getCmssyLocale;
2070
- exports.getCmssyUser = getCmssyUser;
2071
- exports.isAccessExpired = isAccessExpired;
2072
- exports.isCmssyEditMode = isCmssyEditMode;
2073
- exports.isCmssyEditRequest = isCmssyEditRequest;
2074
- exports.localeForPathname = localeForPathname;
2075
- exports.openSession = openSession;
2076
- exports.resolveEditorOrigin = resolveEditorOrigin;
2077
- exports.resolveLocaleFromPathname = resolveLocaleFromPathname;
2078
- exports.sealSession = sealSession;
2079
- exports.sessionCookieOptions = sessionCookieOptions;
2080
- exports.splitCmssyLocale = splitCmssyLocale;
2081
- exports.verifyCmssyWebhook = verifyCmssyWebhook;