@cmssy/core 9.9.0 → 10.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/csp-DDVG6VAm.d.cts +189 -0
- package/dist/csp-DpssAFJC.d.ts +189 -0
- package/dist/graphql-request-CDh6IRok.d.ts +23 -0
- package/dist/graphql-request-NQxSMt6v.d.cts +23 -0
- package/dist/index.cjs +21 -1410
- package/dist/index.d.cts +7 -400
- package/dist/index.d.ts +7 -400
- package/dist/index.js +22 -1331
- package/dist/internal/locale.cjs +279 -0
- package/dist/internal/locale.d.cts +56 -0
- package/dist/internal/locale.d.ts +56 -0
- package/dist/internal/locale.js +267 -0
- package/dist/internal.cjs +939 -0
- package/dist/internal.d.cts +62 -0
- package/dist/internal.d.ts +62 -0
- package/dist/internal.js +898 -0
- package/package.json +12 -2
|
@@ -0,0 +1,939 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/data/http.ts
|
|
4
|
+
var CmssyRequestError = class extends Error {
|
|
5
|
+
status;
|
|
6
|
+
constructor(message, status) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "CmssyRequestError";
|
|
9
|
+
this.status = status;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
var DEFAULT_RETRY_STATUSES = [429, 503];
|
|
13
|
+
function retryAfterMs(response) {
|
|
14
|
+
const raw = response.headers?.get("retry-after");
|
|
15
|
+
if (!raw) return null;
|
|
16
|
+
const seconds = Number(raw);
|
|
17
|
+
if (Number.isFinite(seconds)) return Math.max(0, seconds * 1e3);
|
|
18
|
+
const date = Date.parse(raw);
|
|
19
|
+
if (Number.isFinite(date)) return Math.max(0, date - Date.now());
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
function sleep(ms, signal) {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
if (signal?.aborted) {
|
|
25
|
+
reject(new Error("cmssy: request aborted"));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const timer = setTimeout(() => {
|
|
29
|
+
signal?.removeEventListener("abort", onAbort);
|
|
30
|
+
resolve();
|
|
31
|
+
}, ms);
|
|
32
|
+
function onAbort() {
|
|
33
|
+
clearTimeout(timer);
|
|
34
|
+
reject(new Error("cmssy: request aborted"));
|
|
35
|
+
}
|
|
36
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
async function fetchWithRetry(doFetch, url, init, retry) {
|
|
40
|
+
if (retry === false || retry === void 0) {
|
|
41
|
+
return doFetch(url, init);
|
|
42
|
+
}
|
|
43
|
+
const maxRetries = retry.maxRetries ?? 3;
|
|
44
|
+
const baseDelayMs = retry.baseDelayMs ?? 300;
|
|
45
|
+
const maxDelayMs = retry.maxDelayMs ?? 3e3;
|
|
46
|
+
const retryStatuses = retry.retryStatuses ?? DEFAULT_RETRY_STATUSES;
|
|
47
|
+
let response = await doFetch(url, init);
|
|
48
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
49
|
+
if (response.ok || !retryStatuses.includes(response.status)) {
|
|
50
|
+
return response;
|
|
51
|
+
}
|
|
52
|
+
const backoff = baseDelayMs * 2 ** attempt;
|
|
53
|
+
const wait = Math.min(retryAfterMs(response) ?? backoff, maxDelayMs);
|
|
54
|
+
await sleep(wait, init.signal);
|
|
55
|
+
response = await doFetch(url, init);
|
|
56
|
+
}
|
|
57
|
+
return response;
|
|
58
|
+
}
|
|
59
|
+
async function postGraphql(url, query, variables, options) {
|
|
60
|
+
const doFetch = options.fetch ?? globalThis.fetch;
|
|
61
|
+
if (typeof doFetch !== "function") {
|
|
62
|
+
throw new Error(
|
|
63
|
+
"cmssy: no fetch implementation available - pass options.fetch"
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
const response = await fetchWithRetry(
|
|
67
|
+
doFetch,
|
|
68
|
+
url,
|
|
69
|
+
{
|
|
70
|
+
method: "POST",
|
|
71
|
+
headers: { "content-type": "application/json", ...options.headers },
|
|
72
|
+
body: JSON.stringify({ query, variables }),
|
|
73
|
+
signal: options.signal
|
|
74
|
+
},
|
|
75
|
+
options.retry
|
|
76
|
+
);
|
|
77
|
+
if (!response.ok) {
|
|
78
|
+
let detail = "";
|
|
79
|
+
try {
|
|
80
|
+
const body = await response.json();
|
|
81
|
+
if (body.errors && body.errors.length > 0) {
|
|
82
|
+
detail = ` - ${body.errors.map((error) => error.message ?? "GraphQL error").join("; ")}`;
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
detail = "";
|
|
86
|
+
}
|
|
87
|
+
throw new CmssyRequestError(
|
|
88
|
+
`cmssy: ${options.label} failed (${response.status})${detail}`,
|
|
89
|
+
response.status
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
let json;
|
|
93
|
+
try {
|
|
94
|
+
json = await response.json();
|
|
95
|
+
} catch {
|
|
96
|
+
throw new Error(`cmssy: invalid JSON response from the ${options.label}`);
|
|
97
|
+
}
|
|
98
|
+
if (json.errors && json.errors.length > 0) {
|
|
99
|
+
const message = json.errors.map((error) => error.message ?? "GraphQL error").join("; ");
|
|
100
|
+
throw new Error(`cmssy: ${options.label} error - ${message}`);
|
|
101
|
+
}
|
|
102
|
+
return json.data;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/content/content-client.ts
|
|
106
|
+
var DEFAULT_CMSSY_API_URL = "https://api.cmssy.io/graphql";
|
|
107
|
+
function resolveApiUrl(apiUrl) {
|
|
108
|
+
const explicit = apiUrl?.trim();
|
|
109
|
+
if (explicit) return explicit;
|
|
110
|
+
const env = globalThis.process?.env;
|
|
111
|
+
const fromEnv = env?.CMSSY_API_URL?.trim() ?? "";
|
|
112
|
+
return fromEnv.length > 0 ? fromEnv : DEFAULT_CMSSY_API_URL;
|
|
113
|
+
}
|
|
114
|
+
function resolvePublicUrl(config) {
|
|
115
|
+
const base = resolveApiUrl(config.apiUrl).replace(/\/graphql\/?$/, "");
|
|
116
|
+
return `${base}/public/${config.org}/${config.workspaceSlug}/graphql`;
|
|
117
|
+
}
|
|
118
|
+
var PUBLIC_PAGE_QUERY = `query PublicPage($workspaceSlug: String!, $slug: String!, $previewSecret: String) {
|
|
119
|
+
public {
|
|
120
|
+
page {
|
|
121
|
+
get(workspaceSlug: $workspaceSlug, slug: $slug, previewSecret: $previewSecret) {
|
|
122
|
+
id
|
|
123
|
+
blocks { id type content style advanced }
|
|
124
|
+
publishedBlocks { id type content style advanced }
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}`;
|
|
129
|
+
var PUBLIC_PAGE_DEV_QUERY = `query PublicPage($workspaceSlug: String!, $slug: String!, $previewSecret: String, $devPreview: Boolean) {
|
|
130
|
+
public {
|
|
131
|
+
page {
|
|
132
|
+
get(workspaceSlug: $workspaceSlug, slug: $slug, previewSecret: $previewSecret, devPreview: $devPreview) {
|
|
133
|
+
id
|
|
134
|
+
blocks { id type content style advanced }
|
|
135
|
+
publishedBlocks { id type content style advanced }
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}`;
|
|
140
|
+
var PUBLIC_PAGE_BY_ID_QUERY = `query PublicPageById($workspaceSlug: String!, $pageId: ID!) {
|
|
141
|
+
public {
|
|
142
|
+
page {
|
|
143
|
+
getById(workspaceSlug: $workspaceSlug, pageId: $pageId) {
|
|
144
|
+
id
|
|
145
|
+
publishedBlocks { id type content style advanced }
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}`;
|
|
150
|
+
var PUBLIC_PAGES_QUERY = `query PublicPages($workspaceSlug: String!) {
|
|
151
|
+
public {
|
|
152
|
+
page {
|
|
153
|
+
list(workspaceSlug: $workspaceSlug) {
|
|
154
|
+
id
|
|
155
|
+
slug
|
|
156
|
+
updatedAt
|
|
157
|
+
publishedAt
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}`;
|
|
162
|
+
var PUBLIC_PAGE_META_QUERY = `query PublicPageMeta($workspaceSlug: String!, $slug: String!) {
|
|
163
|
+
public {
|
|
164
|
+
page {
|
|
165
|
+
get(workspaceSlug: $workspaceSlug, slug: $slug) {
|
|
166
|
+
id
|
|
167
|
+
seoTitle
|
|
168
|
+
seoDescription
|
|
169
|
+
seoKeywords
|
|
170
|
+
displayName
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}`;
|
|
175
|
+
var PUBLIC_PAGE_LAYOUTS_QUERY = `query PublicPageLayouts($workspaceSlug: String!, $pageSlug: String!, $previewSecret: String) {
|
|
176
|
+
public {
|
|
177
|
+
page {
|
|
178
|
+
layouts(workspaceSlug: $workspaceSlug, pageSlug: $pageSlug, previewSecret: $previewSecret) {
|
|
179
|
+
position
|
|
180
|
+
blocks { id type content style advanced order isActive }
|
|
181
|
+
settings { desktopWidth mobileBehavior }
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}`;
|
|
186
|
+
function normalizeSlug(path) {
|
|
187
|
+
if (Array.isArray(path)) {
|
|
188
|
+
const joined = path.filter(Boolean).join("/");
|
|
189
|
+
return joined ? `/${joined}` : "/";
|
|
190
|
+
}
|
|
191
|
+
if (!path || path === "/") return "/";
|
|
192
|
+
return path.startsWith("/") ? path : `/${path}`;
|
|
193
|
+
}
|
|
194
|
+
async function fetchPage(config, path, options = {}) {
|
|
195
|
+
const slug = normalizeSlug(path);
|
|
196
|
+
const trimmedSecret = options.previewSecret?.trim();
|
|
197
|
+
const previewSecret = trimmedSecret ? trimmedSecret : null;
|
|
198
|
+
const devToken = options.devToken?.trim();
|
|
199
|
+
const devPreview = Boolean(options.devPreview && devToken);
|
|
200
|
+
const headers = {};
|
|
201
|
+
if (devPreview && devToken) {
|
|
202
|
+
headers["authorization"] = `Bearer ${devToken}`;
|
|
203
|
+
if (options.workspaceId) {
|
|
204
|
+
headers["x-workspace-id"] = options.workspaceId;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const data = await postGraphql(
|
|
208
|
+
resolvePublicUrl(config),
|
|
209
|
+
devPreview ? PUBLIC_PAGE_DEV_QUERY : PUBLIC_PAGE_QUERY,
|
|
210
|
+
{
|
|
211
|
+
workspaceSlug: config.workspaceSlug,
|
|
212
|
+
slug,
|
|
213
|
+
previewSecret,
|
|
214
|
+
...devPreview ? { devPreview: true } : {}
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
fetch: options.fetch,
|
|
218
|
+
signal: options.signal,
|
|
219
|
+
headers,
|
|
220
|
+
retry: options.retry ?? {},
|
|
221
|
+
label: "page fetch"
|
|
222
|
+
}
|
|
223
|
+
);
|
|
224
|
+
const page = data?.public?.page?.get;
|
|
225
|
+
if (!page) return null;
|
|
226
|
+
const draft = previewSecret !== null || devPreview;
|
|
227
|
+
const blocks = (draft ? page.blocks : page.publishedBlocks) ?? [];
|
|
228
|
+
return { id: page.id, blocks };
|
|
229
|
+
}
|
|
230
|
+
async function fetchPageById(config, pageId, options = {}) {
|
|
231
|
+
const data = await postGraphql(
|
|
232
|
+
resolvePublicUrl(config),
|
|
233
|
+
PUBLIC_PAGE_BY_ID_QUERY,
|
|
234
|
+
{ workspaceSlug: config.workspaceSlug, pageId },
|
|
235
|
+
{
|
|
236
|
+
fetch: options.fetch,
|
|
237
|
+
signal: options.signal,
|
|
238
|
+
retry: options.retry ?? {},
|
|
239
|
+
label: "page-by-id fetch"
|
|
240
|
+
}
|
|
241
|
+
);
|
|
242
|
+
const page = data?.public?.page?.getById;
|
|
243
|
+
if (!page) return null;
|
|
244
|
+
return { id: page.id, blocks: page.publishedBlocks ?? [] };
|
|
245
|
+
}
|
|
246
|
+
async function fetchPages(config, options = {}) {
|
|
247
|
+
const data = await postGraphql(
|
|
248
|
+
resolvePublicUrl(config),
|
|
249
|
+
PUBLIC_PAGES_QUERY,
|
|
250
|
+
{ workspaceSlug: config.workspaceSlug },
|
|
251
|
+
{
|
|
252
|
+
fetch: options.fetch,
|
|
253
|
+
signal: options.signal,
|
|
254
|
+
retry: options.retry ?? {},
|
|
255
|
+
label: "pages fetch"
|
|
256
|
+
}
|
|
257
|
+
);
|
|
258
|
+
return data?.public?.page?.list ?? [];
|
|
259
|
+
}
|
|
260
|
+
async function fetchPageMeta(config, path, options = {}) {
|
|
261
|
+
const slug = normalizeSlug(path);
|
|
262
|
+
const data = await postGraphql(
|
|
263
|
+
resolvePublicUrl(config),
|
|
264
|
+
PUBLIC_PAGE_META_QUERY,
|
|
265
|
+
{ workspaceSlug: config.workspaceSlug, slug },
|
|
266
|
+
{
|
|
267
|
+
fetch: options.fetch,
|
|
268
|
+
signal: options.signal,
|
|
269
|
+
retry: options.retry ?? {},
|
|
270
|
+
label: "page meta fetch"
|
|
271
|
+
}
|
|
272
|
+
);
|
|
273
|
+
return data?.public?.page?.get ?? null;
|
|
274
|
+
}
|
|
275
|
+
async function fetchLayouts(config, path, options = {}) {
|
|
276
|
+
const pageSlug = normalizeSlug(path);
|
|
277
|
+
const trimmedSecret = options.previewSecret?.trim();
|
|
278
|
+
const previewSecret = trimmedSecret ? trimmedSecret : null;
|
|
279
|
+
const data = await postGraphql(
|
|
280
|
+
resolvePublicUrl(config),
|
|
281
|
+
PUBLIC_PAGE_LAYOUTS_QUERY,
|
|
282
|
+
{ workspaceSlug: config.workspaceSlug, pageSlug, previewSecret },
|
|
283
|
+
{
|
|
284
|
+
fetch: options.fetch,
|
|
285
|
+
signal: options.signal,
|
|
286
|
+
retry: options.retry ?? {},
|
|
287
|
+
label: "layouts fetch"
|
|
288
|
+
}
|
|
289
|
+
);
|
|
290
|
+
return data?.public?.page?.layouts ?? [];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// src/content/get-block-content.ts
|
|
294
|
+
function isPlainObject(value) {
|
|
295
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
296
|
+
}
|
|
297
|
+
function looksLikeLocaleKey(key) {
|
|
298
|
+
return /^[a-z]{2}(-[A-Za-z]{2})?$/.test(key);
|
|
299
|
+
}
|
|
300
|
+
function getBlockContentForLanguage(content, locale, defaultLocale = "en", availableLocales) {
|
|
301
|
+
if (!isPlainObject(content)) return {};
|
|
302
|
+
const isLocale = availableLocales ? (key) => availableLocales.includes(key) : looksLikeLocaleKey;
|
|
303
|
+
const localeEntries = Object.entries(content).filter(
|
|
304
|
+
([key, value]) => isLocale(key) && isPlainObject(value)
|
|
305
|
+
);
|
|
306
|
+
if (localeEntries.length === 0) return { ...content };
|
|
307
|
+
const localeMap = Object.fromEntries(localeEntries);
|
|
308
|
+
const nonTranslatable = {};
|
|
309
|
+
for (const [key, value] of Object.entries(content)) {
|
|
310
|
+
if (!(isLocale(key) && isPlainObject(value))) nonTranslatable[key] = value;
|
|
311
|
+
}
|
|
312
|
+
const fallbackKey = Object.keys(localeMap)[0];
|
|
313
|
+
const chosen = localeMap[locale] ?? localeMap[defaultLocale] ?? localeMap[fallbackKey];
|
|
314
|
+
return { ...nonTranslatable, ...chosen };
|
|
315
|
+
}
|
|
316
|
+
function asBucket(value) {
|
|
317
|
+
return isPlainObject(value) ? value : {};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/data/graphql-request.ts
|
|
321
|
+
async function graphqlRequest(config, query, variables, options = {}, label = "request") {
|
|
322
|
+
const url = options.public ? resolvePublicUrl(config) : resolveApiUrl(config.apiUrl);
|
|
323
|
+
return postGraphql(url, query, variables, {
|
|
324
|
+
fetch: options.fetch,
|
|
325
|
+
signal: options.signal,
|
|
326
|
+
headers: options.headers,
|
|
327
|
+
retry: options.retry,
|
|
328
|
+
label
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// src/data/queries.ts
|
|
333
|
+
var SITE_CONFIG_QUERY = `query PublicSiteConfig($workspaceSlug: String!) {
|
|
334
|
+
public {
|
|
335
|
+
siteConfig(workspaceSlug: $workspaceSlug) {
|
|
336
|
+
id
|
|
337
|
+
workspaceId
|
|
338
|
+
siteName
|
|
339
|
+
defaultLanguage
|
|
340
|
+
enabledLanguages
|
|
341
|
+
enabledFeatures
|
|
342
|
+
notFoundPageId
|
|
343
|
+
previewUrl
|
|
344
|
+
branding {
|
|
345
|
+
brandName
|
|
346
|
+
logoUrl
|
|
347
|
+
faviconUrl
|
|
348
|
+
ogImageUrl
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}`;
|
|
353
|
+
var MODEL_DEFINITIONS_QUERY = `query PublicModelDefinitions($workspaceId: String!) {
|
|
354
|
+
public {
|
|
355
|
+
model {
|
|
356
|
+
definitions(workspaceId: $workspaceId) {
|
|
357
|
+
id name slug description icon color displayField recordCount
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}`;
|
|
362
|
+
var MODEL_RECORDS_QUERY = `query PublicModelRecords($workspaceId: String!, $modelSlug: String!, $filter: JSON, $sort: String, $locale: String, $limit: Int, $offset: Int, $populate: [String!]) {
|
|
363
|
+
public {
|
|
364
|
+
model {
|
|
365
|
+
records(workspaceId: $workspaceId, modelSlug: $modelSlug, filter: $filter, sort: $sort, locale: $locale, limit: $limit, offset: $offset, populate: $populate) {
|
|
366
|
+
items { id modelId data status createdAt updatedAt }
|
|
367
|
+
total
|
|
368
|
+
hasMore
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}`;
|
|
373
|
+
var FORM_QUERY = `query PublicForm($formId: ID!) {
|
|
374
|
+
public {
|
|
375
|
+
form {
|
|
376
|
+
get(formId: $formId) {
|
|
377
|
+
id
|
|
378
|
+
name
|
|
379
|
+
slug
|
|
380
|
+
description
|
|
381
|
+
fields {
|
|
382
|
+
id name fieldType label placeholder helpText
|
|
383
|
+
defaultValue width order showWhen requiredWhen
|
|
384
|
+
options { value label disabled }
|
|
385
|
+
validation { required minLength maxLength minValue maxValue pattern customMessage }
|
|
386
|
+
}
|
|
387
|
+
settings {
|
|
388
|
+
actionType submitButtonLabel successMessage errorMessage
|
|
389
|
+
redirectUrl requireLogin enableCaptcha
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}`;
|
|
395
|
+
var SUBMIT_FORM_MUTATION = `mutation SubmitForm($formId: ID!, $input: SubmitFormInput!) {
|
|
396
|
+
public {
|
|
397
|
+
form {
|
|
398
|
+
submit(formId: $formId, input: $input) {
|
|
399
|
+
success message submissionId redirectUrl accessToken customer
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}`;
|
|
404
|
+
|
|
405
|
+
// src/data/settings-client.ts
|
|
406
|
+
async function fetchSiteConfig(config, options = {}) {
|
|
407
|
+
const data = await graphqlRequest(
|
|
408
|
+
config,
|
|
409
|
+
SITE_CONFIG_QUERY,
|
|
410
|
+
{ workspaceSlug: config.workspaceSlug },
|
|
411
|
+
{ ...options, public: true, retry: options.retry ?? {} },
|
|
412
|
+
"site config query"
|
|
413
|
+
);
|
|
414
|
+
return data.public?.siteConfig ?? null;
|
|
415
|
+
}
|
|
416
|
+
async function resolveWorkspaceId(config, options = {}) {
|
|
417
|
+
const siteConfig = await fetchSiteConfig(config, options);
|
|
418
|
+
if (!siteConfig?.workspaceId) {
|
|
419
|
+
throw new Error(
|
|
420
|
+
`cmssy: could not resolve workspaceId for "${config.workspaceSlug}"`
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
return siteConfig.workspaceId;
|
|
424
|
+
}
|
|
425
|
+
var workspaceIdCache = /* @__PURE__ */ new Map();
|
|
426
|
+
function cachedWorkspaceId(config) {
|
|
427
|
+
const key = `${resolveApiUrl(config.apiUrl)}::${config.workspaceSlug}`;
|
|
428
|
+
const existing = workspaceIdCache.get(key);
|
|
429
|
+
if (existing) return existing;
|
|
430
|
+
const fresh = resolveWorkspaceId(config).catch((err) => {
|
|
431
|
+
workspaceIdCache.delete(key);
|
|
432
|
+
throw err;
|
|
433
|
+
});
|
|
434
|
+
workspaceIdCache.set(key, fresh);
|
|
435
|
+
return fresh;
|
|
436
|
+
}
|
|
437
|
+
function clearWorkspaceIdCache() {
|
|
438
|
+
workspaceIdCache.clear();
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// src/data/client.ts
|
|
442
|
+
function createCmssyClient(input) {
|
|
443
|
+
const config = {
|
|
444
|
+
...input,
|
|
445
|
+
apiUrl: resolveApiUrl(input.apiUrl)
|
|
446
|
+
};
|
|
447
|
+
let cachedWorkspaceId2;
|
|
448
|
+
let inFlight;
|
|
449
|
+
function resolveWorkspaceId2(options) {
|
|
450
|
+
if (cachedWorkspaceId2) return Promise.resolve(cachedWorkspaceId2);
|
|
451
|
+
if (!inFlight) {
|
|
452
|
+
inFlight = resolveWorkspaceId(config, options).then((id) => {
|
|
453
|
+
cachedWorkspaceId2 = id;
|
|
454
|
+
return id;
|
|
455
|
+
}).finally(() => {
|
|
456
|
+
inFlight = void 0;
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
return inFlight;
|
|
460
|
+
}
|
|
461
|
+
return {
|
|
462
|
+
config,
|
|
463
|
+
resolveWorkspaceId: resolveWorkspaceId2,
|
|
464
|
+
query(document2, variables = {}, options) {
|
|
465
|
+
return graphqlRequest(
|
|
466
|
+
config,
|
|
467
|
+
document2,
|
|
468
|
+
variables,
|
|
469
|
+
options,
|
|
470
|
+
"graphql operation"
|
|
471
|
+
);
|
|
472
|
+
},
|
|
473
|
+
async queryScoped(document2, variables = {}, options = {}) {
|
|
474
|
+
const { workspaceId: provided, headers, ...rest } = options;
|
|
475
|
+
const workspaceId = provided ?? await resolveWorkspaceId2({ ...rest, headers });
|
|
476
|
+
const hasWorkspaceId = variables.workspaceId !== void 0 && variables.workspaceId !== null;
|
|
477
|
+
const scopedVariables = /\$workspaceId\b/.test(document2) && !hasWorkspaceId ? { ...variables, workspaceId } : variables;
|
|
478
|
+
return graphqlRequest(
|
|
479
|
+
config,
|
|
480
|
+
document2,
|
|
481
|
+
scopedVariables,
|
|
482
|
+
{ ...rest, headers: { ...headers, "x-workspace-id": workspaceId } },
|
|
483
|
+
"graphql operation"
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// src/data/resolve-forms.ts
|
|
490
|
+
function collectFormIds(blocks, locale, defaultLocale) {
|
|
491
|
+
const ids = /* @__PURE__ */ new Set();
|
|
492
|
+
for (const block of blocks) {
|
|
493
|
+
const content = getBlockContentForLanguage(
|
|
494
|
+
block.content,
|
|
495
|
+
locale,
|
|
496
|
+
defaultLocale
|
|
497
|
+
);
|
|
498
|
+
const formId = content.formId;
|
|
499
|
+
if (typeof formId === "string" && formId.trim()) ids.add(formId);
|
|
500
|
+
}
|
|
501
|
+
return [...ids];
|
|
502
|
+
}
|
|
503
|
+
async function resolveForms(config, blocks, locale, defaultLocale, options) {
|
|
504
|
+
const ids = collectFormIds(blocks, locale, defaultLocale);
|
|
505
|
+
if (ids.length === 0) return {};
|
|
506
|
+
const client = createCmssyClient(config);
|
|
507
|
+
const entries = await Promise.all(
|
|
508
|
+
ids.map(async (id) => {
|
|
509
|
+
try {
|
|
510
|
+
const data = await client.queryScoped(FORM_QUERY, { formId: id }, options);
|
|
511
|
+
return [id, data.public.form.get];
|
|
512
|
+
} catch (err) {
|
|
513
|
+
if (typeof console !== "undefined") {
|
|
514
|
+
console.warn(`[cmssy] failed to resolve form ${id}`, err);
|
|
515
|
+
}
|
|
516
|
+
return [id, null];
|
|
517
|
+
}
|
|
518
|
+
})
|
|
519
|
+
);
|
|
520
|
+
const forms = {};
|
|
521
|
+
for (const [id, def] of entries) {
|
|
522
|
+
if (def) forms[id] = def;
|
|
523
|
+
}
|
|
524
|
+
return forms;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// src/data/site-locales.ts
|
|
528
|
+
var TTL_MS = 6e4;
|
|
529
|
+
var MAX_ENTRIES = 64;
|
|
530
|
+
var cache = /* @__PURE__ */ new Map();
|
|
531
|
+
function localesFromSiteConfig(siteConfig) {
|
|
532
|
+
const defaultLocale = siteConfig?.defaultLanguage || "en";
|
|
533
|
+
const enabled = siteConfig?.enabledLanguages ?? [];
|
|
534
|
+
return {
|
|
535
|
+
defaultLocale,
|
|
536
|
+
locales: enabled.length > 0 ? enabled : [defaultLocale]
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
async function resolveSiteLocales(config, options) {
|
|
540
|
+
const key = `${resolveApiUrl(config.apiUrl)}::${config.org}::${config.workspaceSlug}`;
|
|
541
|
+
const cached = cache.get(key);
|
|
542
|
+
if (cached && cached.expires > Date.now()) return cached.value;
|
|
543
|
+
cache.delete(key);
|
|
544
|
+
let value;
|
|
545
|
+
try {
|
|
546
|
+
const data = await graphqlRequest(
|
|
547
|
+
config,
|
|
548
|
+
SITE_CONFIG_QUERY,
|
|
549
|
+
{ workspaceSlug: config.workspaceSlug },
|
|
550
|
+
{ ...options, public: true, retry: options?.retry ?? {} },
|
|
551
|
+
"site config"
|
|
552
|
+
);
|
|
553
|
+
value = localesFromSiteConfig(data.public?.siteConfig ?? null);
|
|
554
|
+
} catch {
|
|
555
|
+
value = { defaultLocale: "en", locales: ["en"] };
|
|
556
|
+
}
|
|
557
|
+
if (cache.size >= MAX_ENTRIES) cache.clear();
|
|
558
|
+
cache.set(key, { value, expires: Date.now() + TTL_MS });
|
|
559
|
+
return value;
|
|
560
|
+
}
|
|
561
|
+
function splitLocaleFromPath(path, siteLocales) {
|
|
562
|
+
const first = path?.[0];
|
|
563
|
+
if (first && first !== siteLocales.defaultLocale && siteLocales.locales.includes(first)) {
|
|
564
|
+
return { locale: first, path: path.slice(1) };
|
|
565
|
+
}
|
|
566
|
+
return { locale: siteLocales.defaultLocale, path };
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// src/data/localize-href.ts
|
|
570
|
+
var PROTOCOL_OR_RELATIVE = /^([a-z][a-z0-9+.-]*:|\/\/)/i;
|
|
571
|
+
function isExternalHref(href) {
|
|
572
|
+
const value = href.trim();
|
|
573
|
+
if (!value) return true;
|
|
574
|
+
if (value.startsWith("#")) return true;
|
|
575
|
+
return PROTOCOL_OR_RELATIVE.test(value);
|
|
576
|
+
}
|
|
577
|
+
function stripLeadingLocale(path, locale) {
|
|
578
|
+
const segments = path.split("/");
|
|
579
|
+
const first = segments[1];
|
|
580
|
+
if (first && first !== locale.default && locale.enabled.includes(first)) {
|
|
581
|
+
segments.splice(1, 1);
|
|
582
|
+
const rest = segments.join("/");
|
|
583
|
+
return rest === "" ? "/" : rest;
|
|
584
|
+
}
|
|
585
|
+
return path;
|
|
586
|
+
}
|
|
587
|
+
function addLocalePrefix(path, target, locale) {
|
|
588
|
+
if (target === locale.default) return path;
|
|
589
|
+
if (path === "/") return `/${target}`;
|
|
590
|
+
return `/${target}${path}`;
|
|
591
|
+
}
|
|
592
|
+
function localizeHref(href, locale) {
|
|
593
|
+
const value = href.trim();
|
|
594
|
+
if (isExternalHref(value)) return href;
|
|
595
|
+
const boundary = value.search(/[?#]/);
|
|
596
|
+
const path = boundary === -1 ? value : value.slice(0, boundary);
|
|
597
|
+
const suffix = boundary === -1 ? "" : value.slice(boundary);
|
|
598
|
+
if (!path.startsWith("/")) return href;
|
|
599
|
+
const bare = stripLeadingLocale(path, locale);
|
|
600
|
+
return `${addLocalePrefix(bare, locale.current, locale)}${suffix}`;
|
|
601
|
+
}
|
|
602
|
+
function buildLocaleSwitchHref(target, pathname, locale) {
|
|
603
|
+
const path = pathname && pathname.startsWith("/") ? pathname : "/";
|
|
604
|
+
const bare = stripLeadingLocale(path, locale);
|
|
605
|
+
return addLocalePrefix(bare, target, locale);
|
|
606
|
+
}
|
|
607
|
+
var ANCHOR_HREF = /(<a\b(?:"[^"]*"|'[^']*'|[^>])*?\shref=)(["'])(.*?)\2/gi;
|
|
608
|
+
function localizeHtmlLinks(html, locale) {
|
|
609
|
+
return html.replace(
|
|
610
|
+
ANCHOR_HREF,
|
|
611
|
+
(_match, prefix, quote, url) => `${prefix}${quote}${localizeHref(url, locale)}${quote}`
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// src/data/relation-resolver.ts
|
|
616
|
+
var RECORDS_BY_IDS_QUERY = `query PublicRecordsByIds($workspaceId: String!, $ids: [String!]!, $locale: String) {
|
|
617
|
+
public {
|
|
618
|
+
model {
|
|
619
|
+
recordsByIds(workspaceId: $workspaceId, ids: $ids, locale: $locale) {
|
|
620
|
+
id modelId data status createdAt updatedAt
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}`;
|
|
625
|
+
var BY_IDS_CHUNK = 50;
|
|
626
|
+
var COLLECTION_DEFAULT_LIMIT = 50;
|
|
627
|
+
function relationModel(field) {
|
|
628
|
+
return field.relationTo?.startsWith("model:") ? field.relationTo.slice("model:".length) : void 0;
|
|
629
|
+
}
|
|
630
|
+
function storedIds(value) {
|
|
631
|
+
if (typeof value === "string" && value) return [value];
|
|
632
|
+
if (Array.isArray(value)) {
|
|
633
|
+
return value.filter((id) => typeof id === "string" && !!id);
|
|
634
|
+
}
|
|
635
|
+
return [];
|
|
636
|
+
}
|
|
637
|
+
function collectRefs(entries, schemas) {
|
|
638
|
+
const refs = [];
|
|
639
|
+
for (const entry of entries) {
|
|
640
|
+
const schema = schemas[entry.type];
|
|
641
|
+
if (!schema) continue;
|
|
642
|
+
for (const [key, field] of Object.entries(schema)) {
|
|
643
|
+
if (field.type !== "relation") continue;
|
|
644
|
+
const model = relationModel(field);
|
|
645
|
+
if (!model) continue;
|
|
646
|
+
refs.push({ content: entry.content, key, field, model });
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
return refs;
|
|
650
|
+
}
|
|
651
|
+
function isResolvedRecord(value) {
|
|
652
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.id === "string" && typeof value.data === "object";
|
|
653
|
+
}
|
|
654
|
+
function isListShaped(field) {
|
|
655
|
+
return field.relationMode === "all" || field.relationType === "hasMany" || field.multiple === true;
|
|
656
|
+
}
|
|
657
|
+
function normalizeRelationContent(content, schema, resolved) {
|
|
658
|
+
for (const [key, field] of Object.entries(schema)) {
|
|
659
|
+
if (field.type !== "relation") continue;
|
|
660
|
+
const value = content[key];
|
|
661
|
+
const fallback = resolved?.[key];
|
|
662
|
+
if (isListShaped(field)) {
|
|
663
|
+
if (Array.isArray(value)) {
|
|
664
|
+
const records = value.filter(isResolvedRecord);
|
|
665
|
+
if (records.length > 0 || value.length === 0) {
|
|
666
|
+
content[key] = records;
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
content[key] = Array.isArray(fallback) ? fallback.filter(isResolvedRecord) : [];
|
|
671
|
+
} else if (!isResolvedRecord(value)) {
|
|
672
|
+
if (value != null && value !== "" && isResolvedRecord(fallback)) {
|
|
673
|
+
content[key] = fallback;
|
|
674
|
+
} else {
|
|
675
|
+
delete content[key];
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
function collectionKey(ref) {
|
|
681
|
+
return [ref.model, ref.field.sort ?? "", ref.field.limit ?? ""].join("\0");
|
|
682
|
+
}
|
|
683
|
+
async function resolveRelationContent(config, entries, schemas, locale, requestOptions = {}) {
|
|
684
|
+
const refs = collectRefs(entries, schemas);
|
|
685
|
+
if (refs.length === 0) return;
|
|
686
|
+
const client = createCmssyClient(config);
|
|
687
|
+
const pickedIds = /* @__PURE__ */ new Set();
|
|
688
|
+
const collections = /* @__PURE__ */ new Map();
|
|
689
|
+
for (const ref of refs) {
|
|
690
|
+
if (ref.field.relationMode === "all") {
|
|
691
|
+
collections.set(collectionKey(ref), {
|
|
692
|
+
model: ref.model,
|
|
693
|
+
sort: ref.field.sort,
|
|
694
|
+
limit: ref.field.limit
|
|
695
|
+
});
|
|
696
|
+
} else {
|
|
697
|
+
for (const id of storedIds(ref.content[ref.key])) pickedIds.add(id);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
let recordsById;
|
|
701
|
+
let collectionItems;
|
|
702
|
+
try {
|
|
703
|
+
[recordsById, collectionItems] = await Promise.all([
|
|
704
|
+
fetchPickedRecords(client, [...pickedIds], locale, requestOptions),
|
|
705
|
+
fetchCollections(client, collections, locale, requestOptions)
|
|
706
|
+
]);
|
|
707
|
+
} catch (err) {
|
|
708
|
+
if (typeof console !== "undefined") {
|
|
709
|
+
console.error("[cmssy] relation resolution failed", err);
|
|
710
|
+
}
|
|
711
|
+
for (const ref of refs) {
|
|
712
|
+
if (ref.field.relationMode === "all" || Array.isArray(ref.content[ref.key])) {
|
|
713
|
+
ref.content[ref.key] = [];
|
|
714
|
+
} else {
|
|
715
|
+
delete ref.content[ref.key];
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
for (const ref of refs) {
|
|
721
|
+
if (ref.field.relationMode === "all") {
|
|
722
|
+
ref.content[ref.key] = collectionItems.get(collectionKey(ref)) ?? [];
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
const value = ref.content[ref.key];
|
|
726
|
+
if (Array.isArray(value)) {
|
|
727
|
+
ref.content[ref.key] = storedIds(value).map((id) => recordsById.get(id)).filter((record) => !!record);
|
|
728
|
+
} else if (typeof value === "string" && value) {
|
|
729
|
+
const record = recordsById.get(value);
|
|
730
|
+
if (record) ref.content[ref.key] = record;
|
|
731
|
+
else delete ref.content[ref.key];
|
|
732
|
+
} else {
|
|
733
|
+
delete ref.content[ref.key];
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
async function fetchPickedRecords(client, ids, locale, requestOptions = {}) {
|
|
738
|
+
const byId = /* @__PURE__ */ new Map();
|
|
739
|
+
const chunks = [];
|
|
740
|
+
for (let i = 0; i < ids.length; i += BY_IDS_CHUNK) {
|
|
741
|
+
chunks.push(ids.slice(i, i + BY_IDS_CHUNK));
|
|
742
|
+
}
|
|
743
|
+
await Promise.all(
|
|
744
|
+
chunks.map(async (chunk) => {
|
|
745
|
+
const result = await client.queryScoped(
|
|
746
|
+
RECORDS_BY_IDS_QUERY,
|
|
747
|
+
{ ids: chunk, locale: locale ?? null },
|
|
748
|
+
requestOptions
|
|
749
|
+
);
|
|
750
|
+
for (const record of result.public.model.recordsByIds) {
|
|
751
|
+
byId.set(record.id, record);
|
|
752
|
+
}
|
|
753
|
+
})
|
|
754
|
+
);
|
|
755
|
+
return byId;
|
|
756
|
+
}
|
|
757
|
+
async function fetchCollections(client, collections, locale, requestOptions = {}) {
|
|
758
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
759
|
+
await Promise.all(
|
|
760
|
+
[...collections.entries()].map(async ([key, { model, sort, limit }]) => {
|
|
761
|
+
const result = await client.queryScoped(
|
|
762
|
+
MODEL_RECORDS_QUERY,
|
|
763
|
+
{
|
|
764
|
+
modelSlug: model,
|
|
765
|
+
sort: sort ?? null,
|
|
766
|
+
limit: limit ?? COLLECTION_DEFAULT_LIMIT,
|
|
767
|
+
locale: locale ?? null
|
|
768
|
+
},
|
|
769
|
+
requestOptions
|
|
770
|
+
);
|
|
771
|
+
byKey.set(key, result.public.model.records.items);
|
|
772
|
+
})
|
|
773
|
+
);
|
|
774
|
+
return byKey;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// src/locale.ts
|
|
778
|
+
var CMSSY_LOCALE_HEADER = "x-cmssy-locale";
|
|
779
|
+
async function localeForPathname(config, pathname) {
|
|
780
|
+
const siteLocales = await resolveSiteLocales(config);
|
|
781
|
+
const segments = pathname.split("/").filter(Boolean);
|
|
782
|
+
return splitLocaleFromPath(segments, siteLocales).locale;
|
|
783
|
+
}
|
|
784
|
+
async function splitCmssyLocale(config, path) {
|
|
785
|
+
const siteLocales = await resolveSiteLocales(config);
|
|
786
|
+
return splitLocaleFromPath(path, siteLocales);
|
|
787
|
+
}
|
|
788
|
+
async function localeForPath(config, path) {
|
|
789
|
+
const siteLocales = await resolveSiteLocales(config);
|
|
790
|
+
const segments = Array.isArray(path) ? path.filter(Boolean) : path.split("/").filter(Boolean);
|
|
791
|
+
return splitLocaleFromPath(segments, siteLocales).locale;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// src/seo-paths.ts
|
|
795
|
+
function normalizeSlug2(slug) {
|
|
796
|
+
if (slug === "/" || slug === "") return "/";
|
|
797
|
+
return slug.startsWith("/") ? slug : `/${slug}`;
|
|
798
|
+
}
|
|
799
|
+
function localizedPath(slug, locale, defaultLocale) {
|
|
800
|
+
const normalized = normalizeSlug2(slug);
|
|
801
|
+
const base = normalized === "/" ? "" : normalized;
|
|
802
|
+
return locale === defaultLocale ? base || "/" : `/${locale}${base}`;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// src/bridge/messages.ts
|
|
806
|
+
function normalizeOrigin(origin) {
|
|
807
|
+
const trimmed = origin.trim();
|
|
808
|
+
if (trimmed === "*") return "*";
|
|
809
|
+
try {
|
|
810
|
+
return new URL(trimmed).origin;
|
|
811
|
+
} catch {
|
|
812
|
+
return trimmed;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
function resolveInitialTarget(editorOrigin) {
|
|
816
|
+
const list = (Array.isArray(editorOrigin) ? editorOrigin : [editorOrigin]).map((origin) => normalizeOrigin(origin));
|
|
817
|
+
if (list.includes("*")) return "*";
|
|
818
|
+
if (list.length === 1) return list[0];
|
|
819
|
+
const referrerOrigin = typeof document !== "undefined" && document.referrer ? normalizeOrigin(document.referrer) : "";
|
|
820
|
+
return list.find((origin) => origin === referrerOrigin) ?? list[0];
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// src/secret-match.ts
|
|
824
|
+
var MAX_SECRET_LENGTH = 256;
|
|
825
|
+
async function cmssySecretsMatch(a, b) {
|
|
826
|
+
if (a.length > MAX_SECRET_LENGTH || b.length > MAX_SECRET_LENGTH) {
|
|
827
|
+
return false;
|
|
828
|
+
}
|
|
829
|
+
const encoder = new TextEncoder();
|
|
830
|
+
const [ha, hb] = await Promise.all([
|
|
831
|
+
crypto.subtle.digest("SHA-256", encoder.encode(a)),
|
|
832
|
+
crypto.subtle.digest("SHA-256", encoder.encode(b))
|
|
833
|
+
]);
|
|
834
|
+
const va = new Uint8Array(ha);
|
|
835
|
+
const vb = new Uint8Array(hb);
|
|
836
|
+
let diff = 0;
|
|
837
|
+
for (let i = 0; i < va.length; i += 1) {
|
|
838
|
+
diff |= (va[i] ?? 0) ^ (vb[i] ?? 0);
|
|
839
|
+
}
|
|
840
|
+
return diff === 0;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// src/config.ts
|
|
844
|
+
var DEFAULT_CMSSY_EDITOR_ORIGINS = [
|
|
845
|
+
"https://cmssy.io",
|
|
846
|
+
"https://www.cmssy.io"
|
|
847
|
+
];
|
|
848
|
+
function parseEditorOriginEnv(raw) {
|
|
849
|
+
if (!raw) return void 0;
|
|
850
|
+
const parts = raw.split(",").map((o) => o.trim()).filter((o) => o.length > 0);
|
|
851
|
+
if (parts.length === 0) return void 0;
|
|
852
|
+
return parts.length === 1 ? parts[0] : parts;
|
|
853
|
+
}
|
|
854
|
+
function isDevelopment() {
|
|
855
|
+
return typeof process !== "undefined" && process.env.NODE_ENV === "development";
|
|
856
|
+
}
|
|
857
|
+
function resolveEditorOrigin(editorOrigin) {
|
|
858
|
+
const value = editorOrigin ?? (typeof process !== "undefined" ? parseEditorOriginEnv(process.env.CMSSY_EDITOR_ORIGIN) : void 0);
|
|
859
|
+
if (value === void 0) {
|
|
860
|
+
return isDevelopment() ? "*" : DEFAULT_CMSSY_EDITOR_ORIGINS;
|
|
861
|
+
}
|
|
862
|
+
if (Array.isArray(value)) {
|
|
863
|
+
const cleaned = value.filter((o) => o && o.trim().length > 0);
|
|
864
|
+
return cleaned.length > 0 ? cleaned : DEFAULT_CMSSY_EDITOR_ORIGINS;
|
|
865
|
+
}
|
|
866
|
+
return value.trim().length > 0 ? value : DEFAULT_CMSSY_EDITOR_ORIGINS;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// src/csp.ts
|
|
870
|
+
function toCspOrigin(origin) {
|
|
871
|
+
if (origin === "*") return "*";
|
|
872
|
+
let parsed;
|
|
873
|
+
try {
|
|
874
|
+
parsed = new URL(origin);
|
|
875
|
+
} catch {
|
|
876
|
+
throw new Error(`cmssy: invalid editorOrigin "${origin}"`);
|
|
877
|
+
}
|
|
878
|
+
if (parsed.origin === "null") {
|
|
879
|
+
throw new Error(`cmssy: editorOrigin "${origin}" has no usable origin`);
|
|
880
|
+
}
|
|
881
|
+
return parsed.origin;
|
|
882
|
+
}
|
|
883
|
+
function frameAncestors(editorOrigin) {
|
|
884
|
+
const resolved = resolveEditorOrigin(editorOrigin);
|
|
885
|
+
const origins = Array.isArray(resolved) ? resolved : [resolved];
|
|
886
|
+
if (origins.length === 0) {
|
|
887
|
+
throw new Error(
|
|
888
|
+
"cmssy: editorOrigin must contain at least one valid origin"
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
const normalized = origins.map((origin) => toCspOrigin(origin.trim()));
|
|
892
|
+
return `frame-ancestors ${normalized.join(" ")}`;
|
|
893
|
+
}
|
|
894
|
+
function cmssyCspHeaders(options) {
|
|
895
|
+
return {
|
|
896
|
+
"Content-Security-Policy": frameAncestors(options.editorOrigin)
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
exports.CMSSY_LOCALE_HEADER = CMSSY_LOCALE_HEADER;
|
|
901
|
+
exports.FORM_QUERY = FORM_QUERY;
|
|
902
|
+
exports.MODEL_DEFINITIONS_QUERY = MODEL_DEFINITIONS_QUERY;
|
|
903
|
+
exports.MODEL_RECORDS_QUERY = MODEL_RECORDS_QUERY;
|
|
904
|
+
exports.RECORDS_BY_IDS_QUERY = RECORDS_BY_IDS_QUERY;
|
|
905
|
+
exports.SITE_CONFIG_QUERY = SITE_CONFIG_QUERY;
|
|
906
|
+
exports.SUBMIT_FORM_MUTATION = SUBMIT_FORM_MUTATION;
|
|
907
|
+
exports.asBucket = asBucket;
|
|
908
|
+
exports.buildLocaleSwitchHref = buildLocaleSwitchHref;
|
|
909
|
+
exports.cachedWorkspaceId = cachedWorkspaceId;
|
|
910
|
+
exports.clearWorkspaceIdCache = clearWorkspaceIdCache;
|
|
911
|
+
exports.cmssyCspHeaders = cmssyCspHeaders;
|
|
912
|
+
exports.cmssySecretsMatch = cmssySecretsMatch;
|
|
913
|
+
exports.collectFormIds = collectFormIds;
|
|
914
|
+
exports.fetchLayouts = fetchLayouts;
|
|
915
|
+
exports.fetchPage = fetchPage;
|
|
916
|
+
exports.fetchPageById = fetchPageById;
|
|
917
|
+
exports.fetchPageMeta = fetchPageMeta;
|
|
918
|
+
exports.fetchPages = fetchPages;
|
|
919
|
+
exports.fetchSiteConfig = fetchSiteConfig;
|
|
920
|
+
exports.getBlockContentForLanguage = getBlockContentForLanguage;
|
|
921
|
+
exports.isDevelopment = isDevelopment;
|
|
922
|
+
exports.localeForPath = localeForPath;
|
|
923
|
+
exports.localeForPathname = localeForPathname;
|
|
924
|
+
exports.localesFromSiteConfig = localesFromSiteConfig;
|
|
925
|
+
exports.localizeHref = localizeHref;
|
|
926
|
+
exports.localizeHtmlLinks = localizeHtmlLinks;
|
|
927
|
+
exports.localizedPath = localizedPath;
|
|
928
|
+
exports.normalizeRelationContent = normalizeRelationContent;
|
|
929
|
+
exports.normalizeSlug = normalizeSlug;
|
|
930
|
+
exports.resolveApiUrl = resolveApiUrl;
|
|
931
|
+
exports.resolveForms = resolveForms;
|
|
932
|
+
exports.resolveInitialTarget = resolveInitialTarget;
|
|
933
|
+
exports.resolvePublicUrl = resolvePublicUrl;
|
|
934
|
+
exports.resolveRelationContent = resolveRelationContent;
|
|
935
|
+
exports.resolveSiteLocales = resolveSiteLocales;
|
|
936
|
+
exports.resolveWorkspaceId = resolveWorkspaceId;
|
|
937
|
+
exports.splitCmssyLocale = splitCmssyLocale;
|
|
938
|
+
exports.splitLocaleFromPath = splitLocaleFromPath;
|
|
939
|
+
exports.toCspOrigin = toCspOrigin;
|