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