@cmssy/next 9.10.0 → 10.1.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/server.js CHANGED
@@ -1,33 +1,14 @@
1
1
  import 'server-only';
2
- import { draftMode, headers, cookies } from 'next/headers';
2
+ import { draftMode, headers } from 'next/headers';
3
3
  import { notFound, redirect } from 'next/navigation';
4
- import { createCmssyClient, resolveSiteLocales, splitLocaleFromPath, fetchPage, resolveForms, resolveEditorBlockData, CmssyServerPage, fetchSiteConfig, fetchPageById, fetchLayouts, resolveEditorLayoutBlockData, CmssyServerLayout, normalizeSlug, fetchPageMeta, fetchPages } from '@cmssy/react';
5
- import { CmssyLocaleProvider } from '@cmssy/react/client';
6
- import { isDevelopment, CMSSY_SECRET_QUERY_PARAM, resolveEditorOrigin, toCspOrigin, CMSSY_EDIT_HEADER, localeForPath, CMSSY_LOCALE_HEADER, resolveSiteLocales as resolveSiteLocales$1, localesFromSiteConfig, localizedPath, normalizeSlug as normalizeSlug$1, assertAuthConfig, backendProduct, backendCheckout, backendMergeCart, backendSetShippingMethod, backendRemoveDiscount, backendApplyDiscount, backendClearCart, backendRemoveItem, backendUpdateItem, backendAddToCart, backendGetCart, backendMyOrder, backendMyOrders, cmssySecretsMatch, fetchProducts as fetchProducts$1, fetchProduct as fetchProduct$1, backendSignIn, toSessionPayload, backendRegister, backendSignOut, isAccessExpired, backendSignOutEverywhere, backendForgotPassword, backendResetPassword, backendVerifyEmail, CMSSY_SESSION_COOKIE, openSession, CMSSY_EDIT_QUERY_PARAM, sealSession, sessionCookieOptions, backendRefresh } from '@cmssy/core';
7
- import { jsx, jsxs } from 'react/jsx-runtime';
4
+ import { createCmssyClient, CmssyServerPage } from '@cmssy/react';
5
+ import { CmssyLocaleProvider } from '@cmssy/react/internal';
6
+ import { resolveEditorBlockData } from '@cmssy/react/internal-server';
7
+ import { isDevelopment, resolveSiteLocales, splitLocaleFromPath, fetchPage, resolveForms, toCspOrigin, cmssySecretsMatch } from '@cmssy/core/internal';
8
+ import { CMSSY_SECRET_QUERY_PARAM, resolveEditorOrigin, CMSSY_EDIT_HEADER, CMSSY_EDIT_QUERY_PARAM } from '@cmssy/core';
9
+ import { jsx } from 'react/jsx-runtime';
8
10
 
9
11
  // src/server.ts
10
- async function readValidSession(config) {
11
- const auth = assertAuthConfig(config);
12
- const jar = await cookies();
13
- const raw = jar.get(CMSSY_SESSION_COOKIE)?.value;
14
- if (!raw) return null;
15
- const session = await openSession(
16
- raw,
17
- auth.sessionSecret,
18
- config.workspaceSlug
19
- );
20
- if (!session || isAccessExpired(session)) return null;
21
- return session;
22
- }
23
- async function getCmssyUser(config) {
24
- const session = await readValidSession(config);
25
- return session?.user ?? null;
26
- }
27
- async function getCmssyAccessToken(config) {
28
- const session = await readValidSession(config);
29
- return session?.accessToken ?? null;
30
- }
31
12
  function hasEditFlag(value) {
32
13
  return Array.isArray(value) ? value.includes("1") : value === "1";
33
14
  }
@@ -143,18 +124,7 @@ function buildCmssyPageRenderer(config, blocks, options, editRoute) {
143
124
  }
144
125
  ) });
145
126
  }
146
- let auth;
147
- if (config.auth) {
148
- try {
149
- const user = await getCmssyUser(config);
150
- auth = {
151
- isAuthenticated: !!user,
152
- member: user ? { recordId: user.recordId, email: user.email } : null
153
- };
154
- } catch {
155
- auth = void 0;
156
- }
157
- }
127
+ const auth = void 0;
158
128
  let workspace;
159
129
  try {
160
130
  workspace = {
@@ -221,800 +191,6 @@ function resolveBridgeOrigin(editorOrigin) {
221
191
  }
222
192
  return origins.length === 1 ? origins[0] : origins;
223
193
  }
224
- function DefaultNotFound() {
225
- return /* @__PURE__ */ jsxs(
226
- "main",
227
- {
228
- style: {
229
- minHeight: "60vh",
230
- display: "flex",
231
- flexDirection: "column",
232
- alignItems: "center",
233
- justifyContent: "center",
234
- gap: "0.5rem",
235
- textAlign: "center",
236
- padding: "2rem"
237
- },
238
- children: [
239
- /* @__PURE__ */ jsx("h1", { style: { fontSize: "2rem", fontWeight: 700, margin: 0 }, children: "404" }),
240
- /* @__PURE__ */ jsx("p", { style: { margin: 0, opacity: 0.7 }, children: "Page not found" })
241
- ]
242
- }
243
- );
244
- }
245
- function createCmssyNotFound(config, blocks, options) {
246
- if (!Array.isArray(blocks)) {
247
- throw new Error(
248
- "cmssy: createCmssyNotFound(config, blocks) requires a blocks array \u2014 pass your defineBlock(...) array"
249
- );
250
- }
251
- const clientConfig = {
252
- apiUrl: config.apiUrl,
253
- org: config.org,
254
- workspaceSlug: config.workspaceSlug
255
- };
256
- const fallback = options?.fallback ?? /* @__PURE__ */ jsx(DefaultNotFound, {});
257
- return async function CmssyNotFound() {
258
- try {
259
- const siteConfig = await fetchSiteConfig(clientConfig);
260
- const notFoundPageId = siteConfig?.notFoundPageId;
261
- if (!notFoundPageId) return fallback;
262
- const page = await fetchPageById(clientConfig, notFoundPageId);
263
- if (!page || page.blocks.length === 0) return fallback;
264
- const { defaultLocale, locales: enabledLocales } = await resolveSiteLocales(clientConfig);
265
- const locale = config.resolveLocale ? await config.resolveLocale() : defaultLocale;
266
- const resolvedForms = await resolveForms(
267
- clientConfig,
268
- page.blocks,
269
- locale,
270
- defaultLocale
271
- );
272
- const forms = Object.keys(resolvedForms).length > 0 ? resolvedForms : void 0;
273
- const localeContext = {
274
- current: locale,
275
- default: defaultLocale,
276
- enabled: enabledLocales
277
- };
278
- return /* @__PURE__ */ jsx(CmssyLocaleProvider, { value: localeContext, children: /* @__PURE__ */ jsx(
279
- CmssyServerPage,
280
- {
281
- page,
282
- blocks,
283
- locale,
284
- defaultLocale,
285
- enabledLocales,
286
- forms
287
- }
288
- ) });
289
- } catch (err) {
290
- if (typeof console !== "undefined") {
291
- console.warn("[cmssy] not-found page render failed", err);
292
- }
293
- return fallback;
294
- }
295
- };
296
- }
297
- async function isCmssyEditMode() {
298
- const h = await headers();
299
- return h.get(CMSSY_EDIT_HEADER) === "1";
300
- }
301
- async function getCmssyLocale(config, options) {
302
- if (options?.path !== void 0) {
303
- return localeForPath(config, options.path);
304
- }
305
- const { headers: headers3 } = await import('next/headers');
306
- const headerList = await headers3();
307
- const fromHeader = headerList.get(CMSSY_LOCALE_HEADER);
308
- if (fromHeader) return fromHeader;
309
- const { defaultLocale } = await resolveSiteLocales$1(config);
310
- return defaultLocale;
311
- }
312
- async function CmssyLayoutSlot({
313
- config,
314
- blocks,
315
- position,
316
- page = "/",
317
- editable
318
- }) {
319
- const editMode = await isCmssyEditMode();
320
- const [groups, locale, siteLocales] = await Promise.all([
321
- fetchLayouts(
322
- config,
323
- page,
324
- editMode ? { previewSecret: config.draftSecret } : void 0
325
- ),
326
- getCmssyLocale(config),
327
- resolveSiteLocales(config)
328
- ]);
329
- if (editMode && editable) {
330
- const origin = resolveEditorOrigin(config.editorOrigin);
331
- const Editable = editable;
332
- const editorData = await resolveEditorLayoutBlockData({
333
- groups,
334
- blocks,
335
- position,
336
- locale,
337
- defaultLocale: siteLocales.defaultLocale,
338
- enabledLocales: siteLocales.locales,
339
- isPreview: true,
340
- config
341
- });
342
- return /* @__PURE__ */ jsx(
343
- Editable,
344
- {
345
- groups,
346
- position,
347
- locale,
348
- defaultLocale: siteLocales.defaultLocale,
349
- enabledLocales: siteLocales.locales,
350
- edit: {
351
- editorOrigin: (Array.isArray(origin) ? origin[0] : origin) ?? ""
352
- },
353
- data: editorData.data,
354
- resolvedContent: editorData.content
355
- }
356
- );
357
- }
358
- return /* @__PURE__ */ jsx(
359
- CmssyServerLayout,
360
- {
361
- groups,
362
- blocks,
363
- position,
364
- locale,
365
- defaultLocale: siteLocales.defaultLocale,
366
- enabledLocales: siteLocales.locales,
367
- config,
368
- editMode
369
- }
370
- );
371
- }
372
-
373
- // src/seo-base-url.ts
374
- function trimTrailingSlash(url) {
375
- return url.replace(/\/+$/, "");
376
- }
377
- async function resolveSeoBaseUrl(config, option) {
378
- if (typeof option === "function") return trimTrailingSlash(await option());
379
- if (typeof option === "string" && option) return trimTrailingSlash(option);
380
- if (config.siteUrl) return trimTrailingSlash(config.siteUrl);
381
- const { headers: headers3 } = await import('next/headers');
382
- const h = await headers3();
383
- const host = h.get("host");
384
- if (!host) return "";
385
- const protocol = isLocalHost(host) ? "http" : "https";
386
- return `${protocol}://${host}`;
387
- }
388
- function isLocalHost(host) {
389
- const hostname = host.replace(/:\d+$/, "").replace(/^\[|\]$/g, "");
390
- if (hostname === "localhost" || hostname.endsWith(".localhost") || hostname.endsWith(".local") || hostname === "::1") {
391
- return true;
392
- }
393
- const ipv4 = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(hostname);
394
- if (!ipv4) return false;
395
- const [a, b] = [Number(ipv4[1]), Number(ipv4[2])];
396
- return a === 127 || a === 0 || a === 10 || a === 192 && b === 168 || a === 172 && b >= 16 && b <= 31;
397
- }
398
- function pick(value, locale, defaultLocale) {
399
- if (!value) return "";
400
- if (typeof value === "string") return value;
401
- return value[locale] || value[defaultLocale] || Object.values(value)[0] || "";
402
- }
403
- async function buildCmssyMetadata(config, path, options = {}) {
404
- const clientConfig = {
405
- apiUrl: config.apiUrl,
406
- org: config.org,
407
- workspaceSlug: config.workspaceSlug
408
- };
409
- const [siteConfig, baseUrl] = await Promise.all([
410
- fetchSiteConfig(clientConfig).catch(() => null),
411
- resolveSeoBaseUrl(config, options.baseUrl)
412
- ]);
413
- const { defaultLocale, locales: enabledLocales } = localesFromSiteConfig(siteConfig);
414
- const segments = Array.isArray(path) ? path : path ? path.split("/").filter(Boolean) : void 0;
415
- const fromPath = splitLocaleFromPath(segments, {
416
- defaultLocale,
417
- locales: enabledLocales
418
- });
419
- const locale = options.locale ?? (fromPath.locale !== defaultLocale ? fromPath.locale : await config.resolveLocale?.() ?? defaultLocale);
420
- const slug = normalizeSlug(fromPath.path);
421
- const meta = await fetchPageMeta(clientConfig, slug).catch(() => null);
422
- const siteName = pick(siteConfig?.siteName, locale, defaultLocale) || siteConfig?.branding?.brandName || void 0;
423
- const title = pick(meta?.seoTitle, locale, defaultLocale) || pick(meta?.displayName, locale, defaultLocale) || siteName || "";
424
- const description = pick(meta?.seoDescription, locale, defaultLocale);
425
- const keywords = meta?.seoKeywords?.length ? meta.seoKeywords : void 0;
426
- const image = options.image ?? siteConfig?.branding?.ogImageUrl ?? void 0;
427
- const canonical = baseUrl ? `${baseUrl}${localizedPath(slug, locale, defaultLocale)}` : void 0;
428
- const languages = baseUrl && enabledLocales.length > 1 ? {
429
- ...Object.fromEntries(
430
- enabledLocales.map((l) => [
431
- l,
432
- `${baseUrl}${localizedPath(slug, l, defaultLocale)}`
433
- ])
434
- ),
435
- // What to serve a reader whose language none of these match.
436
- "x-default": `${baseUrl}${localizedPath(slug, defaultLocale, defaultLocale)}`
437
- } : void 0;
438
- return {
439
- ...baseUrl ? { metadataBase: new URL(baseUrl) } : {},
440
- ...title ? { title } : {},
441
- ...description ? { description } : {},
442
- ...keywords ? { keywords } : {},
443
- ...canonical || languages ? {
444
- alternates: {
445
- ...canonical ? { canonical } : {},
446
- ...languages ? { languages } : {}
447
- }
448
- } : {},
449
- openGraph: {
450
- ...title ? { title } : {},
451
- ...description ? { description } : {},
452
- ...canonical ? { url: canonical } : {},
453
- ...siteName ? { siteName } : {},
454
- type: options.ogType ?? "website",
455
- locale,
456
- ...image ? { images: [{ url: image }] } : {}
457
- },
458
- twitter: {
459
- card: image ? options.twitterCard ?? "summary_large_image" : "summary",
460
- ...title ? { title } : {},
461
- ...description ? { description } : {},
462
- ...image ? { images: [image] } : {}
463
- }
464
- };
465
- }
466
-
467
- // src/create-cmssy-robots.ts
468
- function createCmssyRobots(config, options = {}) {
469
- return async function robots() {
470
- const baseUrl = await resolveSeoBaseUrl(config, options.baseUrl);
471
- const rules = options.rules ?? {
472
- userAgent: "*",
473
- allow: "/",
474
- disallow: options.disallow ?? ["/api/"]
475
- };
476
- const includeSitemap = options.sitemap !== false && Boolean(baseUrl);
477
- return {
478
- rules,
479
- ...includeSitemap ? { sitemap: `${baseUrl}/sitemap.xml` } : {},
480
- ...options.host && baseUrl ? { host: baseUrl } : {}
481
- };
482
- };
483
- }
484
- function createCmssySitemap(config, options = {}) {
485
- const clientConfig = {
486
- apiUrl: config.apiUrl,
487
- org: config.org,
488
- workspaceSlug: config.workspaceSlug
489
- };
490
- return async function sitemap() {
491
- let pages = [];
492
- try {
493
- pages = await fetchPages(clientConfig);
494
- } catch (err) {
495
- if (typeof console !== "undefined") {
496
- console.warn("[cmssy] sitemap page fetch failed", err);
497
- }
498
- pages = [];
499
- }
500
- let siteConfig = null;
501
- try {
502
- siteConfig = await fetchSiteConfig(clientConfig);
503
- } catch {
504
- siteConfig = null;
505
- }
506
- const notFoundPageId = siteConfig?.notFoundPageId ?? null;
507
- const baseUrl = await resolveSeoBaseUrl(config, options.baseUrl);
508
- const { defaultLocale, locales } = localesFromSiteConfig(siteConfig);
509
- const excluded = new Set((options.excludeSlugs ?? []).map(normalizeSlug$1));
510
- const languagesFor = (slug) => locales.length > 1 ? {
511
- languages: {
512
- ...Object.fromEntries(
513
- locales.map((locale) => [
514
- locale,
515
- `${baseUrl}${localizedPath(slug, locale, defaultLocale)}`
516
- ])
517
- ),
518
- "x-default": `${baseUrl}${localizedPath(slug, defaultLocale, defaultLocale)}`
519
- }
520
- } : void 0;
521
- const entries = pages.map((page) => ({ ...page, slug: normalizeSlug$1(page.slug) })).filter((page) => page.id !== notFoundPageId && !excluded.has(page.slug)).flatMap((page) => {
522
- const lastModified = page.updatedAt ?? page.publishedAt ?? void 0;
523
- const alternates = languagesFor(page.slug);
524
- return locales.map((locale) => ({
525
- url: `${baseUrl}${localizedPath(page.slug, locale, defaultLocale)}`,
526
- ...lastModified ? { lastModified: new Date(lastModified) } : {},
527
- ...alternates ? { alternates } : {}
528
- }));
529
- });
530
- if (!options.extra) return entries;
531
- const extra = typeof options.extra === "function" ? await options.extra({ baseUrl, defaultLocale, locales }) : options.extra;
532
- return [...entries, ...extra];
533
- };
534
- }
535
- var MAX_BODY_CHARS = 16 * 1024;
536
- function json(body, status = 200) {
537
- return new Response(JSON.stringify(body), {
538
- status,
539
- headers: {
540
- "content-type": "application/json",
541
- "cache-control": "no-store"
542
- }
543
- });
544
- }
545
- async function readBody(request) {
546
- const contentType = request.headers.get("content-type") ?? "";
547
- if (!contentType.toLowerCase().includes("application/json")) {
548
- throw new Error("content-type must be application/json");
549
- }
550
- const text = await request.text();
551
- if (text.length > MAX_BODY_CHARS) {
552
- throw new Error("body too large");
553
- }
554
- if (!text) return {};
555
- const parsed = JSON.parse(text);
556
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
557
- throw new Error("body must be a JSON object");
558
- }
559
- return parsed;
560
- }
561
- function str(value) {
562
- return typeof value === "string" ? value : "";
563
- }
564
- function plainObject(value) {
565
- if (!value || typeof value !== "object" || Array.isArray(value)) return {};
566
- return value;
567
- }
568
- async function readSession(config, auth) {
569
- const jar = await cookies();
570
- const raw = jar.get(CMSSY_SESSION_COOKIE)?.value;
571
- if (!raw) return null;
572
- return openSession(raw, auth.sessionSecret, config.workspaceSlug);
573
- }
574
- async function writeSession(config, auth, payload) {
575
- const sealed = await sealSession(
576
- payload,
577
- auth.sessionSecret,
578
- config.workspaceSlug
579
- );
580
- const jar = await cookies();
581
- jar.set(CMSSY_SESSION_COOKIE, sealed, sessionCookieOptions());
582
- }
583
- async function clearSession() {
584
- const jar = await cookies();
585
- jar.set(CMSSY_SESSION_COOKIE, "", {
586
- ...sessionCookieOptions(),
587
- maxAge: 0
588
- });
589
- }
590
- async function refreshSession(config, auth, session) {
591
- const result = await backendRefresh(config, session.refreshToken);
592
- const payload = toSessionPayload(result);
593
- if (!payload) {
594
- await clearSession();
595
- return null;
596
- }
597
- await writeSession(config, auth, payload);
598
- return payload;
599
- }
600
- function createCmssyAuthRoute(config) {
601
- const auth = assertAuthConfig(config);
602
- async function handleSignIn(body) {
603
- const identity = str(body.identity);
604
- const password = str(body.password);
605
- if (!identity || !password) {
606
- return json({ ok: false, message: "Invalid credentials." }, 400);
607
- }
608
- const result = await backendSignIn(
609
- config,
610
- auth.modelSlug,
611
- identity,
612
- password
613
- );
614
- const payload = toSessionPayload(result);
615
- if (!payload) {
616
- return json({ ok: false, message: result.message }, 401);
617
- }
618
- await writeSession(config, auth, payload);
619
- return json({ ok: true, user: payload.user });
620
- }
621
- async function handleRegister(body) {
622
- const identity = str(body.identity);
623
- const password = str(body.password);
624
- if (!identity || !password) {
625
- return json({ ok: false, message: "Invalid input." }, 400);
626
- }
627
- const result = await backendRegister(
628
- config,
629
- auth.modelSlug,
630
- identity,
631
- password,
632
- plainObject(body.fields)
633
- );
634
- return json({ ok: result.success, message: result.message });
635
- }
636
- async function handleSignOut() {
637
- const session = await readSession(config, auth);
638
- if (session) {
639
- await backendSignOut(config, session.refreshToken);
640
- }
641
- await clearSession();
642
- return json({ ok: true });
643
- }
644
- async function handleSignOutEverywhere() {
645
- let session = await readSession(config, auth);
646
- if (session && isAccessExpired(session)) {
647
- session = await refreshSession(config, auth, session);
648
- }
649
- if (session) {
650
- await backendSignOutEverywhere(config, session.accessToken);
651
- }
652
- await clearSession();
653
- return json({ ok: true });
654
- }
655
- async function handleRefresh() {
656
- const session = await readSession(config, auth);
657
- if (!session) {
658
- return json({ ok: false, user: null }, 401);
659
- }
660
- const refreshed = await refreshSession(config, auth, session);
661
- if (!refreshed) {
662
- return json({ ok: false, user: null }, 401);
663
- }
664
- return json({ ok: true, user: refreshed.user });
665
- }
666
- async function handleMe() {
667
- let session = await readSession(config, auth);
668
- if (session && isAccessExpired(session)) {
669
- session = await refreshSession(config, auth, session);
670
- }
671
- return json({ user: session?.user ?? null });
672
- }
673
- async function handleForgotPassword(body) {
674
- const identity = str(body.identity);
675
- if (!identity) {
676
- return json({ ok: false, message: "Invalid input." }, 400);
677
- }
678
- const result = await backendForgotPassword(
679
- config,
680
- auth.modelSlug,
681
- identity
682
- );
683
- return json({ ok: result.success, message: result.message });
684
- }
685
- async function handleResetPassword(body) {
686
- const token = str(body.token);
687
- const newPassword = str(body.newPassword);
688
- if (!token || !newPassword) {
689
- return json({ ok: false, message: "Invalid input." }, 400);
690
- }
691
- const result = await backendResetPassword(config, token, newPassword);
692
- return json({ ok: result.success, message: result.message });
693
- }
694
- async function handleVerifyEmail(body) {
695
- const token = str(body.token);
696
- if (!token) {
697
- return json({ ok: false, message: "Invalid input." }, 400);
698
- }
699
- const result = await backendVerifyEmail(config, token);
700
- return json({ ok: result.success, message: result.message });
701
- }
702
- return {
703
- async POST(request, context) {
704
- const { action } = await context.params;
705
- let body;
706
- try {
707
- body = await readBody(request);
708
- } catch {
709
- return json({ ok: false, message: "Invalid request body." }, 400);
710
- }
711
- try {
712
- switch (action) {
713
- case "sign-in":
714
- return await handleSignIn(body);
715
- case "register":
716
- return await handleRegister(body);
717
- case "sign-out":
718
- return await handleSignOut();
719
- case "sign-out-everywhere":
720
- return await handleSignOutEverywhere();
721
- case "refresh":
722
- return await handleRefresh();
723
- case "forgot-password":
724
- return await handleForgotPassword(body);
725
- case "reset-password":
726
- return await handleResetPassword(body);
727
- case "verify-email":
728
- return await handleVerifyEmail(body);
729
- default:
730
- return json({ ok: false, message: "Not found." }, 404);
731
- }
732
- } catch {
733
- return json({ ok: false, message: "Something went wrong." }, 500);
734
- }
735
- },
736
- async GET(_request, context) {
737
- const { action } = await context.params;
738
- if (action !== "me") {
739
- return json({ ok: false, message: "Not found." }, 404);
740
- }
741
- try {
742
- return await handleMe();
743
- } catch {
744
- return json({ user: null });
745
- }
746
- }
747
- };
748
- }
749
- var CMSSY_CART_COOKIE = "cmssy_cart";
750
- var CART_MAX_AGE_SECONDS = 30 * 24 * 60 * 60;
751
- var CART_TOKEN_BYTES = 32;
752
- var MAX_BODY_CHARS2 = 16 * 1024;
753
- function json2(body, status = 200) {
754
- return new Response(JSON.stringify(body), {
755
- status,
756
- headers: {
757
- "content-type": "application/json",
758
- "cache-control": "no-store"
759
- }
760
- });
761
- }
762
- function cartCookieOptions() {
763
- return {
764
- httpOnly: true,
765
- secure: process.env.NODE_ENV !== "development",
766
- sameSite: "lax",
767
- path: "/",
768
- maxAge: CART_MAX_AGE_SECONDS
769
- };
770
- }
771
- function mintToken() {
772
- const bytes = new Uint8Array(CART_TOKEN_BYTES);
773
- crypto.getRandomValues(bytes);
774
- let binary = "";
775
- for (const byte of bytes) binary += String.fromCharCode(byte);
776
- return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
777
- }
778
- async function readBody2(request) {
779
- const contentType = request.headers.get("content-type") ?? "";
780
- if (!contentType.toLowerCase().includes("application/json")) {
781
- throw new Error("content-type must be application/json");
782
- }
783
- const text = await request.text();
784
- if (text.length > MAX_BODY_CHARS2) throw new Error("body too large");
785
- if (!text) return {};
786
- const parsed = JSON.parse(text);
787
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
788
- throw new Error("body must be a JSON object");
789
- }
790
- return parsed;
791
- }
792
- function str2(value) {
793
- return typeof value === "string" ? value : "";
794
- }
795
- function plainObject2(value) {
796
- if (!value || typeof value !== "object" || Array.isArray(value)) return {};
797
- return value;
798
- }
799
- function optionalStr(value) {
800
- return typeof value === "string" && value.trim() ? value.trim() : null;
801
- }
802
- var ADDRESS_REQUIRED = [
803
- "name",
804
- "line1",
805
- "postalCode",
806
- "city",
807
- "country"
808
- ];
809
- var CmssyAddressError = class extends Error {
810
- constructor(missing) {
811
- super(
812
- `cmssy: shippingAddress is missing required field(s): ${missing.join(
813
- ", "
814
- )}. Expected { name, line1, postalCode, city, country } (plus optional company, line2, region, phone, vatId).`
815
- );
816
- this.missing = missing;
817
- this.name = "CmssyAddressError";
818
- }
819
- missing;
820
- };
821
- function shippingAddress(value) {
822
- if (value === void 0 || value === null) return null;
823
- if (typeof value !== "object" || Array.isArray(value)) {
824
- throw new CmssyAddressError([...ADDRESS_REQUIRED]);
825
- }
826
- const raw = value;
827
- const missing = ADDRESS_REQUIRED.filter((key) => !optionalStr(raw[key]));
828
- if (missing.length > 0) throw new CmssyAddressError(missing);
829
- return {
830
- name: optionalStr(raw.name),
831
- company: optionalStr(raw.company),
832
- line1: optionalStr(raw.line1),
833
- line2: optionalStr(raw.line2),
834
- postalCode: optionalStr(raw.postalCode),
835
- city: optionalStr(raw.city),
836
- region: optionalStr(raw.region),
837
- country: optionalStr(raw.country),
838
- phone: optionalStr(raw.phone),
839
- vatId: optionalStr(raw.vatId)
840
- };
841
- }
842
- function createCmssyCartRoute(config) {
843
- async function ensureCartToken() {
844
- const jar = await cookies();
845
- const existing = jar.get(CMSSY_CART_COOKIE)?.value;
846
- if (existing) return existing;
847
- const token = mintToken();
848
- jar.set(CMSSY_CART_COOKIE, token, cartCookieOptions());
849
- return token;
850
- }
851
- async function clearCartToken() {
852
- const jar = await cookies();
853
- jar.set(CMSSY_CART_COOKIE, "", { ...cartCookieOptions(), maxAge: 0 });
854
- }
855
- async function memberAccessToken() {
856
- if (!config.auth) return void 0;
857
- const jar = await cookies();
858
- const raw = jar.get(CMSSY_SESSION_COOKIE)?.value;
859
- if (!raw) return void 0;
860
- const session = await openSession(
861
- raw,
862
- config.auth.sessionSecret,
863
- config.workspaceSlug
864
- );
865
- if (!session || isAccessExpired(session)) return void 0;
866
- return session.accessToken;
867
- }
868
- async function buildContext() {
869
- const cartToken = await ensureCartToken();
870
- const accessToken = await memberAccessToken();
871
- return accessToken ? { cartToken, accessToken } : { cartToken };
872
- }
873
- return {
874
- async POST(request, context) {
875
- const { action } = await context.params;
876
- let body;
877
- try {
878
- body = await readBody2(request);
879
- } catch {
880
- return json2({ message: "Invalid request body." }, 400);
881
- }
882
- try {
883
- const ctx = await buildContext();
884
- switch (action) {
885
- case "cart":
886
- return json2({ cart: await backendGetCart(config, ctx) });
887
- case "add":
888
- return json2({
889
- cart: await backendAddToCart(config, ctx, {
890
- recordId: str2(body.recordId),
891
- quantity: typeof body.quantity === "number" ? body.quantity : 1,
892
- variantSelections: body.variantSelections,
893
- notes: typeof body.notes === "string" ? body.notes : void 0
894
- })
895
- });
896
- case "update":
897
- return json2({
898
- cart: await backendUpdateItem(config, ctx, {
899
- itemId: str2(body.itemId),
900
- quantity: typeof body.quantity === "number" ? body.quantity : 0
901
- })
902
- });
903
- case "remove":
904
- return json2({
905
- cart: await backendRemoveItem(config, ctx, str2(body.itemId))
906
- });
907
- case "clear":
908
- return json2({ cart: await backendClearCart(config, ctx) });
909
- case "apply-discount":
910
- return json2({
911
- cart: await backendApplyDiscount(config, ctx, str2(body.code))
912
- });
913
- case "remove-discount":
914
- return json2({ cart: await backendRemoveDiscount(config, ctx) });
915
- case "set-shipping":
916
- return json2({
917
- cart: await backendSetShippingMethod(
918
- config,
919
- ctx,
920
- optionalStr(body.shippingMethodId)
921
- )
922
- });
923
- case "merge":
924
- return json2({ cart: await backendMergeCart(config, ctx) });
925
- case "checkout": {
926
- const order = await backendCheckout(config, ctx, {
927
- customerEmail: str2(body.customerEmail),
928
- poNumber: optionalStr(body.poNumber),
929
- customerNote: optionalStr(body.customerNote),
930
- shippingAddress: shippingAddress(body.shippingAddress)
931
- });
932
- await clearCartToken();
933
- return json2({ order });
934
- }
935
- case "product":
936
- return json2({
937
- product: await backendProduct(
938
- config,
939
- ctx,
940
- str2(body.modelSlug),
941
- plainObject2(body.filter)
942
- )
943
- });
944
- default:
945
- return json2({ message: "Not found." }, 404);
946
- }
947
- } catch (err) {
948
- if (err instanceof CmssyAddressError) {
949
- return json2({ message: err.message, missing: err.missing }, 400);
950
- }
951
- return json2(
952
- {
953
- message: err instanceof Error ? err.message : "Commerce request failed"
954
- },
955
- 502
956
- );
957
- }
958
- }
959
- };
960
- }
961
- var DEFAULT_LIMIT = 20;
962
- var MAX_LIMIT = 100;
963
- function json3(body, status = 200) {
964
- return new Response(JSON.stringify(body), {
965
- status,
966
- headers: {
967
- "content-type": "application/json",
968
- "cache-control": "no-store"
969
- }
970
- });
971
- }
972
- function createCmssyOrdersRoute(config) {
973
- async function memberAccessToken() {
974
- if (!config.auth) return void 0;
975
- const jar = await cookies();
976
- const raw = jar.get(CMSSY_SESSION_COOKIE)?.value;
977
- if (!raw) return void 0;
978
- const session = await openSession(
979
- raw,
980
- config.auth.sessionSecret,
981
- config.workspaceSlug
982
- );
983
- if (!session || isAccessExpired(session)) return void 0;
984
- return session.accessToken;
985
- }
986
- return {
987
- async GET(request) {
988
- const accessToken = await memberAccessToken();
989
- if (!accessToken) {
990
- return json3({ message: "Not signed in." }, 401);
991
- }
992
- const url = new URL(request.url);
993
- try {
994
- const id = url.searchParams.get("id");
995
- if (id) {
996
- return json3({ order: await backendMyOrder(config, accessToken, id) });
997
- }
998
- const skip = Math.max(
999
- 0,
1000
- Math.floor(Number(url.searchParams.get("skip")) || 0)
1001
- );
1002
- const limitParam = Math.floor(Number(url.searchParams.get("limit")));
1003
- const limit = Number.isFinite(limitParam) && limitParam > 0 ? Math.min(limitParam, MAX_LIMIT) : DEFAULT_LIMIT;
1004
- const result = await backendMyOrders(config, accessToken, {
1005
- skip,
1006
- limit
1007
- });
1008
- return json3(result);
1009
- } catch (err) {
1010
- return json3(
1011
- { message: err instanceof Error ? err.message : "Orders error" },
1012
- 502
1013
- );
1014
- }
1015
- }
1016
- };
1017
- }
1018
194
  var MIN_SECRET_LENGTH = 16;
1019
195
  function safeRedirect(redirect2, fallback) {
1020
196
  if (!redirect2 || !redirect2.startsWith("/")) return fallback;
@@ -1063,20 +239,9 @@ function createDraftRoute(config) {
1063
239
  redirect(location);
1064
240
  };
1065
241
  }
1066
- async function requestLocale(config) {
1067
- try {
1068
- return await getCmssyLocale(config);
1069
- } catch {
1070
- return void 0;
1071
- }
1072
- }
1073
- async function fetchProducts(config, options) {
1074
- const locale = options.locale ?? await requestLocale(config);
1075
- return fetchProducts$1(config, { ...options, locale });
1076
- }
1077
- async function fetchProduct(config, options) {
1078
- const locale = options.locale ?? await requestLocale(config);
1079
- return fetchProduct$1(config, { ...options, locale });
242
+ async function isCmssyEditMode() {
243
+ const h = await headers();
244
+ return h.get(CMSSY_EDIT_HEADER) === "1";
1080
245
  }
1081
246
 
1082
- export { CMSSY_CART_COOKIE, CmssyLayoutSlot, buildCmssyMetadata, createCmssyAuthRoute, createCmssyCartRoute, createCmssyEditPage, createCmssyNotFound, createCmssyOrdersRoute, createCmssyPage, createCmssyRobots, createCmssySitemap, createDraftRoute, fetchProduct, fetchProducts, getCmssyAccessToken, getCmssyLocale, getCmssyUser, isCmssyEditMode };
247
+ export { createCmssyEditPage, createCmssyPage, createDraftRoute, isCmssyEditMode };