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