siteplane 0.1.68 → 0.2.0-next.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 +59 -22
- package/dist/bin/siteplane.js +1422 -1475
- package/dist/chunk-BLJCWIYD.js +340 -0
- package/dist/chunk-KZ3AQ6OA.js +27 -0
- package/dist/components.d.ts +22 -0
- package/dist/components.js +43 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.js +6 -0
- package/dist/index.d.ts +38 -1
- package/dist/index.js +128 -1916
- package/dist/internal/analytics.js +91 -0
- package/dist/internal/bridge-loader.js +28 -0
- package/dist/internal/bridge.js +1 -0
- package/dist/next.cjs +204 -0
- package/dist/next.d.ts +29 -119
- package/dist/next.js +128 -2819
- package/dist/route.d.ts +13 -0
- package/dist/route.js +144 -0
- package/package.json +46 -11
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readSiteplaneConfig
|
|
3
|
+
} from "./chunk-KZ3AQ6OA.js";
|
|
4
|
+
|
|
5
|
+
// ../shared/dist/signing-keys.js
|
|
6
|
+
var pkcs8Ed25519Prefix = Uint8Array.from([
|
|
7
|
+
48,
|
|
8
|
+
46,
|
|
9
|
+
2,
|
|
10
|
+
1,
|
|
11
|
+
0,
|
|
12
|
+
48,
|
|
13
|
+
5,
|
|
14
|
+
6,
|
|
15
|
+
3,
|
|
16
|
+
43,
|
|
17
|
+
101,
|
|
18
|
+
112,
|
|
19
|
+
4,
|
|
20
|
+
34,
|
|
21
|
+
4,
|
|
22
|
+
32
|
|
23
|
+
]);
|
|
24
|
+
var ed25519 = { name: "Ed25519" };
|
|
25
|
+
async function verifyWithSigningKey(publicKey, signature, data) {
|
|
26
|
+
const key = await crypto.subtle.importKey("raw", decodeKey(publicKey, "whpk_"), ed25519, false, ["verify"]);
|
|
27
|
+
return crypto.subtle.verify(ed25519, key, signature, data);
|
|
28
|
+
}
|
|
29
|
+
function decodeBase64(value) {
|
|
30
|
+
const standard = value.replace(/-/gu, "+").replace(/_/gu, "/");
|
|
31
|
+
return Uint8Array.from(atob(standard.padEnd(Math.ceil(standard.length / 4) * 4, "=")), (character) => character.charCodeAt(0));
|
|
32
|
+
}
|
|
33
|
+
function decodeKey(key, prefix) {
|
|
34
|
+
let bytes;
|
|
35
|
+
try {
|
|
36
|
+
if (key.startsWith(prefix))
|
|
37
|
+
bytes = decodeBase64(key.slice(prefix.length));
|
|
38
|
+
} catch {
|
|
39
|
+
bytes = void 0;
|
|
40
|
+
}
|
|
41
|
+
if (bytes?.length !== 32)
|
|
42
|
+
throw new Error("invalid_signing_key");
|
|
43
|
+
return bytes;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ../shared/dist/preview-token.js
|
|
47
|
+
var PREVIEW_TOKEN_MAX_SECONDS = 60 * 60;
|
|
48
|
+
var header = encodeJson({ alg: "EdDSA", typ: "JWT" });
|
|
49
|
+
var clockSkewSeconds = 60;
|
|
50
|
+
async function verifyPreviewToken(input) {
|
|
51
|
+
const parts = input.token.split(".");
|
|
52
|
+
const nowSeconds = Math.floor((input.now ?? /* @__PURE__ */ new Date()).getTime() / 1e3);
|
|
53
|
+
let claims = null;
|
|
54
|
+
let signature = null;
|
|
55
|
+
if (parts.length === 3 && parts[0] === header) {
|
|
56
|
+
try {
|
|
57
|
+
claims = readClaims(JSON.parse(new TextDecoder().decode(decodeBase64(parts[1]))), nowSeconds);
|
|
58
|
+
signature = decodeBase64(parts[2]);
|
|
59
|
+
} catch {
|
|
60
|
+
claims = null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (!claims || !signature)
|
|
64
|
+
return { ok: false, reason: "malformed" };
|
|
65
|
+
const signingInput = new TextEncoder().encode(`${parts[0]}.${parts[1]}`);
|
|
66
|
+
let signed = false;
|
|
67
|
+
for (const publicKey of input.publicKeys) {
|
|
68
|
+
signed = await verifyWithSigningKey(publicKey, signature, signingInput);
|
|
69
|
+
if (signed)
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
if (!signed)
|
|
73
|
+
return { ok: false, reason: "signature_invalid" };
|
|
74
|
+
if (claims.aud !== input.siteId)
|
|
75
|
+
return { ok: false, reason: "wrong_site" };
|
|
76
|
+
if (claims.exp <= nowSeconds)
|
|
77
|
+
return { ok: false, reason: "expired" };
|
|
78
|
+
return { ok: true, claims };
|
|
79
|
+
}
|
|
80
|
+
function readClaims(value, nowSeconds) {
|
|
81
|
+
if (typeof value !== "object" || value === null)
|
|
82
|
+
return null;
|
|
83
|
+
const claims = value;
|
|
84
|
+
const { aud, sub, scope, actor, editor_origin: editorOrigin, iat, exp, jti } = claims;
|
|
85
|
+
if (typeof aud !== "string" || !aud || typeof sub !== "string" || !sub || scope !== "drafts:read" || actor !== "workspace_member" && actor !== "site_member" || typeof editorOrigin !== "string" || !isOrigin(editorOrigin) || !Number.isSafeInteger(iat) || !Number.isSafeInteger(exp) || typeof iat !== "number" || typeof exp !== "number" || exp - iat > PREVIEW_TOKEN_MAX_SECONDS || iat > nowSeconds + clockSkewSeconds || typeof jti !== "string" || !jti) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
return { aud, sub, scope, actor, editor_origin: editorOrigin, iat, exp, jti };
|
|
89
|
+
}
|
|
90
|
+
function isOrigin(value) {
|
|
91
|
+
try {
|
|
92
|
+
return new URL(value).origin === value;
|
|
93
|
+
} catch {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function encodeJson(value) {
|
|
98
|
+
return toBase64Url(new TextEncoder().encode(JSON.stringify(value)));
|
|
99
|
+
}
|
|
100
|
+
function toBase64Url(bytes) {
|
|
101
|
+
let binary = "";
|
|
102
|
+
for (const byte of bytes)
|
|
103
|
+
binary += String.fromCharCode(byte);
|
|
104
|
+
return btoa(binary).replace(/\+/gu, "-").replace(/\//gu, "_").replace(/=+$/u, "");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ../shared/dist/webhook-signature.js
|
|
108
|
+
var WEBHOOK_TOLERANCE_SECONDS = 5 * 60;
|
|
109
|
+
var encoder = new TextEncoder();
|
|
110
|
+
function webhookSignedContent(id, timestamp, payload) {
|
|
111
|
+
return `${id}.${timestamp}.${payload}`;
|
|
112
|
+
}
|
|
113
|
+
async function verifyWebhook(input) {
|
|
114
|
+
const read = (name) => input.headers instanceof Headers ? input.headers.get(name) : input.headers[name];
|
|
115
|
+
const id = read("webhook-id");
|
|
116
|
+
const timestamp = read("webhook-timestamp");
|
|
117
|
+
const signatureHeader = read("webhook-signature");
|
|
118
|
+
if (!id || !timestamp || !signatureHeader)
|
|
119
|
+
return { ok: false, reason: "missing_headers" };
|
|
120
|
+
const nowSeconds = Math.floor((input.now ?? /* @__PURE__ */ new Date()).getTime() / 1e3);
|
|
121
|
+
if (!/^\d{1,12}$/u.test(timestamp) || Math.abs(nowSeconds - Number(timestamp)) > WEBHOOK_TOLERANCE_SECONDS) {
|
|
122
|
+
return { ok: false, reason: "timestamp_out_of_window" };
|
|
123
|
+
}
|
|
124
|
+
const content = encoder.encode(webhookSignedContent(id, timestamp, input.payload));
|
|
125
|
+
const signatures = signatureHeader.split(" ").flatMap((entry) => {
|
|
126
|
+
const [version, signature] = entry.split(",");
|
|
127
|
+
if (version !== "v1a" || !signature)
|
|
128
|
+
return [];
|
|
129
|
+
try {
|
|
130
|
+
return [decodeBase64(signature)];
|
|
131
|
+
} catch {
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
for (const publicKey of input.publicKeys) {
|
|
136
|
+
for (const signature of signatures) {
|
|
137
|
+
if (await verifyWithSigningKey(publicKey, signature, content))
|
|
138
|
+
return { ok: true };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return { ok: false, reason: "no_matching_signature" };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// src/runtime/preview.ts
|
|
145
|
+
var PREVIEW_COOKIE = "siteplane-preview";
|
|
146
|
+
async function verifySitePreviewToken(config, token, keys, now) {
|
|
147
|
+
const verify = async (reload) => verifyPreviewToken({
|
|
148
|
+
token,
|
|
149
|
+
siteId: config.siteId,
|
|
150
|
+
publicKeys: await keys(config, "preview", reload ? { reload } : {}),
|
|
151
|
+
now
|
|
152
|
+
});
|
|
153
|
+
let result = await verify(false);
|
|
154
|
+
if (!result.ok && result.reason === "signature_invalid") result = await verify(true);
|
|
155
|
+
return result.ok ? result.claims : null;
|
|
156
|
+
}
|
|
157
|
+
async function verifySiteRevalidation(config, request, keys, now) {
|
|
158
|
+
const verify = async (reload) => verifyWebhook({
|
|
159
|
+
headers: request.headers,
|
|
160
|
+
payload: request.payload,
|
|
161
|
+
publicKeys: await keys(config, "revalidation", reload ? { reload } : {}),
|
|
162
|
+
now
|
|
163
|
+
});
|
|
164
|
+
let result = await verify(false);
|
|
165
|
+
if (!result.ok && result.reason === "no_matching_signature") result = await verify(true);
|
|
166
|
+
return result.ok;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// src/runtime/server.ts
|
|
170
|
+
import { cookies, draftMode } from "next/headers";
|
|
171
|
+
import projectConfig from "siteplane/config";
|
|
172
|
+
|
|
173
|
+
// src/runtime/fetch-json.ts
|
|
174
|
+
var SITEPLANE_DEADLINE_MS = 3e3;
|
|
175
|
+
var SITEPLANE_DRAFT_MODE_DEADLINE_MS = 1e4;
|
|
176
|
+
var MAX_BODY_BYTES = 10 * 1024 * 1024;
|
|
177
|
+
async function fetchJson(fetchImpl, url, init, deadlineMs = SITEPLANE_DEADLINE_MS) {
|
|
178
|
+
const controller = new AbortController();
|
|
179
|
+
let timer;
|
|
180
|
+
const deadline = new Promise((resolve) => {
|
|
181
|
+
timer = setTimeout(() => {
|
|
182
|
+
controller.abort();
|
|
183
|
+
resolve(null);
|
|
184
|
+
}, deadlineMs);
|
|
185
|
+
});
|
|
186
|
+
const answer = (async () => {
|
|
187
|
+
const response = await fetchImpl(url, { ...init, signal: controller.signal });
|
|
188
|
+
const text = await readBoundedText(response);
|
|
189
|
+
if (text === null) return null;
|
|
190
|
+
return { status: response.status, body: JSON.parse(text) };
|
|
191
|
+
})().catch(() => null);
|
|
192
|
+
try {
|
|
193
|
+
return await Promise.race([answer, deadline]);
|
|
194
|
+
} finally {
|
|
195
|
+
clearTimeout(timer);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async function readBoundedText(response) {
|
|
199
|
+
if (!response.body) return "";
|
|
200
|
+
const reader = response.body.getReader();
|
|
201
|
+
const chunks = [];
|
|
202
|
+
let bytes = 0;
|
|
203
|
+
for (; ; ) {
|
|
204
|
+
const chunk = await reader.read();
|
|
205
|
+
if (chunk.done) break;
|
|
206
|
+
bytes += chunk.value.byteLength;
|
|
207
|
+
if (bytes > MAX_BODY_BYTES) {
|
|
208
|
+
await reader.cancel();
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
chunks.push(chunk.value);
|
|
212
|
+
}
|
|
213
|
+
const combined = new Uint8Array(bytes);
|
|
214
|
+
let offset = 0;
|
|
215
|
+
for (const chunk of chunks) {
|
|
216
|
+
combined.set(chunk, offset);
|
|
217
|
+
offset += chunk.byteLength;
|
|
218
|
+
}
|
|
219
|
+
return new TextDecoder().decode(combined);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// src/runtime/signing-keys.ts
|
|
223
|
+
var MAX_AGE_MS = 60 * 60 * 1e3;
|
|
224
|
+
var RELOAD_THROTTLE_MS = 60 * 1e3;
|
|
225
|
+
function createSigningKeyStore(dependencies) {
|
|
226
|
+
const cached = /* @__PURE__ */ new Map();
|
|
227
|
+
const lastAttempt = /* @__PURE__ */ new Map();
|
|
228
|
+
async function load(apiOrigin) {
|
|
229
|
+
lastAttempt.set(apiOrigin, dependencies.now());
|
|
230
|
+
const answer = await fetchJson(dependencies.fetch, `${apiOrigin}/api/runtime/v1/signing-keys`, {
|
|
231
|
+
cache: "no-store"
|
|
232
|
+
});
|
|
233
|
+
const keys = answer?.status === 200 ? readKeySet(answer.body) : null;
|
|
234
|
+
if (keys) cached.set(apiOrigin, { keys, loadedAt: dependencies.now() });
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
async keys(config, purpose, options = {}) {
|
|
238
|
+
const entry = cached.get(config.apiOrigin);
|
|
239
|
+
const now = dependencies.now();
|
|
240
|
+
const expired = !entry || now - entry.loadedAt > MAX_AGE_MS;
|
|
241
|
+
const reloadAllowed = now - (lastAttempt.get(config.apiOrigin) ?? Number.NEGATIVE_INFINITY) >= RELOAD_THROTTLE_MS;
|
|
242
|
+
if ((expired || options.reload) && (reloadAllowed || !entry)) await load(config.apiOrigin);
|
|
243
|
+
return cached.get(config.apiOrigin)?.keys[purpose] ?? [];
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function readKeySet(body) {
|
|
248
|
+
if (typeof body !== "object" || body === null) return null;
|
|
249
|
+
const { revalidation, preview } = body;
|
|
250
|
+
return isKeyList(revalidation) && isKeyList(preview) ? { revalidation, preview } : null;
|
|
251
|
+
}
|
|
252
|
+
function isKeyList(value) {
|
|
253
|
+
return Array.isArray(value) && value.every((key) => typeof key === "string" && key.startsWith("whpk_"));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// src/runtime/values.ts
|
|
257
|
+
function siteCacheTag(siteId) {
|
|
258
|
+
return `siteplane:${siteId}`;
|
|
259
|
+
}
|
|
260
|
+
function createValueSource(dependencies) {
|
|
261
|
+
const lastGood = /* @__PURE__ */ new Map();
|
|
262
|
+
return {
|
|
263
|
+
async published(config, options = {}) {
|
|
264
|
+
const key = `${config.apiOrigin} ${config.siteId}`;
|
|
265
|
+
const values = await readValues(
|
|
266
|
+
dependencies.fetch,
|
|
267
|
+
config,
|
|
268
|
+
`${config.apiOrigin}/api/runtime/v1/sites/${config.siteId}/values`,
|
|
269
|
+
{ next: { revalidate: 60, tags: [siteCacheTag(config.siteId)] } },
|
|
270
|
+
options.draftMode ? SITEPLANE_DRAFT_MODE_DEADLINE_MS : SITEPLANE_DEADLINE_MS
|
|
271
|
+
);
|
|
272
|
+
if (values) {
|
|
273
|
+
lastGood.set(key, values);
|
|
274
|
+
return values;
|
|
275
|
+
}
|
|
276
|
+
return lastGood.get(key) ?? {};
|
|
277
|
+
},
|
|
278
|
+
async drafts(config, previewToken) {
|
|
279
|
+
return readValues(
|
|
280
|
+
dependencies.fetch,
|
|
281
|
+
config,
|
|
282
|
+
`${config.apiOrigin}/api/runtime/v1/sites/${config.siteId}/drafts`,
|
|
283
|
+
{ cache: "no-store", headers: { authorization: `Bearer ${previewToken}` } },
|
|
284
|
+
SITEPLANE_DRAFT_MODE_DEADLINE_MS
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
async function readValues(fetchImpl, config, url, init, deadlineMs) {
|
|
290
|
+
const answer = await fetchJson(fetchImpl, url, init, deadlineMs);
|
|
291
|
+
if (!answer || answer.status !== 200) return null;
|
|
292
|
+
const { siteId, values } = answer.body ?? {};
|
|
293
|
+
if (siteId !== config.siteId || typeof values !== "object" || values === null || Array.isArray(values)) {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
return values;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// src/runtime/server.ts
|
|
300
|
+
var siteConfig = readSiteplaneConfig(projectConfig);
|
|
301
|
+
var nextFetch = (input, init) => fetch(input, init);
|
|
302
|
+
var valueSource = createValueSource({ fetch: nextFetch });
|
|
303
|
+
var signingKeys = createSigningKeyStore({ fetch: nextFetch, now: () => Date.now() });
|
|
304
|
+
async function isDraftMode() {
|
|
305
|
+
try {
|
|
306
|
+
return (await draftMode()).isEnabled;
|
|
307
|
+
} catch {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
async function readPreviewToken() {
|
|
312
|
+
if (!await isDraftMode()) return null;
|
|
313
|
+
try {
|
|
314
|
+
return (await cookies()).get(PREVIEW_COOKIE)?.value ?? null;
|
|
315
|
+
} catch {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
async function loadSiteValues() {
|
|
320
|
+
if (!siteConfig) return {};
|
|
321
|
+
const draft = await isDraftMode();
|
|
322
|
+
const token = draft ? await readPreviewToken() : null;
|
|
323
|
+
const [published, drafts] = await Promise.all([
|
|
324
|
+
valueSource.published(siteConfig, { draftMode: draft }),
|
|
325
|
+
token ? valueSource.drafts(siteConfig, token) : Promise.resolve(null)
|
|
326
|
+
]);
|
|
327
|
+
return drafts ? { ...published, ...drafts } : published;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export {
|
|
331
|
+
PREVIEW_COOKIE,
|
|
332
|
+
verifySitePreviewToken,
|
|
333
|
+
verifySiteRevalidation,
|
|
334
|
+
siteCacheTag,
|
|
335
|
+
siteConfig,
|
|
336
|
+
signingKeys,
|
|
337
|
+
isDraftMode,
|
|
338
|
+
readPreviewToken,
|
|
339
|
+
loadSiteValues
|
|
340
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// ../shared/dist/siteplane-config.js
|
|
2
|
+
var SITEPLANE_CONFIG_FILE = "siteplane.config.json";
|
|
3
|
+
var siteIdPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/u;
|
|
4
|
+
function readSiteplaneConfig(value) {
|
|
5
|
+
if (typeof value !== "object" || value === null)
|
|
6
|
+
return null;
|
|
7
|
+
const { siteId, apiOrigin, editorOrigins } = value;
|
|
8
|
+
if (typeof siteId !== "string" || !siteIdPattern.test(siteId) || !isWebOrigin(apiOrigin) || !Array.isArray(editorOrigins) || !editorOrigins.every(isWebOrigin)) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
return { siteId, apiOrigin, editorOrigins: [...editorOrigins] };
|
|
12
|
+
}
|
|
13
|
+
function isWebOrigin(value) {
|
|
14
|
+
if (typeof value !== "string")
|
|
15
|
+
return false;
|
|
16
|
+
try {
|
|
17
|
+
const url = new URL(value);
|
|
18
|
+
return (url.protocol === "https:" || url.protocol === "http:") && url.origin === value;
|
|
19
|
+
} catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
SITEPLANE_CONFIG_FILE,
|
|
26
|
+
readSiteplaneConfig
|
|
27
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `<SiteplaneBridge />` in the root layout (Vertrag 54 §8.2). A server
|
|
5
|
+
* component: for normal visitors it renders nothing. In Draft Mode with a valid
|
|
6
|
+
* preview token it loads the bridge, which talks only to the token's editor
|
|
7
|
+
* origin.
|
|
8
|
+
*/
|
|
9
|
+
declare function SiteplaneBridge(): Promise<react_jsx_runtime.JSX.Element | null>;
|
|
10
|
+
/**
|
|
11
|
+
* `<SiteplaneAnalytics siteId="…" />` (Vertrag 54 §10): page views, App Router
|
|
12
|
+
* navigations and clicks, without cookies or browser storage, sent to
|
|
13
|
+
* Siteplane's collector. In Draft Mode, which only the editor's preview uses,
|
|
14
|
+
* it renders nothing, so previews never count. The client part reads the
|
|
15
|
+
* search parameters and therefore sits in its own Suspense boundary, as with
|
|
16
|
+
* Vercel's `<Analytics />`.
|
|
17
|
+
*/
|
|
18
|
+
declare function SiteplaneAnalytics({ siteId }: {
|
|
19
|
+
siteId: string;
|
|
20
|
+
}): Promise<react_jsx_runtime.JSX.Element | null>;
|
|
21
|
+
|
|
22
|
+
export { SiteplaneAnalytics, SiteplaneBridge };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isDraftMode,
|
|
3
|
+
readPreviewToken,
|
|
4
|
+
signingKeys,
|
|
5
|
+
siteConfig,
|
|
6
|
+
verifySitePreviewToken
|
|
7
|
+
} from "./chunk-BLJCWIYD.js";
|
|
8
|
+
import "./chunk-KZ3AQ6OA.js";
|
|
9
|
+
|
|
10
|
+
// ../shared/dist/site-analytics.js
|
|
11
|
+
var SITE_ANALYTICS_SEND_PATH = "/api/analytics/send";
|
|
12
|
+
function siteAnalyticsEndpoint(apiOrigin) {
|
|
13
|
+
return `${apiOrigin}${SITE_ANALYTICS_SEND_PATH}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/components.tsx
|
|
17
|
+
import { Suspense } from "react";
|
|
18
|
+
import { AnalyticsTracker } from "siteplane/internal/analytics";
|
|
19
|
+
import { BridgeLoader } from "siteplane/internal/bridge-loader";
|
|
20
|
+
import { jsx } from "react/jsx-runtime";
|
|
21
|
+
async function SiteplaneBridge() {
|
|
22
|
+
const editorOrigin = await readEditorOrigin();
|
|
23
|
+
return editorOrigin ? /* @__PURE__ */ jsx(BridgeLoader, { editorOrigin }) : null;
|
|
24
|
+
}
|
|
25
|
+
async function readEditorOrigin() {
|
|
26
|
+
try {
|
|
27
|
+
const token = await readPreviewToken();
|
|
28
|
+
if (!siteConfig || !token) return null;
|
|
29
|
+
const claims = await verifySitePreviewToken(siteConfig, token, signingKeys.keys, /* @__PURE__ */ new Date());
|
|
30
|
+
return claims?.editor_origin ?? null;
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function SiteplaneAnalytics({ siteId }) {
|
|
36
|
+
if (await isDraftMode()) return null;
|
|
37
|
+
const endpoint = siteAnalyticsEndpoint(siteConfig?.apiOrigin ?? "https://siteplane.io");
|
|
38
|
+
return /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(AnalyticsTracker, { siteId, endpoint }) });
|
|
39
|
+
}
|
|
40
|
+
export {
|
|
41
|
+
SiteplaneAnalytics,
|
|
42
|
+
SiteplaneBridge
|
|
43
|
+
};
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `siteplane/config`: `withSiteplane` points this module at the project's
|
|
3
|
+
* `siteplane.config.json`. Without the wrapper or the file it stays empty, and
|
|
4
|
+
* the website renders its code fallbacks.
|
|
5
|
+
*/
|
|
6
|
+
declare const config: unknown;
|
|
7
|
+
|
|
8
|
+
export { config as default };
|
package/dist/config.js
ADDED
package/dist/index.d.ts
CHANGED
|
@@ -1 +1,38 @@
|
|
|
1
|
-
|
|
1
|
+
/** A link field: `href` and a visible `label`. */
|
|
2
|
+
type SiteplaneLink = {
|
|
3
|
+
href: string;
|
|
4
|
+
label: string;
|
|
5
|
+
};
|
|
6
|
+
/** An image field; `alt` may be empty for decorative images, the size comes from Siteplane when known. */
|
|
7
|
+
type SiteplaneImage = {
|
|
8
|
+
src: string;
|
|
9
|
+
alt: string;
|
|
10
|
+
width?: number | undefined;
|
|
11
|
+
height?: number | undefined;
|
|
12
|
+
};
|
|
13
|
+
type SiteplaneAttrs = {
|
|
14
|
+
"data-siteplane-field": string;
|
|
15
|
+
} | Record<string, never>;
|
|
16
|
+
/** The attribute the bridge finds the field by in the Draft Mode preview. */
|
|
17
|
+
declare function attrs(fieldId: string): SiteplaneAttrs;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The field API for server components (Vertrag 54 §6.2):
|
|
21
|
+
*
|
|
22
|
+
* ```tsx
|
|
23
|
+
* const title = await siteplane.text("home.hero.title", "Fresh rolls since 1920");
|
|
24
|
+
* <h1 {...siteplane.attrs("home.hero.title")}>{title}</h1>
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* All values of the site load once per request (React `cache()`); without
|
|
28
|
+
* Siteplane the code fallbacks render.
|
|
29
|
+
*/
|
|
30
|
+
declare const siteplane: {
|
|
31
|
+
text: (fieldId: string, fallback: string) => Promise<string>;
|
|
32
|
+
longText: (fieldId: string, fallback: string) => Promise<string>;
|
|
33
|
+
link: (fieldId: string, fallback: SiteplaneLink) => Promise<SiteplaneLink>;
|
|
34
|
+
image: (fieldId: string, fallback: SiteplaneImage) => Promise<SiteplaneImage>;
|
|
35
|
+
attrs: typeof attrs;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export { type SiteplaneAttrs, type SiteplaneImage, type SiteplaneLink, siteplane };
|