payload-live-preview 1.0.3 → 1.0.4
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 +146 -88
- package/dist/adapters/astro/RichText.astro +8 -4
- package/dist/adapters/astro/index.d.ts +84 -9
- package/dist/adapters/astro/index.js +1 -363
- package/dist/adapters/astro/index.js.map +1 -1
- package/dist/adapters/astro/middleware-entry.d.ts +1 -1
- package/dist/adapters/astro/middleware-entry.js +1 -296
- package/dist/adapters/astro/middleware-entry.js.map +1 -1
- package/dist/adapters/nextjs/index.d.ts +24 -10
- package/dist/adapters/nextjs/index.js +1 -305
- package/dist/adapters/nextjs/index.js.map +1 -1
- package/dist/adapters/nuxt/index.d.ts +20 -10
- package/dist/adapters/nuxt/index.js +1 -293
- package/dist/adapters/nuxt/index.js.map +1 -1
- package/dist/adapters/sveltekit/index.d.ts +22 -6
- package/dist/adapters/sveltekit/index.js +1 -272
- package/dist/adapters/sveltekit/index.js.map +1 -1
- package/dist/codegen-astro.js +1 -609
- package/dist/codegen-astro.js.map +1 -1
- package/dist/codegen-cli.js +1 -665
- package/dist/codegen-cli.js.map +1 -1
- package/dist/codegen.cjs +1 -550
- package/dist/codegen.cjs.map +1 -1
- package/dist/codegen.js +1 -546
- package/dist/codegen.js.map +1 -1
- package/dist/core.cjs +1 -3723
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +1210 -7
- package/dist/core.d.ts +1210 -7
- package/dist/core.js +1 -3696
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +1 -4122
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1359 -34
- package/dist/index.d.ts +1359 -34
- package/dist/index.js +1 -4079
- package/dist/index.js.map +1 -1
- package/dist/{middleware-BwIlf0jW.d.cts → middleware-CkSeLZ-9.d.ts} +35 -19
- package/dist/payload.cjs +1 -30
- package/dist/payload.cjs.map +1 -1
- package/dist/payload.d.cts +4 -3
- package/dist/payload.d.ts +4 -3
- package/dist/payload.js +1 -28
- package/dist/payload.js.map +1 -1
- package/package.json +55 -16
- package/dist/adapters/astro/index.cjs +0 -370
- package/dist/adapters/astro/index.cjs.map +0 -1
- package/dist/adapters/astro/index.d.cts +0 -105
- package/dist/adapters/astro/middleware-entry.cjs +0 -303
- package/dist/adapters/astro/middleware-entry.cjs.map +0 -1
- package/dist/adapters/astro/middleware-entry.d.cts +0 -15
- package/dist/adapters/nextjs/index.cjs +0 -309
- package/dist/adapters/nextjs/index.cjs.map +0 -1
- package/dist/adapters/nextjs/index.d.cts +0 -75
- package/dist/adapters/nuxt/index.cjs +0 -299
- package/dist/adapters/nuxt/index.cjs.map +0 -1
- package/dist/adapters/nuxt/index.d.cts +0 -126
- package/dist/adapters/sveltekit/index.cjs +0 -275
- package/dist/adapters/sveltekit/index.cjs.map +0 -1
- package/dist/adapters/sveltekit/index.d.cts +0 -80
- package/dist/bind-BSgxoQhN.d.cts +0 -1137
- package/dist/bind-BSgxoQhN.d.ts +0 -1137
- package/dist/codegen-astro.cjs +0 -612
- package/dist/codegen-astro.cjs.map +0 -1
- package/dist/codegen-astro.d.cts +0 -54
- package/dist/codegen-cli.cjs +0 -669
- package/dist/codegen-cli.cjs.map +0 -1
- package/dist/codegen-cli.d.cts +0 -19
- package/dist/middleware-BwIlf0jW.d.ts +0 -168
- package/dist/preview-request-DAvxm18T.d.cts +0 -68
- package/dist/preview-request-DAvxm18T.d.ts +0 -68
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,645 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for the library version.
|
|
3
|
+
*
|
|
4
|
+
* Both the npm package surface (`src/index.ts`, `src/core-entry.ts`)
|
|
5
|
+
* and the inline runtime (`src/core/runtime.ts`, which gets bundled by
|
|
6
|
+
* esbuild and embedded into `runtime.generated.ts`) read the version
|
|
7
|
+
* from here so they never drift.
|
|
8
|
+
*
|
|
9
|
+
* Imports `package.json` directly — tsc, tsup, and esbuild all inline
|
|
10
|
+
* the value at build time when `resolveJsonModule` is enabled.
|
|
11
|
+
*
|
|
12
|
+
* @module @/version
|
|
13
|
+
*/
|
|
14
|
+
declare const VERSION: string;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Payload Live Preview message protocol.
|
|
18
|
+
*
|
|
19
|
+
* Mirrors the messages emitted by Payload CMS through
|
|
20
|
+
* `window.postMessage`, verified against the shipped admin code
|
|
21
|
+
* (`@payloadcms/ui` → `elements/LivePreview/Window`):
|
|
22
|
+
*
|
|
23
|
+
* - Payload **3.x** sends `{ type, collectionSlug, globalSlug, data,
|
|
24
|
+
* locale, externallyUpdatedRelationship }` on every form edit —
|
|
25
|
+
* `data` holds **raw form values** (relationships as bare IDs) and
|
|
26
|
+
* there is **no** `fieldSchemaJSON`.
|
|
27
|
+
* - Payload **2.x** additionally sent `fieldSchemaJSON` with the
|
|
28
|
+
* first update (client-side merge era).
|
|
29
|
+
* - `previewToken` and `protocolVersion` are extensions of THIS
|
|
30
|
+
* library for custom admin senders; stock Payload never sends
|
|
31
|
+
* them and ignores them in our `ready` handshake.
|
|
32
|
+
*
|
|
33
|
+
* @module @/types/payload-protocol
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* A `payload-live-preview` data update.
|
|
37
|
+
*
|
|
38
|
+
* `data` is the serialized document state. `fieldSchemaJSON`, when
|
|
39
|
+
* present (Payload 2.x or custom senders), allows the schema-driven
|
|
40
|
+
* engine to resolve field types without any DOM annotations.
|
|
41
|
+
*/
|
|
42
|
+
interface PayloadLivePreviewMessage {
|
|
43
|
+
readonly type: 'payload-live-preview';
|
|
44
|
+
readonly data?: Record<string, unknown>;
|
|
45
|
+
readonly fieldSchemaJSON?: readonly PayloadFieldSchema[];
|
|
46
|
+
readonly globalSlug?: string;
|
|
47
|
+
readonly collectionSlug?: string;
|
|
48
|
+
readonly locale?: string;
|
|
49
|
+
readonly ready?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Payload 3.x: most recent relationship-document event (create/update
|
|
52
|
+
* in a drawer), or `null`. The official client ignores it too — it is
|
|
53
|
+
* modeled here for completeness and consumer event access.
|
|
54
|
+
*/
|
|
55
|
+
readonly externallyUpdatedRelationship?: PayloadDocumentEventDetail | null;
|
|
56
|
+
/**
|
|
57
|
+
* Optional preview JWT. ⚠️ Library extension — stock Payload never
|
|
58
|
+
* sends one. When `RuntimeOptions.validateToken` is set, the runtime
|
|
59
|
+
* requires every data update to carry a token that passes the
|
|
60
|
+
* validator; messages without a valid token are dropped.
|
|
61
|
+
*/
|
|
62
|
+
readonly previewToken?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Highest protocol version the sender supports. ⚠️ Library extension
|
|
65
|
+
* for custom senders — stock Payload sends no version. When absent,
|
|
66
|
+
* the receiver assumes v1.
|
|
67
|
+
*/
|
|
68
|
+
readonly protocolVersion?: number;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Payload's `DocumentEvent` shape (admin `providers/DocumentEvents`).
|
|
72
|
+
*/
|
|
73
|
+
interface PayloadDocumentEventDetail {
|
|
74
|
+
readonly entitySlug: string;
|
|
75
|
+
readonly operation?: 'create' | 'update';
|
|
76
|
+
readonly id?: string | number;
|
|
77
|
+
readonly updatedAt?: string;
|
|
78
|
+
readonly [extra: string]: unknown;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Parsed, validated message payload exposed to consumers via events.
|
|
82
|
+
*/
|
|
83
|
+
interface PayloadLivePreviewData {
|
|
84
|
+
readonly fields: Record<string, unknown>;
|
|
85
|
+
readonly schema?: readonly PayloadFieldSchema[];
|
|
86
|
+
readonly globalSlug?: string;
|
|
87
|
+
readonly collectionSlug?: string;
|
|
88
|
+
readonly locale?: string;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The subset of Payload's field-schema description we rely on.
|
|
92
|
+
*
|
|
93
|
+
* Payload emits the entire field configuration tree but we only need
|
|
94
|
+
* the discriminator `type` and the nesting via `fields`/`blocks`.
|
|
95
|
+
* Extra properties are preserved opaquely.
|
|
96
|
+
*/
|
|
97
|
+
interface PayloadFieldSchema {
|
|
98
|
+
readonly name: string;
|
|
99
|
+
readonly type: PayloadFieldType;
|
|
100
|
+
readonly label?: string;
|
|
101
|
+
readonly required?: boolean;
|
|
102
|
+
readonly localized?: boolean;
|
|
103
|
+
readonly fields?: readonly PayloadFieldSchema[];
|
|
104
|
+
readonly blocks?: readonly PayloadBlockSchema[];
|
|
105
|
+
readonly relationTo?: string | readonly string[];
|
|
106
|
+
readonly hasMany?: boolean;
|
|
107
|
+
readonly admin?: {
|
|
108
|
+
readonly condition?: PayloadFieldCondition;
|
|
109
|
+
};
|
|
110
|
+
readonly [extra: string]: unknown;
|
|
111
|
+
}
|
|
112
|
+
interface PayloadBlockSchema {
|
|
113
|
+
readonly slug: string;
|
|
114
|
+
readonly fields: readonly PayloadFieldSchema[];
|
|
115
|
+
readonly [extra: string]: unknown;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* The Payload core field types this library recognises (20 as of
|
|
119
|
+
* Payload 3.x). `tabs` is treated as a structural container; `group`
|
|
120
|
+
* flattens nested fields.
|
|
121
|
+
*/
|
|
122
|
+
type PayloadFieldType = 'text' | 'textarea' | 'richText' | 'email' | 'number' | 'checkbox' | 'date' | 'select' | 'radio' | 'array' | 'blocks' | 'group' | 'tabs' | 'row' | 'collapsible' | 'relationship' | 'upload' | 'point' | 'json' | 'code' | 'ui';
|
|
123
|
+
/**
|
|
124
|
+
* Payload's `admin.condition` is a function in source, but it cannot
|
|
125
|
+
* be transferred over postMessage. The schema arrives with this slot
|
|
126
|
+
* either absent or, in future Payload versions, replaced by a
|
|
127
|
+
* serialized expression. We model the slot but do not consume it yet.
|
|
128
|
+
*/
|
|
129
|
+
type PayloadFieldCondition = (data: unknown, siblingData: unknown) => boolean;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Public event payload shapes for the live preview lifecycle.
|
|
133
|
+
*
|
|
134
|
+
* The map keys form the union of valid event names. Adding a new event
|
|
135
|
+
* is a two-line change: add the entry here and dispatch from the
|
|
136
|
+
* relevant module. Consumers get autocompletion and exhaustive checks
|
|
137
|
+
* for free because the `EventEmitter` is generic over this map.
|
|
138
|
+
*
|
|
139
|
+
* @module @events/types
|
|
140
|
+
*/
|
|
141
|
+
|
|
142
|
+
interface LivePreviewEventMap {
|
|
143
|
+
/**
|
|
144
|
+
* Fired once per startup attempt after observers, cache, and message listening
|
|
145
|
+
* are active. A later startup failure may roll that attempt back; retrying then
|
|
146
|
+
* emits a new `init` event.
|
|
147
|
+
*/
|
|
148
|
+
readonly init: {
|
|
149
|
+
readonly timestamp: number;
|
|
150
|
+
};
|
|
151
|
+
/** Fired for the first accepted data-bearing update from a trusted origin. */
|
|
152
|
+
readonly connect: {
|
|
153
|
+
readonly origin: string;
|
|
154
|
+
readonly timestamp: number;
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* Fired when the heartbeat times out or the runtime is destroyed. The
|
|
158
|
+
* `'unload'` reason remains in the public union for 1.x producer compatibility;
|
|
159
|
+
* the built-in runtime does not currently emit it.
|
|
160
|
+
*/
|
|
161
|
+
readonly disconnect: {
|
|
162
|
+
readonly reason: 'timeout' | 'destroy' | 'unload';
|
|
163
|
+
readonly timestamp: number;
|
|
164
|
+
};
|
|
165
|
+
/**
|
|
166
|
+
* Fired before the DOM is mutated for an incoming update.
|
|
167
|
+
*
|
|
168
|
+
* Handlers may call `cancel()` before their synchronous or asynchronous
|
|
169
|
+
* work settles to skip exactly this revision.
|
|
170
|
+
*/
|
|
171
|
+
readonly beforeUpdate: {
|
|
172
|
+
readonly data: PayloadLivePreviewData;
|
|
173
|
+
/** Runtime-populated ordering metadata; optional for 1.x producer compatibility. */
|
|
174
|
+
readonly revision?: number;
|
|
175
|
+
readonly cancel: () => void;
|
|
176
|
+
};
|
|
177
|
+
/**
|
|
178
|
+
* Fired after each actual, current DOM application batch. Visibility replay
|
|
179
|
+
* may produce another event for the same revision; cancelled, obsolete, and
|
|
180
|
+
* deferred-only batches produce none.
|
|
181
|
+
*/
|
|
182
|
+
readonly afterUpdate: {
|
|
183
|
+
readonly data: PayloadLivePreviewData;
|
|
184
|
+
readonly updatedCount: number;
|
|
185
|
+
readonly durationMs: number;
|
|
186
|
+
/** Runtime-populated ordering metadata; optional for 1.x producer compatibility. */
|
|
187
|
+
readonly revision?: number;
|
|
188
|
+
};
|
|
189
|
+
/** Fired for each successful, still-current element write. */
|
|
190
|
+
readonly elementUpdate: {
|
|
191
|
+
readonly element: Element;
|
|
192
|
+
readonly fieldName: string;
|
|
193
|
+
readonly previousValue: unknown;
|
|
194
|
+
readonly nextValue: unknown;
|
|
195
|
+
/** Runtime-populated ordering metadata; optional for 1.x producer compatibility. */
|
|
196
|
+
readonly revision?: number;
|
|
197
|
+
};
|
|
198
|
+
/** Fired whenever the element cache is rebuilt (initial scan or MutationObserver-triggered). */
|
|
199
|
+
readonly cacheRefresh: {
|
|
200
|
+
readonly elementCount: number;
|
|
201
|
+
readonly fieldCount: number;
|
|
202
|
+
readonly durationMs: number;
|
|
203
|
+
};
|
|
204
|
+
/** Fired when a `payload-document-event` message arrives (document save). */
|
|
205
|
+
readonly documentSave: {
|
|
206
|
+
readonly timestamp: number;
|
|
207
|
+
};
|
|
208
|
+
/**
|
|
209
|
+
* Fired on errors that the runtime caught but cannot fully recover from.
|
|
210
|
+
*
|
|
211
|
+
* The `context` string identifies where the error originated.
|
|
212
|
+
*/
|
|
213
|
+
readonly error: {
|
|
214
|
+
readonly error: Error;
|
|
215
|
+
readonly context: string;
|
|
216
|
+
};
|
|
217
|
+
/**
|
|
218
|
+
* Fired during destroy after browser observers and the message listener are
|
|
219
|
+
* removed. Event/plugin listeners remain available for this notification and
|
|
220
|
+
* are released by the owning client afterwards.
|
|
221
|
+
*/
|
|
222
|
+
readonly destroy: {
|
|
223
|
+
readonly timestamp: number;
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Handler signature: receives the payload and may return a Promise.
|
|
228
|
+
*
|
|
229
|
+
* Promise-returning handlers are awaited in registration order — this
|
|
230
|
+
* is important for `beforeUpdate.cancel()` semantics.
|
|
231
|
+
*/
|
|
232
|
+
type EventHandler<TPayload> = (payload: TPayload) => void | Promise<void>;
|
|
233
|
+
/** Subscription removal function returned by `on()`/`once()`. */
|
|
234
|
+
type Unsubscribe = () => void;
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Per-instance, type-safe event emitter.
|
|
238
|
+
*
|
|
239
|
+
* Replaces the legacy module-level singleton. Each `LivePreviewClient`
|
|
240
|
+
* instantiates its own emitter so that calling `destroy()` on one
|
|
241
|
+
* instance cannot clobber listeners attached to another.
|
|
242
|
+
*
|
|
243
|
+
* Design notes:
|
|
244
|
+
*
|
|
245
|
+
* - Generic over an event map type. The default `LivePreviewEventMap`
|
|
246
|
+
* captures every event this library emits, but the implementation
|
|
247
|
+
* is reusable for any map.
|
|
248
|
+
* - Handlers may be `async`. They are awaited sequentially in
|
|
249
|
+
* registration order. This is intentional: `beforeUpdate.cancel()`
|
|
250
|
+
* and similar synchronization rely on stable ordering.
|
|
251
|
+
* - Errors thrown by one handler are logged via `console.error` and
|
|
252
|
+
* do not prevent later handlers from running.
|
|
253
|
+
* - `once()` semantics: registered into a separate map; the entire
|
|
254
|
+
* snapshot for an event is claimed immediately before its dispatch,
|
|
255
|
+
* and remains consumed even if individual handlers throw.
|
|
256
|
+
*
|
|
257
|
+
* @module @events/emitter
|
|
258
|
+
*/
|
|
259
|
+
|
|
260
|
+
declare class EventEmitter<TMap extends object = LivePreviewEventMap> {
|
|
261
|
+
#private;
|
|
262
|
+
/**
|
|
263
|
+
* Subscribe `handler` to `event`. Returns an unsubscribe function;
|
|
264
|
+
* call it to remove the handler. The handler is invoked for every
|
|
265
|
+
* subsequent emit until unsubscribed.
|
|
266
|
+
*/
|
|
267
|
+
on<E extends keyof TMap>(event: E, handler: EventHandler<TMap[E]>): Unsubscribe;
|
|
268
|
+
/**
|
|
269
|
+
* Subscribe `handler` for a single emit. After the first dispatch,
|
|
270
|
+
* the handler is removed automatically.
|
|
271
|
+
*/
|
|
272
|
+
once<E extends keyof TMap>(event: E, handler: EventHandler<TMap[E]>): Unsubscribe;
|
|
273
|
+
/**
|
|
274
|
+
* Remove `handler` from `event`. Works for both `on`- and
|
|
275
|
+
* `once`-registered handlers. No-op when the handler isn't registered.
|
|
276
|
+
*/
|
|
277
|
+
off<E extends keyof TMap>(event: E, handler: EventHandler<TMap[E]>): void;
|
|
278
|
+
/**
|
|
279
|
+
* Dispatch `payload` to every handler registered for `event`.
|
|
280
|
+
*
|
|
281
|
+
* Handlers run sequentially in registration order — regular ones
|
|
282
|
+
* first, then `once` handlers. Async handlers are awaited. Synchronous
|
|
283
|
+
* exceptions are caught, logged, and isolated to the offending handler.
|
|
284
|
+
*/
|
|
285
|
+
emit<E extends keyof TMap>(event: E, payload: TMap[E]): Promise<void>;
|
|
286
|
+
/**
|
|
287
|
+
* Lifecycle-only guarded dispatch. The predicate is checked before and after
|
|
288
|
+
* every handler so an async boundary cannot let an obsolete transaction run
|
|
289
|
+
* later callbacks. `emit()` intentionally keeps its established behaviour.
|
|
290
|
+
*
|
|
291
|
+
* Once-handlers are claimed only when dispatch reaches the once phase. Once
|
|
292
|
+
* claimed, the whole snapshot remains consumed even if the predicate changes
|
|
293
|
+
* while one of its handlers is awaiting, matching normal once semantics.
|
|
294
|
+
*
|
|
295
|
+
* @internal
|
|
296
|
+
* @returns `true` when the complete handler sequence ran while eligible.
|
|
297
|
+
*/
|
|
298
|
+
emitWhile<E extends keyof TMap>(event: E, payload: TMap[E], shouldContinue: () => boolean): Promise<boolean>;
|
|
299
|
+
/**
|
|
300
|
+
* Number of handlers currently registered for `event`.
|
|
301
|
+
* Sum of `on` and `once` registrations.
|
|
302
|
+
*/
|
|
303
|
+
listenerCount(event: keyof TMap): number;
|
|
304
|
+
/**
|
|
305
|
+
* Remove every handler. Pass an event name to limit the removal to
|
|
306
|
+
* one event; omit it to clear every event on this emitter.
|
|
307
|
+
*
|
|
308
|
+
* Crucially this only affects the current instance — there is no
|
|
309
|
+
* shared global state.
|
|
310
|
+
*/
|
|
311
|
+
removeAllListeners(event?: keyof TMap): void;
|
|
312
|
+
/**
|
|
313
|
+
* Snapshot of all event names that currently have at least one
|
|
314
|
+
* registered handler. Useful for debugging.
|
|
315
|
+
*/
|
|
316
|
+
eventNames(): (keyof TMap)[];
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Core type definitions shared across all runtime primitives.
|
|
321
|
+
*
|
|
322
|
+
* @module @core/types
|
|
323
|
+
*/
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Field types recognized by the live preview renderer.
|
|
327
|
+
*
|
|
328
|
+
* Extends Payload's field set with `html` and `url` which are not
|
|
329
|
+
* Payload field types per se but useful DOM-binding categories.
|
|
330
|
+
*/
|
|
331
|
+
type FieldType = PayloadFieldType | 'html' | 'url' | 'image' | 'structural-array';
|
|
332
|
+
/**
|
|
333
|
+
* A DOM element that has been registered as a live preview binding.
|
|
334
|
+
*
|
|
335
|
+
* Stored in the element cache indexed by `fieldName`. Multiple cached
|
|
336
|
+
* entries can share a field name when the same field is rendered in
|
|
337
|
+
* several places (e.g., a title shown in both the header and the
|
|
338
|
+
* page body).
|
|
339
|
+
*/
|
|
340
|
+
interface CachedElement {
|
|
341
|
+
/** The bound DOM element. */
|
|
342
|
+
readonly element: Element;
|
|
343
|
+
/** Payload field path, e.g., `title` or `hero.subtitle`. */
|
|
344
|
+
readonly fieldName: string;
|
|
345
|
+
/**
|
|
346
|
+
* Resolved field type, used to dispatch to the right renderer.
|
|
347
|
+
*
|
|
348
|
+
* The cache resolves this from DOM attributes (`data-payload-type`,
|
|
349
|
+
* tag-based heuristics) at build time. When `explicitFieldType` is
|
|
350
|
+
* `false` the lifecycle is allowed to override this with the type
|
|
351
|
+
* learned from the schema once `fieldSchemaJSON` arrives.
|
|
352
|
+
*/
|
|
353
|
+
readonly fieldType: FieldType;
|
|
354
|
+
/**
|
|
355
|
+
* `true` when the field type was set by an explicit
|
|
356
|
+
* `data-payload-type` attribute (consumer trumps schema), `false`
|
|
357
|
+
* when it was inferred via tag-based heuristics (schema may
|
|
358
|
+
* override later). Optional so synthetic bindings (tests / programmatic
|
|
359
|
+
* `cache.add()` callers) need not supply it; the cache always
|
|
360
|
+
* populates it for DOM-derived bindings.
|
|
361
|
+
*/
|
|
362
|
+
readonly explicitFieldType?: boolean;
|
|
363
|
+
/**
|
|
364
|
+
* Optional target attribute: the value is written to this attribute
|
|
365
|
+
* instead of the element's content (from `data-payload-attribute`,
|
|
366
|
+
* or `bind(field, { attribute })`). Writes are policed — event
|
|
367
|
+
* handlers, `style`, and unsafe URLs are refused.
|
|
368
|
+
*/
|
|
369
|
+
readonly targetAttribute?: string;
|
|
370
|
+
/** Optional sibling-field path bound to the element's `href` attribute. */
|
|
371
|
+
readonly hrefField?: string;
|
|
372
|
+
/** Optional sibling-field path bound to the element's `src` attribute. */
|
|
373
|
+
readonly srcField?: string;
|
|
374
|
+
/** Optional sibling-field path bound to the element's `alt` attribute. */
|
|
375
|
+
readonly altField?: string;
|
|
376
|
+
/** Optional inline template for array/blocks rendering. */
|
|
377
|
+
readonly arrayTemplate?: string;
|
|
378
|
+
/** Optional separator for primitive-array stringification. */
|
|
379
|
+
readonly arraySeparator?: string;
|
|
380
|
+
/** Optional locale code locked onto this element (overrides the global locale). */
|
|
381
|
+
readonly locale?: string;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Context passed to every field renderer. Provides access to the
|
|
385
|
+
* entire field tree so renderers can resolve sibling fields (e.g.,
|
|
386
|
+
* an `<a>` that pulls `href` from a different field).
|
|
387
|
+
*/
|
|
388
|
+
interface RenderContext {
|
|
389
|
+
/** Full field map for the current update — used for sibling lookups. */
|
|
390
|
+
readonly allFields: Record<string, unknown>;
|
|
391
|
+
/** Active locale, or `undefined` when none is established. */
|
|
392
|
+
readonly locale: string | undefined;
|
|
393
|
+
/** Optional schema descriptor for the field, when available. */
|
|
394
|
+
readonly schema: PayloadFieldSchema | undefined;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Contract that every field renderer implements.
|
|
398
|
+
*
|
|
399
|
+
* Renderers are pure DOM-write functions: they receive the cached
|
|
400
|
+
* binding plus the new value and apply it to the element. They must
|
|
401
|
+
* not throw — failures should be silenced and logged so that one bad
|
|
402
|
+
* field cannot stop an entire update. The public renderer result remains
|
|
403
|
+
* `void` for 1.x compatibility. Built-in renderers may use an internal exact
|
|
404
|
+
* `false` sentinel when they deliberately perform no DOM write; callers must
|
|
405
|
+
* not rely on that implementation detail.
|
|
406
|
+
*/
|
|
407
|
+
interface FieldRenderer {
|
|
408
|
+
readonly name: FieldType;
|
|
409
|
+
render(target: CachedElement, value: unknown, context: RenderContext): void;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Plugin system types.
|
|
414
|
+
*
|
|
415
|
+
* Each plugin participates in the live-preview lifecycle through a
|
|
416
|
+
* scoped, per-instance context. Plugins can register custom field
|
|
417
|
+
* renderers, transform incoming values, and subscribe to events —
|
|
418
|
+
* the same primitives the built-in renderers use.
|
|
419
|
+
*
|
|
420
|
+
* The system is intentionally minimal. Anything that can be expressed
|
|
421
|
+
* with an event subscription belongs in `events`, not `plugins`.
|
|
422
|
+
*
|
|
423
|
+
* @module @plugins/types
|
|
424
|
+
*/
|
|
425
|
+
|
|
426
|
+
/** Idempotent handle that releases one plugin-owned resource. */
|
|
427
|
+
type PluginDisposer = () => void;
|
|
428
|
+
/**
|
|
429
|
+
* Event subscriptions scoped to one plugin registration.
|
|
430
|
+
*
|
|
431
|
+
* This remains nominally compatible with the pre-1.0.4 `EventEmitter` context
|
|
432
|
+
* type. Its methods are registration-scoped: even bulk removal affects only
|
|
433
|
+
* this plugin's subscriptions, never a consumer's or another plugin's
|
|
434
|
+
* listeners.
|
|
435
|
+
*/
|
|
436
|
+
interface PluginEvents extends EventEmitter {
|
|
437
|
+
readonly on: <E extends keyof LivePreviewEventMap>(event: E, handler: EventHandler<LivePreviewEventMap[E]>) => Unsubscribe;
|
|
438
|
+
readonly once: <E extends keyof LivePreviewEventMap>(event: E, handler: EventHandler<LivePreviewEventMap[E]>) => Unsubscribe;
|
|
439
|
+
readonly off: <E extends keyof LivePreviewEventMap>(event: E, handler: EventHandler<LivePreviewEventMap[E]>) => void;
|
|
440
|
+
/** Emit through the owning client's event channel. */
|
|
441
|
+
readonly emit: <E extends keyof LivePreviewEventMap>(event: E, payload: LivePreviewEventMap[E]) => Promise<void>;
|
|
442
|
+
/**
|
|
443
|
+
* Guarded emit through the owning client channel. Dispatch stops when the
|
|
444
|
+
* caller predicate becomes false or this plugin registration is removed.
|
|
445
|
+
*/
|
|
446
|
+
readonly emitWhile: <E extends keyof LivePreviewEventMap>(event: E, payload: LivePreviewEventMap[E], shouldContinue: () => boolean) => Promise<boolean>;
|
|
447
|
+
/** Number of listeners owned by this plugin registration. */
|
|
448
|
+
readonly listenerCount: (event: keyof LivePreviewEventMap) => number;
|
|
449
|
+
/** Remove only listeners owned by this plugin registration. */
|
|
450
|
+
readonly removeAllListeners: (event?: keyof LivePreviewEventMap) => void;
|
|
451
|
+
/** Event names for which this plugin registration owns listeners. */
|
|
452
|
+
readonly eventNames: () => (keyof LivePreviewEventMap)[];
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Synchronous function that transforms a merged field value while a revision's
|
|
456
|
+
* per-binding scheduler entry is prepared. Transforms run in registration
|
|
457
|
+
* order and each receives the preceding transform's result. The final value is
|
|
458
|
+
* frozen into that entry before later renderer or attribute dispatch;
|
|
459
|
+
* `allFields` remains the merged, untransformed update snapshot.
|
|
460
|
+
*
|
|
461
|
+
* Returning a different shape than the input is allowed, but returning a
|
|
462
|
+
* Promise or other thenable is a contract error. If a transform throws or
|
|
463
|
+
* returns a thenable, the chain stops, the runtime emits an `error` event, and
|
|
464
|
+
* dispatch falls back to the original merged value. That fallback still passes
|
|
465
|
+
* through the normal renderer and attribute security controls.
|
|
466
|
+
*/
|
|
467
|
+
type FieldTransform = (value: unknown, context: {
|
|
468
|
+
readonly fieldName: string;
|
|
469
|
+
readonly element: Element;
|
|
470
|
+
readonly allFields: Record<string, unknown>;
|
|
471
|
+
}) => unknown;
|
|
472
|
+
/**
|
|
473
|
+
* Per-instance plugin context. Replaces the legacy singleton context.
|
|
474
|
+
*
|
|
475
|
+
* Plugins receive a context that is bound to the parent
|
|
476
|
+
* `LivePreviewClient`. They can:
|
|
477
|
+
*
|
|
478
|
+
* - subscribe via `events.on/once`,
|
|
479
|
+
* - register field renderers,
|
|
480
|
+
* - register value transforms,
|
|
481
|
+
* - read the client's (frozen) configuration,
|
|
482
|
+
* - log through the client's debug channel.
|
|
483
|
+
*/
|
|
484
|
+
interface PluginContext {
|
|
485
|
+
readonly events: PluginEvents;
|
|
486
|
+
/** Register a renderer owned by this plugin registration. */
|
|
487
|
+
readonly registerFieldRenderer: (renderer: FieldRenderer) => void;
|
|
488
|
+
/** Register an ordered transform owned by this plugin registration. */
|
|
489
|
+
readonly registerTransform: (fieldName: string, transform: FieldTransform) => void;
|
|
490
|
+
/**
|
|
491
|
+
* Register any additional synchronous cleanup owned by this registration.
|
|
492
|
+
* The manager invokes it during rollback, `unuse()`, or client destruction.
|
|
493
|
+
* Optional so pre-1.0.4 structural context mocks remain assignable.
|
|
494
|
+
*/
|
|
495
|
+
readonly registerCleanup?: (cleanup: PluginDisposer) => void;
|
|
496
|
+
readonly getConfig: () => Readonly<Record<string, unknown>>;
|
|
497
|
+
readonly log: (...args: unknown[]) => void;
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Plugin definition. `init` runs once per registration; `destroy`
|
|
501
|
+
* runs when the plugin is unregistered or the client is destroyed.
|
|
502
|
+
*
|
|
503
|
+
* Both hooks may return a promise; the manager awaits them.
|
|
504
|
+
*/
|
|
505
|
+
interface LivePreviewPlugin {
|
|
506
|
+
readonly name: string;
|
|
507
|
+
readonly version?: string;
|
|
508
|
+
readonly init: (context: PluginContext) => void | Promise<void>;
|
|
509
|
+
readonly destroy?: () => void | Promise<void>;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* `LivePreviewClient` configuration shape.
|
|
514
|
+
*
|
|
515
|
+
* @module @client/config
|
|
516
|
+
*/
|
|
517
|
+
interface LivePreviewClientConfig {
|
|
518
|
+
/** Explicit additional trusted origins. */
|
|
519
|
+
readonly allowedOrigins?: readonly string[];
|
|
520
|
+
/**
|
|
521
|
+
* Payload server origin, e.g. `https://cms.example.com`. When set,
|
|
522
|
+
* every incoming update is re-fetched through the Payload REST API
|
|
523
|
+
* so relationship/upload fields render populated instead of as bare
|
|
524
|
+
* IDs (same strategy as the official client). Recommended for
|
|
525
|
+
* Payload 3.x.
|
|
526
|
+
*/
|
|
527
|
+
readonly serverURL?: string;
|
|
528
|
+
/** REST API route prefix used with `serverURL`. Defaults to `/api`. */
|
|
529
|
+
readonly apiRoute?: string;
|
|
530
|
+
/** Population depth used with `serverURL`. Defaults to `1`. */
|
|
531
|
+
readonly mergeDepth?: number;
|
|
532
|
+
/**
|
|
533
|
+
* Custom fetch implementation for the `serverURL` merge request —
|
|
534
|
+
* the equivalent of the official client's `requestHandler`. Use it
|
|
535
|
+
* to attach auth headers or route through your own proxy. Only
|
|
536
|
+
* available on the programmatic client (functions cannot be
|
|
537
|
+
* serialised into the inline script).
|
|
538
|
+
*/
|
|
539
|
+
readonly mergeFetch?: typeof fetch;
|
|
540
|
+
/** Enable verbose console debug output. Defaults to dev-mode detection. */
|
|
541
|
+
readonly debug?: boolean;
|
|
542
|
+
/** Debounce window for incoming updates. Defaults to 50 ms. */
|
|
543
|
+
readonly debounceMs?: number;
|
|
544
|
+
/**
|
|
545
|
+
* Heartbeat timeout in ms. Defaults to `0` (disabled) because the
|
|
546
|
+
* Payload admin sends messages only on form edits — there is no
|
|
547
|
+
* protocol keepalive, so an idle timeout would produce false
|
|
548
|
+
* disconnects.
|
|
549
|
+
*/
|
|
550
|
+
readonly heartbeatMs?: number;
|
|
551
|
+
/** IntersectionObserver rootMargin. Defaults to `200px`. */
|
|
552
|
+
readonly intersectionRootMargin?: string;
|
|
553
|
+
/** Bypass the visibility gate (apply every update). Defaults to `false`. */
|
|
554
|
+
readonly disableVisibilityGate?: boolean;
|
|
555
|
+
/** Cache-size threshold above which off-screen updates are queued for replay. Defaults to 50. */
|
|
556
|
+
readonly visibilityGateThreshold?: number;
|
|
557
|
+
/**
|
|
558
|
+
* Mount an `aria-live` region for connections, applied updates, and mounted
|
|
559
|
+
* heartbeat-timeout disconnects. Destroy releases it synchronously. Default `true`.
|
|
560
|
+
*/
|
|
561
|
+
readonly enableA11y?: boolean;
|
|
562
|
+
/** Locale used to pick A11y announcement strings. Defaults to detected locale. */
|
|
563
|
+
readonly a11yLocale?: string;
|
|
564
|
+
/** Document root, defaults to `document`. */
|
|
565
|
+
readonly root?: Document | Element;
|
|
566
|
+
/** Disable `document.referrer` detection. */
|
|
567
|
+
readonly disableReferrerDetection?: boolean;
|
|
568
|
+
/** Disable the localhost dev-mode pattern matcher. */
|
|
569
|
+
readonly disableLocalhostMatching?: boolean;
|
|
570
|
+
/** Auto-start the runtime in the constructor. Defaults to `true`. */
|
|
571
|
+
readonly autoStart?: boolean;
|
|
572
|
+
/**
|
|
573
|
+
* Optional preview-token validator. When set, every data update
|
|
574
|
+
* must carry a `previewToken` field that this function approves.
|
|
575
|
+
*
|
|
576
|
+
* ⚠️ `previewToken` is an extension of THIS library, not part of the
|
|
577
|
+
* stock Payload postMessage protocol — the Payload admin never sends
|
|
578
|
+
* one. Only enable this when a custom admin component adds the token
|
|
579
|
+
* to outgoing messages; against an unmodified Payload admin it would
|
|
580
|
+
* drop every update.
|
|
581
|
+
*/
|
|
582
|
+
readonly validateToken?: (token: string | undefined, origin: string) => boolean | Promise<boolean>;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Public, high-level `LivePreviewClient`.
|
|
587
|
+
*
|
|
588
|
+
* The client wraps a `LivePreviewRuntime` with:
|
|
589
|
+
*
|
|
590
|
+
* - Per-instance event emitter (no singleton).
|
|
591
|
+
* - Per-instance plugin manager.
|
|
592
|
+
* - Custom-renderer registration.
|
|
593
|
+
* - Field-name transforms frozen into revision-bound scheduler entries before
|
|
594
|
+
* their later attribute or renderer dispatch.
|
|
595
|
+
*
|
|
596
|
+
* Consumers who want full programmatic control instantiate
|
|
597
|
+
* `LivePreviewClient` directly. Consumers who just want a working
|
|
598
|
+
* inline preview should use `generateInlineScript()` instead.
|
|
599
|
+
*
|
|
600
|
+
* @module @client
|
|
601
|
+
*/
|
|
602
|
+
|
|
603
|
+
declare class LivePreviewClient {
|
|
604
|
+
#private;
|
|
605
|
+
constructor(config?: LivePreviewClientConfig);
|
|
606
|
+
/**
|
|
607
|
+
* Start the runtime. Returns `true` when this client is eligible and runtime
|
|
608
|
+
* startup is active or scheduled for DOM readiness. Repeated calls on an
|
|
609
|
+
* active client also return `true`; `false` means the client was destroyed or
|
|
610
|
+
* the page is outside a preview context. A deferred startup can still fail,
|
|
611
|
+
* roll back, and be retried by a later call.
|
|
612
|
+
*/
|
|
613
|
+
start(): boolean;
|
|
614
|
+
/**
|
|
615
|
+
* Stop the runtime and tear down every plugin. Idempotent.
|
|
616
|
+
*/
|
|
617
|
+
destroy(): Promise<void>;
|
|
618
|
+
/**
|
|
619
|
+
* Register a plugin.
|
|
620
|
+
*/
|
|
621
|
+
use(plugin: LivePreviewPlugin): Promise<void>;
|
|
622
|
+
/** Unregister a plugin and release all resources owned by that registration. */
|
|
623
|
+
unuse(name: string): Promise<void>;
|
|
624
|
+
/** Rebuild the element cache manually. */
|
|
625
|
+
refreshCache(): void;
|
|
626
|
+
/** Read-only access to the event emitter for `on`/`once`/`off`. */
|
|
627
|
+
get events(): EventEmitter;
|
|
628
|
+
/** Names of currently registered plugins. */
|
|
629
|
+
get plugins(): readonly string[];
|
|
630
|
+
/** Current connection status. */
|
|
631
|
+
get status(): 'disconnected' | 'connecting' | 'connected';
|
|
632
|
+
/** Number of valid updates received so far. */
|
|
633
|
+
get updateCount(): number;
|
|
634
|
+
/** `true` once `destroy()` has been called. */
|
|
635
|
+
get destroyed(): boolean;
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Convenience factory: instantiate the client, returning `null` when
|
|
639
|
+
* the page is not currently a preview context (top-level navigation).
|
|
640
|
+
* Useful for SSR-style integrations that import this from any context.
|
|
641
|
+
*/
|
|
642
|
+
declare function initLivePreview(config?: LivePreviewClientConfig): LivePreviewClient | null;
|
|
4
643
|
|
|
5
644
|
interface RuntimeBuildInfo {
|
|
6
645
|
readonly generatedAt: string;
|
|
@@ -83,9 +722,11 @@ interface InlineScriptConfig {
|
|
|
83
722
|
*/
|
|
84
723
|
readonly disableLocalhostMatching?: boolean;
|
|
85
724
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
725
|
+
* Retained for 1.x source compatibility, but has no effect here:
|
|
726
|
+
* `generateInlineScript()` returns a script body and creates no tag.
|
|
727
|
+
* Pass the nonce separately to `wrapWithScriptTag(body, { nonce })`.
|
|
728
|
+
*
|
|
729
|
+
* @deprecated Use the second argument of `wrapWithScriptTag()`.
|
|
89
730
|
*/
|
|
90
731
|
readonly nonce?: string;
|
|
91
732
|
}
|
|
@@ -110,33 +751,354 @@ declare function wrapWithScriptTag(body: string, options?: {
|
|
|
110
751
|
*/
|
|
111
752
|
declare function runtimeBuildInfo(): RuntimeBuildInfo;
|
|
112
753
|
|
|
754
|
+
/**
|
|
755
|
+
* HTML-escape primitives.
|
|
756
|
+
*
|
|
757
|
+
* Implementations follow the OWASP XSS Prevention Cheat Sheet: each
|
|
758
|
+
* special character is replaced with its decimal or hex entity. The
|
|
759
|
+
* functions are isomorphic — they have no dependency on the DOM and
|
|
760
|
+
* can run in any JavaScript environment (browser, Node, Deno, Bun).
|
|
761
|
+
*
|
|
762
|
+
* @module @security/escape
|
|
763
|
+
*/
|
|
764
|
+
/**
|
|
765
|
+
* Escape every HTML-significant character in `text` so the result is
|
|
766
|
+
* safe to interpolate into any HTML context (element body or attribute).
|
|
767
|
+
*
|
|
768
|
+
* - Coerces non-strings via `String(value)`; pass `null`/`undefined`
|
|
769
|
+
* intentionally — they become `"null"`/`"undefined"`.
|
|
770
|
+
* - Cannot fail; returns `""` for the empty input.
|
|
771
|
+
*/
|
|
772
|
+
declare function escapeHtml(text: string): string;
|
|
773
|
+
/**
|
|
774
|
+
* Escape characters that would break out of a double-quoted HTML
|
|
775
|
+
* attribute value. Stricter than `escapeHtml` for body content but
|
|
776
|
+
* preserves URL characters (`/`, `=`, etc.) that are common in
|
|
777
|
+
* `href` and `src` values.
|
|
778
|
+
*
|
|
779
|
+
* Only call this for values that have already been validated as safe
|
|
780
|
+
* URLs or trusted strings; it does NOT block dangerous protocols.
|
|
781
|
+
*/
|
|
782
|
+
declare function escapeHtmlAttribute(value: string): string;
|
|
783
|
+
|
|
784
|
+
/**
|
|
785
|
+
* URL safety validation.
|
|
786
|
+
*
|
|
787
|
+
* Single source of truth for URL-protocol decisions used by both the
|
|
788
|
+
* inline runtime and the high-level client. The previous version of
|
|
789
|
+
* this library had two implementations that subtly disagreed; this
|
|
790
|
+
* module replaces both.
|
|
791
|
+
*
|
|
792
|
+
* Policy:
|
|
793
|
+
* - Allow only the protocols in `SAFE_PROTOCOLS`.
|
|
794
|
+
* - Reject every other protocol explicitly, including `javascript:`,
|
|
795
|
+
* `data:`, `vbscript:`, `file:`, `blob:`, and any custom scheme.
|
|
796
|
+
* - Empty strings are NOT considered safe (this is a tightening from
|
|
797
|
+
* the legacy behaviour and avoids producing `<a href="">` markup).
|
|
798
|
+
*
|
|
799
|
+
* @module @security/url-validator
|
|
800
|
+
*/
|
|
801
|
+
/**
|
|
802
|
+
* Returns `true` only when `url` is one of the explicitly allowed forms:
|
|
803
|
+
*
|
|
804
|
+
* - Absolute URL with `http:`, `https:`, `mailto:`, or `tel:` protocol
|
|
805
|
+
* - Protocol-relative URL (`//example.com/...`)
|
|
806
|
+
* - Same-origin absolute path (`/foo`)
|
|
807
|
+
* - Hash or query fragment (`#section`, `?q=1`)
|
|
808
|
+
* - Plain relative path (`foo/bar`, `./foo`, `../foo`)
|
|
809
|
+
*/
|
|
810
|
+
declare function isSafeUrl(url: unknown): boolean;
|
|
811
|
+
/**
|
|
812
|
+
* Returns `true` when the URL points to an external HTTP(S) origin.
|
|
813
|
+
* Protocol-relative URLs (`//example.com/...`) count as external —
|
|
814
|
+
* they resolve to another origin and need the same `noopener`
|
|
815
|
+
* hardening as absolute ones.
|
|
816
|
+
*
|
|
817
|
+
* Used by renderers to decide whether to add `target="_blank"` and the
|
|
818
|
+
* `noopener noreferrer` rel attributes to anchors. Inputs that fail
|
|
819
|
+
* `isSafeUrl()` always return `false`.
|
|
820
|
+
*/
|
|
821
|
+
declare function isExternalHttpUrl(url: string): boolean;
|
|
822
|
+
|
|
823
|
+
interface SanitizeOptions {
|
|
824
|
+
/** Extra tags to allow beyond the built-in list. Lower-case, untrimmed. */
|
|
825
|
+
readonly additionalAllowedTags?: readonly string[];
|
|
826
|
+
/** Extra per-tag attributes to allow. Tag and attribute names must be lower-case. */
|
|
827
|
+
readonly additionalAllowedAttributes?: Readonly<Record<string, readonly string[]>>;
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Minimal `Document`-shaped surface the sanitizer needs. Any DOM
|
|
831
|
+
* implementation (browser, jsdom, linkedom, happy-dom, parse5 + a
|
|
832
|
+
* thin adapter) that produces `<template>`-style elements works.
|
|
833
|
+
*/
|
|
834
|
+
interface SanitizerDocument {
|
|
835
|
+
createElement: (tagName: string) => {
|
|
836
|
+
innerHTML: string;
|
|
837
|
+
readonly content: ParentNode;
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* Inject a `Document` implementation for the sanitizer to use when
|
|
842
|
+
* `globalThis.document` is unavailable.
|
|
843
|
+
*
|
|
844
|
+
* Server-side renderers (Node, Bun, Deno without DOM globals) can wire
|
|
845
|
+
* this up once at startup with any of the popular pure-JS DOM
|
|
846
|
+
* libraries:
|
|
847
|
+
*
|
|
848
|
+
* ```ts
|
|
849
|
+
* // linkedom — recommended (smallest, fastest)
|
|
850
|
+
* import { parseHTML } from 'linkedom';
|
|
851
|
+
* import { setSanitizerDocument } from 'payload-live-preview';
|
|
852
|
+
* const { document } = parseHTML('<!doctype html><html><body></body></html>');
|
|
853
|
+
* setSanitizerDocument(document);
|
|
854
|
+
* ```
|
|
855
|
+
*
|
|
856
|
+
* ```ts
|
|
857
|
+
* // jsdom — heavier, but already in many SSR pipelines
|
|
858
|
+
* import { JSDOM } from 'jsdom';
|
|
859
|
+
* setSanitizerDocument(new JSDOM().window.document);
|
|
860
|
+
* ```
|
|
861
|
+
*
|
|
862
|
+
* Pass `null` to clear a previous override (mostly useful for tests).
|
|
863
|
+
*/
|
|
864
|
+
declare function setSanitizerDocument(doc: SanitizerDocument | null): void;
|
|
865
|
+
/**
|
|
866
|
+
* Sanitize `html` and return safe HTML.
|
|
867
|
+
*
|
|
868
|
+
* The function never returns `undefined`/`null`. On malformed input the
|
|
869
|
+
* browser parser silently recovers and the sanitizer continues from
|
|
870
|
+
* the recovered tree.
|
|
871
|
+
*
|
|
872
|
+
* @throws {SanitizerEnvironmentError} when no DOM is available.
|
|
873
|
+
*/
|
|
874
|
+
declare function sanitizeHtml(html: string, options?: SanitizeOptions): string;
|
|
875
|
+
|
|
876
|
+
/**
|
|
877
|
+
* Content-Security-Policy helpers.
|
|
878
|
+
*
|
|
879
|
+
* Two responsibilities:
|
|
880
|
+
*
|
|
881
|
+
* 1. Generate cryptographic nonces so consumers can opt into a strict
|
|
882
|
+
* `script-src 'nonce-...'` policy and avoid `'unsafe-inline'`.
|
|
883
|
+
* 2. Build the `frame-ancestors` directive value, restricting which
|
|
884
|
+
* origins may embed the preview page in an iframe.
|
|
885
|
+
*
|
|
886
|
+
* Both functions are isomorphic — they work in browsers, Node, and
|
|
887
|
+
* edge runtimes that expose Web Crypto via `globalThis.crypto`.
|
|
888
|
+
*
|
|
889
|
+
* @module @security/csp
|
|
890
|
+
*/
|
|
891
|
+
/**
|
|
892
|
+
* Minimal Web-Crypto surface area we actually call. The full `Crypto`
|
|
893
|
+
* type drags in browser-only declarations that don't typecheck cleanly
|
|
894
|
+
* against Node's `webcrypto` shape — this narrower interface accepts
|
|
895
|
+
* both without an unsafe cast.
|
|
896
|
+
*/
|
|
897
|
+
interface WebCryptoLike {
|
|
898
|
+
getRandomValues: <T extends ArrayBufferView | null>(array: T) => T;
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Inject a Web Crypto implementation to use when `globalThis.crypto`
|
|
902
|
+
* is unavailable.
|
|
903
|
+
*
|
|
904
|
+
* Node.js exposed `globalThis.crypto` globally starting with v19. On
|
|
905
|
+
* Node 18 (still LTS at time of writing) consumers can flip the
|
|
906
|
+
* `--experimental-global-webcrypto` flag *or* call this function once
|
|
907
|
+
* at startup:
|
|
908
|
+
*
|
|
909
|
+
* ```ts
|
|
910
|
+
* import { webcrypto } from 'node:crypto';
|
|
911
|
+
* import { setCspCrypto } from 'payload-live-preview';
|
|
912
|
+
* setCspCrypto(webcrypto as { getRandomValues: typeof webcrypto.getRandomValues });
|
|
913
|
+
* ```
|
|
914
|
+
*
|
|
915
|
+
* Browser bundles never need to call this — `globalThis.crypto` is
|
|
916
|
+
* always present there, and the override only matters when the global
|
|
917
|
+
* is missing.
|
|
918
|
+
*
|
|
919
|
+
* Pass `null` to clear a previous override (mostly useful for tests).
|
|
920
|
+
*/
|
|
921
|
+
declare function setCspCrypto(crypto: WebCryptoLike | null): void;
|
|
922
|
+
/**
|
|
923
|
+
* Generate a base64url-encoded cryptographic nonce.
|
|
924
|
+
*
|
|
925
|
+
* Uses `crypto.getRandomValues` everywhere — falls back to throwing
|
|
926
|
+
* if no Web Crypto implementation is available, because a predictable
|
|
927
|
+
* nonce would silently defeat the entire CSP.
|
|
928
|
+
*
|
|
929
|
+
* @param bytes Number of random bytes (defaults to 16 → 128-bit entropy).
|
|
930
|
+
*/
|
|
931
|
+
declare function generateCspNonce(bytes?: number): string;
|
|
932
|
+
interface FrameAncestorsOptions {
|
|
933
|
+
readonly self?: boolean;
|
|
934
|
+
readonly origins?: readonly string[];
|
|
935
|
+
readonly allowNone?: boolean;
|
|
936
|
+
}
|
|
937
|
+
/**
|
|
938
|
+
* Build the value for `frame-ancestors` in a `Content-Security-Policy`
|
|
939
|
+
* header.
|
|
940
|
+
*
|
|
941
|
+
* Returns `'none'` if `allowNone: true` and no other sources are
|
|
942
|
+
* provided. Otherwise concatenates `'self'` (if requested) with the
|
|
943
|
+
* given origins, deduplicating and trimming entries.
|
|
944
|
+
*
|
|
945
|
+
* @example
|
|
946
|
+
* `frame-ancestors ${buildFrameAncestors({ self: true, origins: [adminUrl] })}`
|
|
947
|
+
*/
|
|
948
|
+
declare function buildFrameAncestors(options?: FrameAncestorsOptions): string;
|
|
949
|
+
/**
|
|
950
|
+
* Build a `script-src` value that permits a single nonce plus optional
|
|
951
|
+
* extra sources.
|
|
952
|
+
*
|
|
953
|
+
* `'strict-dynamic'` is **opt-in**: under CSP 3 it makes browsers
|
|
954
|
+
* ignore `'self'` and every host source in the directive, so any
|
|
955
|
+
* parser-inserted script without a nonce (framework hydration scripts,
|
|
956
|
+
* consumer `<script>` tags) would be blocked. Only enable it when every
|
|
957
|
+
* script on the page carries the nonce.
|
|
958
|
+
*/
|
|
959
|
+
declare function buildScriptSrcWithNonce(nonce: string, options?: {
|
|
960
|
+
readonly self?: boolean;
|
|
961
|
+
readonly extra?: readonly string[];
|
|
962
|
+
readonly strictDynamic?: boolean;
|
|
963
|
+
}): string;
|
|
964
|
+
/**
|
|
965
|
+
* How a directive is merged into an existing CSP header value.
|
|
966
|
+
*
|
|
967
|
+
* - `union` (default) — the new sources are added to the existing
|
|
968
|
+
* directive's sources (deduplicated). An existing `'none'` is
|
|
969
|
+
* dropped when real sources are added, since `'none'` may not be
|
|
970
|
+
* combined with other sources.
|
|
971
|
+
* - `replace` — the new value overwrites the existing directive.
|
|
972
|
+
*/
|
|
973
|
+
interface CspDirectiveMerge {
|
|
974
|
+
readonly value: string;
|
|
975
|
+
readonly mode?: 'union' | 'replace';
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* Merge directives into an existing `Content-Security-Policy` header
|
|
979
|
+
* value without clobbering what a consumer (or another middleware)
|
|
980
|
+
* already configured. Directives not mentioned in `additions` pass
|
|
981
|
+
* through untouched; new directives are appended. Existing policy parsing
|
|
982
|
+
* follows CSP3's ASCII-whitespace and first-duplicate-wins rules.
|
|
983
|
+
*
|
|
984
|
+
* This is the single CSP-merge implementation shared by every adapter.
|
|
985
|
+
*/
|
|
986
|
+
declare function mergeCspHeader(existing: string, additions: Readonly<Record<string, string | CspDirectiveMerge>>): string;
|
|
987
|
+
|
|
988
|
+
/**
|
|
989
|
+
* Server-side preview-intent detection.
|
|
990
|
+
*
|
|
991
|
+
* The live-preview runtime only ever activates inside the Payload
|
|
992
|
+
* admin's iframe, but the *server* still has to decide whether a
|
|
993
|
+
* request expresses preview intent before doing optional preview
|
|
994
|
+
* work. Adapters use this predicate as a delivery optimisation, and
|
|
995
|
+
* consumers with hand-rolled middleware can import it directly.
|
|
996
|
+
*
|
|
997
|
+
* IMPORTANT: every signal inspected here is controlled or triggerable
|
|
998
|
+
* by the client. A positive result is never proof of identity,
|
|
999
|
+
* authentication, or permission to read drafts. Before forwarding
|
|
1000
|
+
* credentials, fetching draft data, bypassing caches, changing CSP,
|
|
1001
|
+
* or injecting the runtime, verify the request with an application-
|
|
1002
|
+
* owned server session or a short-lived signed authorization. Use the
|
|
1003
|
+
* same verified decision for all of those effects.
|
|
1004
|
+
*
|
|
1005
|
+
* Preview intent is present when any of these hold:
|
|
1006
|
+
*
|
|
1007
|
+
* 1. A preview query parameter is present (`?preview=true`,
|
|
1008
|
+
* `?draft=true`, `?livePreview=true` — configurable).
|
|
1009
|
+
* 2. `Sec-Fetch-Dest: iframe` — the document is being loaded as an
|
|
1010
|
+
* iframe, which is exactly how the Payload admin embeds it.
|
|
1011
|
+
* (Sent by all evergreen browsers; absence never *excludes* a
|
|
1012
|
+
* request, presence includes it.)
|
|
1013
|
+
* 3. The `Referer` points at one of the given admin origins.
|
|
1014
|
+
*
|
|
1015
|
+
* @module @adapters/shared/preview-request
|
|
1016
|
+
*/
|
|
1017
|
+
/** The individual signals the predicate can consider. */
|
|
1018
|
+
type PreviewSignal = 'query' | 'fetch-dest' | 'referer';
|
|
1019
|
+
interface PreviewRequestOptions {
|
|
1020
|
+
/**
|
|
1021
|
+
* Which signals count as preview intent. Default: all three.
|
|
1022
|
+
* Restricting this to `['query']` reduces accidental activation, but
|
|
1023
|
+
* an explicit `?preview=true` is still client-controlled and must not
|
|
1024
|
+
* authorize response changes or privileged data access by itself.
|
|
1025
|
+
*/
|
|
1026
|
+
readonly signals?: readonly PreviewSignal[];
|
|
1027
|
+
/**
|
|
1028
|
+
* Query parameters (with values `true` or `1`) that signal preview
|
|
1029
|
+
* intent. Default: `['preview', 'draft', 'livePreview']`.
|
|
1030
|
+
*/
|
|
1031
|
+
readonly queryParams?: readonly string[];
|
|
1032
|
+
/**
|
|
1033
|
+
* Treat `Sec-Fetch-Dest: iframe` as a preview-intent signal. Default `true`.
|
|
1034
|
+
* Legacy alias for excluding `'fetch-dest'` from `signals`.
|
|
1035
|
+
*/
|
|
1036
|
+
readonly checkFetchDest?: boolean;
|
|
1037
|
+
/**
|
|
1038
|
+
* Admin origins whose `Referer` signals preview intent, e.g.
|
|
1039
|
+
* `['https://cms.example.com']`. Referer matching is not HTTP-request
|
|
1040
|
+
* authentication.
|
|
1041
|
+
*/
|
|
1042
|
+
readonly adminOrigins?: readonly string[];
|
|
1043
|
+
}
|
|
1044
|
+
/**
|
|
1045
|
+
* The minimal request surface the predicate needs. The standard
|
|
1046
|
+
* `Request` satisfies it structurally, and server frameworks without
|
|
1047
|
+
* fetch-style requests (e.g. Nitro/H3) can supply a tiny shim.
|
|
1048
|
+
*/
|
|
1049
|
+
interface PreviewRequestLike {
|
|
1050
|
+
readonly url: string;
|
|
1051
|
+
readonly headers: {
|
|
1052
|
+
get(name: string): string | null;
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Detect whether `request` carries live-preview intent. See the module
|
|
1057
|
+
* docblock for the exact signals and the required authorization
|
|
1058
|
+
* boundary. This compatibility name does not imply authentication.
|
|
1059
|
+
*/
|
|
1060
|
+
declare function isPreviewRequest(request: PreviewRequestLike, options?: PreviewRequestOptions): boolean;
|
|
1061
|
+
|
|
113
1062
|
/**
|
|
114
1063
|
* Draft-aware document fetching for the initial preview render.
|
|
115
1064
|
*
|
|
116
1065
|
* The live-preview runtime patches the DOM *after* the page loads —
|
|
117
1066
|
* the initial server render is the consumer's job. With Payload
|
|
118
|
-
* drafts enabled,
|
|
119
|
-
* first load, otherwise editors see stale published
|
|
120
|
-
* their first keystroke.
|
|
1067
|
+
* drafts enabled, an **authorized** preview should render the draft
|
|
1068
|
+
* version on first load, otherwise editors see stale published
|
|
1069
|
+
* content until their first keystroke.
|
|
121
1070
|
*
|
|
122
|
-
* These helpers wrap the
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
1071
|
+
* These helpers wrap the corresponding REST queries with `draft` and
|
|
1072
|
+
* `depth` options and an explicit nudge to keep `depth` in sync with
|
|
1073
|
+
* the runtime's `mergeDepth` — a mismatch makes populated
|
|
1074
|
+
* relationships degrade to IDs after the first edit.
|
|
1075
|
+
*
|
|
1076
|
+
* These helpers do not authenticate or authorize requests.
|
|
1077
|
+
* `isPreviewRequest()` detects client-controlled preview intent only.
|
|
1078
|
+
* Verify an application-owned server session or short-lived signed
|
|
1079
|
+
* authorization first, and use that one decision to gate `draft`, any
|
|
1080
|
+
* privileged headers, cache bypass, CSP changes, and runtime injection.
|
|
1081
|
+
* Never attach a long-lived service/API key merely because
|
|
1082
|
+
* `isPreviewRequest()` returned `true`.
|
|
127
1083
|
*
|
|
128
1084
|
* Isomorphic and dependency-free: works in Astro frontmatter,
|
|
129
1085
|
* SvelteKit `load`, Next.js server components, or any Node/edge
|
|
130
1086
|
* runtime with `fetch`.
|
|
131
1087
|
*
|
|
132
1088
|
* ```ts
|
|
133
|
-
* // Astro frontmatter
|
|
1089
|
+
* // Astro frontmatter. `verifyAppPreviewSession` is application-owned,
|
|
1090
|
+
* // server-only code; it validates the request and returns only the
|
|
1091
|
+
* // minimal Payload session headers needed for this request.
|
|
1092
|
+
* const hasPreviewIntent = isPreviewRequest(Astro.request);
|
|
1093
|
+
* const authorization = hasPreviewIntent
|
|
1094
|
+
* ? await verifyAppPreviewSession(Astro.request)
|
|
1095
|
+
* : null;
|
|
134
1096
|
* const page = await fetchPreviewDocument<Page>({
|
|
135
1097
|
* serverURL: import.meta.env.PAYLOAD_URL,
|
|
136
1098
|
* collection: 'pages',
|
|
137
1099
|
* where: { slug: { equals: Astro.params.slug } },
|
|
138
|
-
* draft:
|
|
139
|
-
*
|
|
1100
|
+
* draft: authorization !== null,
|
|
1101
|
+
* ...(authorization === null ? {} : { headers: authorization.payloadHeaders }),
|
|
140
1102
|
* });
|
|
141
1103
|
* ```
|
|
142
1104
|
*
|
|
@@ -155,16 +1117,18 @@ interface PreviewFetchBaseOptions {
|
|
|
155
1117
|
*/
|
|
156
1118
|
readonly depth?: number;
|
|
157
1119
|
/**
|
|
158
|
-
* Fetch the draft version. Defaults to `true`
|
|
159
|
-
*
|
|
160
|
-
*
|
|
1120
|
+
* Fetch the draft version. Defaults to `true` for 1.x compatibility.
|
|
1121
|
+
* Set this from a verified authorization decision, not directly
|
|
1122
|
+
* from `isPreviewRequest()`, whose result only expresses intent.
|
|
161
1123
|
*/
|
|
162
1124
|
readonly draft?: boolean;
|
|
163
1125
|
/** Locale to fetch. */
|
|
164
1126
|
readonly locale?: string;
|
|
165
1127
|
/**
|
|
166
|
-
* Extra request headers
|
|
167
|
-
*
|
|
1128
|
+
* Extra request headers. Supply only credentials derived from the
|
|
1129
|
+
* current request's verified server-side session (or another
|
|
1130
|
+
* short-lived, scoped authorization). Do not attach a long-lived
|
|
1131
|
+
* service/API key based on a preview-intent signal.
|
|
168
1132
|
*/
|
|
169
1133
|
readonly headers?: Readonly<Record<string, string>>;
|
|
170
1134
|
/** Injectable fetch implementation (tests, custom agents). */
|
|
@@ -188,13 +1152,15 @@ interface FetchPreviewGlobalOptions extends PreviewFetchBaseOptions {
|
|
|
188
1152
|
readonly global: string;
|
|
189
1153
|
}
|
|
190
1154
|
/**
|
|
191
|
-
* Fetch a single collection document (draft-first by default
|
|
1155
|
+
* Fetch a single collection document (draft-first by default for 1.x
|
|
1156
|
+
* compatibility; callers must authorize draft access).
|
|
192
1157
|
* Returns `null` when nothing matches or the request fails —
|
|
193
1158
|
* loaders should fall back to their regular data path or a 404.
|
|
194
1159
|
*/
|
|
195
1160
|
declare function fetchPreviewDocument<T = Record<string, unknown>>(options: FetchPreviewDocumentOptions): Promise<T | null>;
|
|
196
1161
|
/**
|
|
197
|
-
* Fetch a global (draft-first by default
|
|
1162
|
+
* Fetch a global (draft-first by default for 1.x compatibility;
|
|
1163
|
+
* callers must authorize draft access). Returns `null` on failure.
|
|
198
1164
|
*/
|
|
199
1165
|
declare function fetchPreviewGlobal<T = Record<string, unknown>>(options: FetchPreviewGlobalOptions): Promise<T | null>;
|
|
200
1166
|
|
|
@@ -360,6 +1326,15 @@ declare function createAnalyticsPlugin(): LivePreviewPlugin & {
|
|
|
360
1326
|
*/
|
|
361
1327
|
|
|
362
1328
|
type DocumentSaveStrategy = 'silent' | 'reload' | 'revalidate' | 'fetch';
|
|
1329
|
+
/**
|
|
1330
|
+
* A fetch strategy handler. The optional parameter preserves calls through
|
|
1331
|
+
* pre-1.0.4 option objects (`options.handler?.()`), while method bivariance
|
|
1332
|
+
* also accepts explicitly signal-aware handlers. The built-in plugin always
|
|
1333
|
+
* supplies a live `AbortSignal`.
|
|
1334
|
+
*/
|
|
1335
|
+
type DocumentSaveHandler = {
|
|
1336
|
+
bivarianceHack(signal?: AbortSignal): void | Promise<void>;
|
|
1337
|
+
}['bivarianceHack'];
|
|
363
1338
|
interface DocumentSavePluginOptions {
|
|
364
1339
|
/** Strategy to invoke on each `documentSave` event. Default `'silent'`. */
|
|
365
1340
|
readonly strategy?: DocumentSaveStrategy;
|
|
@@ -375,9 +1350,11 @@ interface DocumentSavePluginOptions {
|
|
|
375
1350
|
readonly revalidateHeaders?: Readonly<Record<string, string>>;
|
|
376
1351
|
/**
|
|
377
1352
|
* For `'fetch'`: async handler called on each event. The plugin
|
|
378
|
-
* awaits it; rejections are logged but don't crash the runtime.
|
|
1353
|
+
* awaits it; rejections are logged but don't crash the runtime. The signal
|
|
1354
|
+
* aborts when the plugin is removed. Existing zero-argument handlers remain
|
|
1355
|
+
* compatible because they may ignore the argument.
|
|
379
1356
|
*/
|
|
380
|
-
readonly handler?:
|
|
1357
|
+
readonly handler?: DocumentSaveHandler;
|
|
381
1358
|
/**
|
|
382
1359
|
* Optional fallback strategy when `'revalidate'` POST fails (network
|
|
383
1360
|
* error or non-2xx). Set to `'reload'` to hard-refresh the page when
|
|
@@ -390,6 +1367,192 @@ interface DocumentSavePluginOptions {
|
|
|
390
1367
|
*/
|
|
391
1368
|
declare function documentSavePlugin(options?: DocumentSavePluginOptions): LivePreviewPlugin;
|
|
392
1369
|
|
|
1370
|
+
/**
|
|
1371
|
+
* Environment introspection helpers.
|
|
1372
|
+
*
|
|
1373
|
+
* The detection layer needs to know whether the runtime is currently
|
|
1374
|
+
* running inside an iframe, in a popup, in development mode, and
|
|
1375
|
+
* whether environment variables are accessible. None of these checks
|
|
1376
|
+
* is dramatic on its own, but having a single, tested module avoids
|
|
1377
|
+
* the duplicated, subtly-different `typeof process` snippets that
|
|
1378
|
+
* accumulated in the legacy implementation.
|
|
1379
|
+
*
|
|
1380
|
+
* @module @detection/environment
|
|
1381
|
+
*/
|
|
1382
|
+
/**
|
|
1383
|
+
* Returns `true` when the current window is inside an iframe.
|
|
1384
|
+
*
|
|
1385
|
+
* Uses a try/catch around `window.self !== window.top` because the
|
|
1386
|
+
* comparison throws in cross-origin iframes — and that very throw is
|
|
1387
|
+
* itself a definitive "yes, I am in an iframe" signal.
|
|
1388
|
+
*/
|
|
1389
|
+
declare function isInIframe(): boolean;
|
|
1390
|
+
/**
|
|
1391
|
+
* Returns `true` when the current window was opened via `window.open()`
|
|
1392
|
+
* and the opener reference is still accessible.
|
|
1393
|
+
*/
|
|
1394
|
+
declare function isInPopup(): boolean;
|
|
1395
|
+
/**
|
|
1396
|
+
* Returns `true` when running in a preview-capable context: iframe or
|
|
1397
|
+
* popup. Anything else (top-level navigation) should skip live preview
|
|
1398
|
+
* initialisation entirely.
|
|
1399
|
+
*/
|
|
1400
|
+
declare function isInPreviewContext(): boolean;
|
|
1401
|
+
/**
|
|
1402
|
+
* Returns `true` when the runtime appears to be in development mode.
|
|
1403
|
+
*
|
|
1404
|
+
* Detection order:
|
|
1405
|
+
* 1. `process.env.NODE_ENV !== 'production'`
|
|
1406
|
+
* 2. `import.meta.env.DEV === true` (Vite / Astro / SvelteKit)
|
|
1407
|
+
* 3. `window.location.hostname` is `localhost`/`127.0.0.1`
|
|
1408
|
+
*
|
|
1409
|
+
* Returns `false` when none of those are available.
|
|
1410
|
+
*/
|
|
1411
|
+
declare function isDevMode(): boolean;
|
|
1412
|
+
|
|
1413
|
+
/**
|
|
1414
|
+
* Origin detection and matching.
|
|
1415
|
+
*
|
|
1416
|
+
* Builds the set of trusted Payload admin origins and exposes a fast
|
|
1417
|
+
* matcher used by the message bus. Three sources, in precedence order:
|
|
1418
|
+
*
|
|
1419
|
+
* 1. Explicit origins passed via configuration. Always trusted.
|
|
1420
|
+
* 2. `document.referrer` — **only when no explicit origins are
|
|
1421
|
+
* configured**. The browser writes the embedder's origin here
|
|
1422
|
+
* when the iframe loads; using it as a fallback gives a
|
|
1423
|
+
* zero-config path, but it must never widen an explicit
|
|
1424
|
+
* allow-list (the referrer is whoever framed the page, which in
|
|
1425
|
+
* an attack is the attacker).
|
|
1426
|
+
* 3. Localhost pattern (any port on `localhost` / `127.0.0.1`),
|
|
1427
|
+
* enabled when development mode is detected. Replaces the
|
|
1428
|
+
* legacy hand-rolled port list.
|
|
1429
|
+
*
|
|
1430
|
+
* Once the parent's origin is confirmed by an accepted data-bearing
|
|
1431
|
+
* update, callers can call `lockOrigin()` to drop every other candidate,
|
|
1432
|
+
* making subsequent matches exact. A data-less `ready` handshake does
|
|
1433
|
+
* not lock the detector.
|
|
1434
|
+
*
|
|
1435
|
+
* @module @detection/origin
|
|
1436
|
+
*/
|
|
1437
|
+
interface OriginDetectorOptions {
|
|
1438
|
+
/** Explicit additional origins to trust. Always allowed. */
|
|
1439
|
+
readonly additionalOrigins?: readonly string[];
|
|
1440
|
+
/** Set to `false` to opt out of `document.referrer` detection. */
|
|
1441
|
+
readonly enableReferrerDetection?: boolean;
|
|
1442
|
+
/** Set to `false` to opt out of localhost-pattern matching. */
|
|
1443
|
+
readonly enableLocalhostMatching?: boolean;
|
|
1444
|
+
/**
|
|
1445
|
+
* Override the dev-mode detection. When `undefined`, dev mode is
|
|
1446
|
+
* inferred from the runtime (see `isDevMode`).
|
|
1447
|
+
*/
|
|
1448
|
+
readonly forceDevMode?: boolean;
|
|
1449
|
+
/** Override `document.referrer` for tests. */
|
|
1450
|
+
readonly referrer?: string;
|
|
1451
|
+
}
|
|
1452
|
+
/**
|
|
1453
|
+
* Holds the resolved origin policy and exposes a matcher.
|
|
1454
|
+
*
|
|
1455
|
+
* The detector is stateful: it starts with the union of all candidate
|
|
1456
|
+
* sources, then narrows down to a single, verified origin once the
|
|
1457
|
+
* lifecycle calls `lockOrigin()` for its first accepted data-bearing
|
|
1458
|
+
* update.
|
|
1459
|
+
*/
|
|
1460
|
+
declare class OriginDetector {
|
|
1461
|
+
#private;
|
|
1462
|
+
constructor(options?: OriginDetectorOptions);
|
|
1463
|
+
/**
|
|
1464
|
+
* Returns `true` when `origin` is trusted under the current policy.
|
|
1465
|
+
*
|
|
1466
|
+
* If an origin has been locked, only that exact value matches.
|
|
1467
|
+
* Otherwise explicit origins and (in dev mode) localhost-pattern
|
|
1468
|
+
* matches are accepted. The referrer-derived origin is a **fallback,
|
|
1469
|
+
* not a union member**: it only counts when NO explicit origins are
|
|
1470
|
+
* configured. `document.referrer` always names whoever actually
|
|
1471
|
+
* framed the page — if it were unioned with the explicit list, any
|
|
1472
|
+
* site embedding the page in an iframe would become a trusted
|
|
1473
|
+
* postMessage sender despite the consumer having pinned the admin
|
|
1474
|
+
* origin explicitly.
|
|
1475
|
+
*/
|
|
1476
|
+
matches(origin: string): boolean;
|
|
1477
|
+
/**
|
|
1478
|
+
* Narrow the trusted set to a single origin after an accepted
|
|
1479
|
+
* data-bearing update. Subsequent calls to `matches()` only allow
|
|
1480
|
+
* `origin`.
|
|
1481
|
+
*
|
|
1482
|
+
* Returns `true` if locking succeeded; `false` if the candidate is
|
|
1483
|
+
* not currently considered trusted (which would defeat the purpose
|
|
1484
|
+
* of locking).
|
|
1485
|
+
*/
|
|
1486
|
+
lockOrigin(origin: string): boolean;
|
|
1487
|
+
/**
|
|
1488
|
+
* Release the origin lock so any allow-listed origin is trusted
|
|
1489
|
+
* again until the next accepted data-bearing update. The lifecycle
|
|
1490
|
+
* calls this on heartbeat timeout — after the trusted parent disappears,
|
|
1491
|
+
* a different (still-allowed) origin should be free to reconnect.
|
|
1492
|
+
*
|
|
1493
|
+
* Returns the previously-locked origin, or `undefined` when nothing
|
|
1494
|
+
* was locked.
|
|
1495
|
+
*/
|
|
1496
|
+
unlockOrigin(): string | undefined;
|
|
1497
|
+
/** The currently locked origin, or `undefined`. */
|
|
1498
|
+
get lockedOrigin(): string | undefined;
|
|
1499
|
+
/**
|
|
1500
|
+
* Snapshot of every origin currently allowed by the policy. Used by
|
|
1501
|
+
* the message bus to broadcast `ready` messages.
|
|
1502
|
+
*
|
|
1503
|
+
* Localhost pattern matches are expanded into the common dev ports
|
|
1504
|
+
* so the handshake can find a Payload running on its usual port.
|
|
1505
|
+
*/
|
|
1506
|
+
enumerate(): string[];
|
|
1507
|
+
/**
|
|
1508
|
+
* Reports whether `document.referrer` contributed at least one
|
|
1509
|
+
* origin. Useful for surfacing a console warning when running in
|
|
1510
|
+
* production without an explicit origin and the referrer is absent
|
|
1511
|
+
* (because of a `Referrer-Policy: no-referrer` header).
|
|
1512
|
+
*/
|
|
1513
|
+
get referrerWasAvailable(): boolean;
|
|
1514
|
+
/**
|
|
1515
|
+
* Indicates whether the detector is operating in a "production-without-
|
|
1516
|
+
* explicit-origin" configuration. When `true`, the host should log a
|
|
1517
|
+
* loud warning — silent connection failure is the worst outcome.
|
|
1518
|
+
*/
|
|
1519
|
+
get isProductionUnconfigured(): boolean;
|
|
1520
|
+
/**
|
|
1521
|
+
* `true` when the referrer is the ONLY trust source (no explicit
|
|
1522
|
+
* origins, no localhost matching). In that mode any site that frames
|
|
1523
|
+
* the page becomes a trusted postMessage sender — `document.referrer`
|
|
1524
|
+
* always names the actual embedder. Content is still sanitized, but
|
|
1525
|
+
* the origin allow-list no longer restricts who may drive it. Hosts
|
|
1526
|
+
* should surface a warning suggesting explicit `allowedOrigins` and
|
|
1527
|
+
* a `frame-ancestors` policy in production.
|
|
1528
|
+
*/
|
|
1529
|
+
get isReferrerOnlyTrust(): boolean;
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
/**
|
|
1533
|
+
* Locale detection.
|
|
1534
|
+
*
|
|
1535
|
+
* The runtime needs a sensible default locale for date/number renders
|
|
1536
|
+
* before Payload tells us the active locale. Order of precedence:
|
|
1537
|
+
*
|
|
1538
|
+
* 1. The `lang` attribute on `<html>`.
|
|
1539
|
+
* 2. `navigator.language`.
|
|
1540
|
+
* 3. The hard-coded fallback `'en'`.
|
|
1541
|
+
*
|
|
1542
|
+
* The previous version of this library defaulted to `'de'` — a German
|
|
1543
|
+
* default that surprised consumers in other locales. We now use the
|
|
1544
|
+
* browser hint and fall back to English.
|
|
1545
|
+
*
|
|
1546
|
+
* @module @detection/locale
|
|
1547
|
+
*/
|
|
1548
|
+
/**
|
|
1549
|
+
* Returns a BCP-47 locale string suitable for `Intl.*` constructors.
|
|
1550
|
+
*
|
|
1551
|
+
* Always returns a non-empty string. When no signal is available the
|
|
1552
|
+
* fallback (`'en'`) is returned.
|
|
1553
|
+
*/
|
|
1554
|
+
declare function detectInitialLocale(): string;
|
|
1555
|
+
|
|
393
1556
|
/**
|
|
394
1557
|
* Field-renderer registry.
|
|
395
1558
|
*
|
|
@@ -401,18 +1564,15 @@ declare function documentSavePlugin(options?: DocumentSavePluginOptions): LivePr
|
|
|
401
1564
|
* - Plugins can register custom renderers via the high-level client.
|
|
402
1565
|
* - New field types ship without touching `lifecycle.ts`.
|
|
403
1566
|
*
|
|
404
|
-
* The
|
|
405
|
-
*
|
|
406
|
-
*
|
|
407
|
-
* array — `@field-types/index` invokes it with every concrete renderer
|
|
408
|
-
* so the references stay reachable.
|
|
1567
|
+
* The explicit aggregator in `@field-types/index` supplies the default
|
|
1568
|
+
* renderers as values, so it remains tree-shaking-safe without module import
|
|
1569
|
+
* side effects. This registry stores only explicit consumer overrides.
|
|
409
1570
|
*
|
|
410
1571
|
* @module @field-types/registry
|
|
411
1572
|
*/
|
|
412
1573
|
|
|
413
1574
|
/**
|
|
414
|
-
* Register
|
|
415
|
-
* module's side-effecting registration.
|
|
1575
|
+
* Register or replace an explicit renderer override for future snapshots.
|
|
416
1576
|
*/
|
|
417
1577
|
declare function registerBuiltinRenderer(renderer: FieldRenderer): void;
|
|
418
1578
|
|
|
@@ -470,4 +1630,169 @@ interface PayloadRelationship<TSlug extends string = string> {
|
|
|
470
1630
|
*/
|
|
471
1631
|
declare function buildBuiltinRenderers(): Readonly<Record<string, FieldRenderer>>;
|
|
472
1632
|
|
|
473
|
-
|
|
1633
|
+
/**
|
|
1634
|
+
* Protocol-version negotiation.
|
|
1635
|
+
*
|
|
1636
|
+
* ⚠️ Reality check: **stock Payload does not version its live-preview
|
|
1637
|
+
* postMessage protocol.** The admin never sends a `protocolVersion`
|
|
1638
|
+
* field and ignores extra fields in our `ready` handshake. Observed
|
|
1639
|
+
* wire behaviour:
|
|
1640
|
+
*
|
|
1641
|
+
* - Payload **2.x** sent `fieldSchemaJSON` with the first update
|
|
1642
|
+
* (client-side merge era).
|
|
1643
|
+
* - Payload **3.x** removed `fieldSchemaJSON` entirely; the official
|
|
1644
|
+
* client re-fetches merged documents through the REST API instead.
|
|
1645
|
+
* - No Payload version sends `previewToken` or any keepalive.
|
|
1646
|
+
*
|
|
1647
|
+
* This module therefore only matters for NON-stock admin panels — a
|
|
1648
|
+
* custom `window.postMessage` sender can advertise a version and this
|
|
1649
|
+
* library will negotiate `min(ours, theirs)` and expose capability
|
|
1650
|
+
* flags. Against stock Payload, `theirs` stays `undefined`, negotiation
|
|
1651
|
+
* collapses to v1, and nothing is gated on it: schema handling
|
|
1652
|
+
* activates whenever `fieldSchemaJSON` is actually present, and token
|
|
1653
|
+
* validation only runs when the consumer opts in.
|
|
1654
|
+
*
|
|
1655
|
+
* Version ladder (this library's own numbering, not Payload's):
|
|
1656
|
+
*
|
|
1657
|
+
* - **v1** — data + slug + locale (matches stock Payload of any era).
|
|
1658
|
+
* - **v2** — remote sends `fieldSchemaJSON` (Payload 2.x behaviour).
|
|
1659
|
+
* - **v3** — remote sends `previewToken` (custom senders only).
|
|
1660
|
+
* - **v4** — nested-array recursion / richer document events
|
|
1661
|
+
* (custom senders only).
|
|
1662
|
+
*
|
|
1663
|
+
* @module @core/protocol-version
|
|
1664
|
+
*/
|
|
1665
|
+
declare const LIBRARY_PROTOCOL_VERSION = 4;
|
|
1666
|
+
/** Capability flag → minimum protocol version it requires. */
|
|
1667
|
+
declare const CAPABILITY_REQUIREMENTS: Readonly<Record<string, number>>;
|
|
1668
|
+
type ProtocolCapability = keyof typeof CAPABILITY_REQUIREMENTS;
|
|
1669
|
+
interface ProtocolNegotiation {
|
|
1670
|
+
/** This library's protocol version. */
|
|
1671
|
+
readonly ours: number;
|
|
1672
|
+
/** The remote party's version, when known. */
|
|
1673
|
+
readonly theirs: number | undefined;
|
|
1674
|
+
/** `min(ours, theirs ?? 1)` — the version both sides actually share. */
|
|
1675
|
+
readonly negotiated: number;
|
|
1676
|
+
/** Capability flags enabled at the negotiated version. */
|
|
1677
|
+
readonly capabilities: ReadonlySet<string>;
|
|
1678
|
+
}
|
|
1679
|
+
/**
|
|
1680
|
+
* Compute the negotiated view of the protocol.
|
|
1681
|
+
*
|
|
1682
|
+
* `theirs === undefined` collapses to v1 — assume the remote party is
|
|
1683
|
+
* an older Payload that does not advertise a version.
|
|
1684
|
+
*
|
|
1685
|
+
* Non-finite or non-positive `theirs` values fall back to v1 as well —
|
|
1686
|
+
* we never trust an obviously-corrupted negotiation byte.
|
|
1687
|
+
*/
|
|
1688
|
+
declare function negotiateProtocol(theirs: number | undefined): ProtocolNegotiation;
|
|
1689
|
+
declare function hasCapability(negotiation: ProtocolNegotiation, capability: ProtocolCapability): boolean;
|
|
1690
|
+
|
|
1691
|
+
/**
|
|
1692
|
+
* Type-level path utilities — used by the typed binding DSL to give
|
|
1693
|
+
* callers compile-time autocomplete on field names.
|
|
1694
|
+
*
|
|
1695
|
+
* The same path strings the runtime reads from `data-payload-field`
|
|
1696
|
+
* are typed here, so a typo like `'heroTitlee'` is rejected at compile
|
|
1697
|
+
* time *and* the runtime side does not need to change.
|
|
1698
|
+
*
|
|
1699
|
+
* @module @dsl/paths
|
|
1700
|
+
*/
|
|
1701
|
+
/**
|
|
1702
|
+
* All string-typed top-level keys of `T`. Non-string keys (numeric
|
|
1703
|
+
* indices on arrays) are filtered out.
|
|
1704
|
+
*/
|
|
1705
|
+
type FieldName<T> = Extract<keyof T, string>;
|
|
1706
|
+
/**
|
|
1707
|
+
* Dotted paths into nested object structures, capped at three levels
|
|
1708
|
+
* deep — sufficient for Payload's `group`/`tab`/`array` nesting
|
|
1709
|
+
* without ballooning the IDE's IntelliSense list past the point of
|
|
1710
|
+
* usefulness.
|
|
1711
|
+
*/
|
|
1712
|
+
type FieldPath<T, Depth extends 0 | 1 | 2 | 3 = 3> = Depth extends 0 ? never : T extends readonly (infer U)[] ? FieldPath<U, Prev<Depth>> : T extends object ? {
|
|
1713
|
+
[K in Extract<keyof T, string>]: K | (T[K] extends object ? `${K}.${FieldPath<T[K], Prev<Depth>>}` : never);
|
|
1714
|
+
}[Extract<keyof T, string>] : never;
|
|
1715
|
+
/** Decrement helper for depth-bounded recursion. */
|
|
1716
|
+
type Prev<N extends 0 | 1 | 2 | 3> = N extends 3 ? 2 : N extends 2 ? 1 : N extends 1 ? 0 : 0;
|
|
1717
|
+
/**
|
|
1718
|
+
* Resolve the value type at a dotted path. Returns `unknown` when the
|
|
1719
|
+
* path does not exist on `T`, so consumers can opt into stricter
|
|
1720
|
+
* `keyof`-based DSLs when they want absolute type safety.
|
|
1721
|
+
*/
|
|
1722
|
+
type ValueAt<T, P extends string> = P extends `${infer Head}.${infer Rest}` ? Head extends keyof T ? T[Head] extends readonly (infer U)[] ? ValueAt<U, Rest> : T[Head] extends object ? ValueAt<T[Head], Rest> : unknown : unknown : P extends keyof T ? T[P] : unknown;
|
|
1723
|
+
|
|
1724
|
+
/**
|
|
1725
|
+
* Typed binding helpers — emit the `data-payload-field` attribute pair
|
|
1726
|
+
* that the live-preview runtime reads, with compile-time validation
|
|
1727
|
+
* of the field name.
|
|
1728
|
+
*
|
|
1729
|
+
* Two forms are exposed:
|
|
1730
|
+
*
|
|
1731
|
+
* - **`bind<Schema>('heroTitle')`** — string-literal form. Lightest
|
|
1732
|
+
* possible runtime (no Proxy), but a rename in the IDE does NOT
|
|
1733
|
+
* follow into the string.
|
|
1734
|
+
* - **`bindByPath<Schema>(s => s.heroTitle)`** — proxy form. The
|
|
1735
|
+
* callback walks a Proxy that records the access path, returning
|
|
1736
|
+
* the same shape as `bind` so the two are interchangeable. Slightly
|
|
1737
|
+
* heavier runtime, but rename-safe and discourages typos.
|
|
1738
|
+
*
|
|
1739
|
+
* Both helpers also accept a richer return — `bind('image', 'src')` —
|
|
1740
|
+
* to bind an attribute other than the default `data-payload-field`,
|
|
1741
|
+
* matching the cache resolver's `data-payload-attribute` flow.
|
|
1742
|
+
*
|
|
1743
|
+
* @module @dsl/bind
|
|
1744
|
+
*/
|
|
1745
|
+
|
|
1746
|
+
/**
|
|
1747
|
+
* Shape returned by every binding helper — a spreadable HTML attribute
|
|
1748
|
+
* record. Astro / JSX / Svelte all accept this style directly:
|
|
1749
|
+
*
|
|
1750
|
+
* ```astro
|
|
1751
|
+
* <h1 {...bind<Homepage>('heroTitle')}>{data.heroTitle}</h1>
|
|
1752
|
+
* ```
|
|
1753
|
+
*/
|
|
1754
|
+
interface FieldBindingAttributes {
|
|
1755
|
+
readonly 'data-payload-field': string;
|
|
1756
|
+
readonly 'data-payload-attribute'?: string;
|
|
1757
|
+
readonly 'data-payload-type'?: string;
|
|
1758
|
+
}
|
|
1759
|
+
interface BindOptions {
|
|
1760
|
+
/** Override the bound attribute (e.g., `'src'` for `<img>`). Default: textContent. */
|
|
1761
|
+
readonly attribute?: string;
|
|
1762
|
+
/** Explicit field-type override — bypasses schema detection. */
|
|
1763
|
+
readonly type?: string;
|
|
1764
|
+
}
|
|
1765
|
+
/**
|
|
1766
|
+
* String-literal binding helper.
|
|
1767
|
+
*
|
|
1768
|
+
* ```ts
|
|
1769
|
+
* import { bind } from 'payload-live-preview';
|
|
1770
|
+
* import type { Homepage } from './payload-types';
|
|
1771
|
+
*
|
|
1772
|
+
* <h1 {...bind<Homepage>('heroTitle')}>{data.heroTitle}</h1>
|
|
1773
|
+
* <img {...bind<Homepage>('heroImage', { attribute: 'src' })} />
|
|
1774
|
+
* ```
|
|
1775
|
+
*
|
|
1776
|
+
* The generic `T` is the schema interface emitted by `pll-codegen`.
|
|
1777
|
+
* `field` is constrained to `keyof T` so misspellings fail at compile
|
|
1778
|
+
* time. When `T` is omitted, the helper accepts any string — useful
|
|
1779
|
+
* for incremental adoption.
|
|
1780
|
+
*/
|
|
1781
|
+
declare function bind<T = Record<string, unknown>>(field: FieldName<T>, options?: BindOptions): FieldBindingAttributes;
|
|
1782
|
+
/**
|
|
1783
|
+
* Proxy-path binding helper.
|
|
1784
|
+
*
|
|
1785
|
+
* ```ts
|
|
1786
|
+
* <h1 {...bindByPath<Homepage>(d => d.heroTitle)}>{data.heroTitle}</h1>
|
|
1787
|
+
* ```
|
|
1788
|
+
*
|
|
1789
|
+
* The picker is invoked once with a recording Proxy; the recorded path
|
|
1790
|
+
* is joined with `.` and emitted as the binding name. Property access
|
|
1791
|
+
* on nested objects and arrays both work — `d => d.slides[0].title`
|
|
1792
|
+
* becomes `slides.title` (array indices are normalised away because the
|
|
1793
|
+
* runtime resolves bindings against schema, not against positional
|
|
1794
|
+
* indices).
|
|
1795
|
+
*/
|
|
1796
|
+
declare function bindByPath<T = Record<string, unknown>>(picker: (data: T) => unknown, options?: BindOptions): FieldBindingAttributes;
|
|
1797
|
+
|
|
1798
|
+
export { type AnalyticsSnapshot, type BindOptions, type BlockRenderContext, type BlockRenderer, type CachedElement, type CspDirectiveMerge, type DocumentSaveHandler, type DocumentSavePluginOptions, type DocumentSaveStrategy, EventEmitter, type EventHandler, type FetchPreviewDocumentOptions, type FetchPreviewGlobalOptions, type FieldBindingAttributes, type FieldName, type FieldPath, type FieldRenderer, type FieldTransform, type FieldType, type FrameAncestorsOptions, type InlineScriptConfig, LIBRARY_PROTOCOL_VERSION, type LexicalNode, type LexicalRoot, LivePreviewClient, type LivePreviewClientConfig, type LivePreviewEventMap, type LivePreviewPlugin, OriginDetector, type PayloadFieldSchema, type PayloadFieldType, type PayloadLivePreviewData, type PayloadLivePreviewMessage, type PayloadMedia, type PayloadRelationship, type PluginContext, type PluginDisposer, type PluginEvents, type PreviewFetchBaseOptions, type PreviewRequestLike, type PreviewRequestOptions, type PreviewWhere, type ProtocolCapability, type ProtocolNegotiation, type RenderContext, type SanitizerDocument, type Unsubscribe, VERSION, type ValueAt, bind, bindByPath, buildBuiltinRenderers, buildFrameAncestors, buildScriptSrcWithNonce, createAnalyticsPlugin, debugPlugin, detectInitialLocale, documentSavePlugin, escapeHtml, escapeHtmlAttribute, fetchPreviewDocument, fetchPreviewGlobal, generateCspNonce, generateInlineScript, hasCapability, highlightPlugin, initLivePreview, isDevMode, isExternalHttpUrl, isInIframe, isInPopup, isInPreviewContext, isLexicalContent, isPreviewRequest, isSafeUrl, lexicalToHtml, lexicalToPlainText, mergeCspHeader, negotiateProtocol, registerBlockRenderer, registerBuiltinRenderer, runtimeBuildInfo, sanitizeHtml, setCspCrypto, setSanitizerDocument, wrapWithScriptTag };
|