payload-live-preview 2.0.6 → 2.1.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 +148 -38
- package/dist/adapters/astro/FragmentBridge.astro +25 -0
- package/dist/adapters/astro/PreviewBoundary.astro +2 -4
- package/dist/adapters/astro/RichText.astro +26 -5
- package/dist/adapters/astro/index.d.ts +41 -9
- package/dist/adapters/astro/index.js +1 -1
- package/dist/adapters/astro/index.js.map +1 -1
- package/dist/adapters/astro/middleware-entry.d.ts +3 -3
- package/dist/adapters/astro/middleware-entry.js +1 -1
- package/dist/adapters/astro/middleware-entry.js.map +1 -1
- package/dist/adapters/nextjs/index.d.ts +17 -12
- package/dist/adapters/nextjs/index.js +1 -1
- package/dist/adapters/nextjs/index.js.map +1 -1
- package/dist/adapters/nuxt/index.d.ts +11 -6
- package/dist/adapters/nuxt/index.js +1 -1
- package/dist/adapters/nuxt/index.js.map +1 -1
- package/dist/adapters/nuxt/module.d.ts +64 -13
- package/dist/adapters/nuxt/module.js +1 -1
- package/dist/adapters/nuxt/module.js.map +1 -1
- package/dist/adapters/react/index.d.ts +11 -7
- package/dist/adapters/react/index.js +1 -1
- package/dist/adapters/react/index.js.map +1 -1
- package/dist/adapters/sveltekit/index.d.ts +10 -5
- package/dist/adapters/sveltekit/index.js +1 -1
- package/dist/adapters/sveltekit/index.js.map +1 -1
- package/dist/adapters/vue/index.d.ts +10 -7
- package/dist/adapters/vue/index.js +1 -1
- package/dist/adapters/vue/index.js.map +1 -1
- package/dist/annotate.d.ts +15 -2
- package/dist/annotate.js.map +1 -1
- package/dist/client.cjs +1 -1
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +367 -266
- package/dist/client.d.ts +367 -266
- package/dist/client.js +1 -1
- package/dist/client.js.map +1 -1
- package/dist/codegen-cli.js.map +1 -1
- package/dist/codegen.cjs.map +1 -1
- package/dist/codegen.d.cts +7 -1
- package/dist/codegen.d.ts +7 -1
- package/dist/codegen.js.map +1 -1
- package/dist/core.cjs +1 -1
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +549 -393
- package/dist/core.d.ts +549 -393
- package/dist/core.js +1 -1
- package/dist/core.js.map +1 -1
- package/dist/doctor-cli.d.ts +1 -1
- package/dist/doctor-cli.js +1 -1
- package/dist/doctor-cli.js.map +1 -1
- package/dist/doctor.d.ts +2 -2
- package/dist/doctor.js +1 -1
- package/dist/doctor.js.map +1 -1
- package/dist/{document-session-BfBfLVpg.d.ts → document-session-DWi9H3Qk.d.ts} +7 -0
- package/dist/{fragment-endpoint-DATKU183.d.ts → fragment-endpoint-B2qcRw5T.d.ts} +22 -7
- package/dist/fragment.cjs +1 -1
- package/dist/fragment.cjs.map +1 -1
- package/dist/fragment.d.cts +37 -16
- package/dist/fragment.d.ts +37 -16
- package/dist/fragment.js +1 -1
- package/dist/fragment.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +607 -405
- package/dist/index.d.ts +607 -405
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lean.cjs +1 -1
- package/dist/lean.cjs.map +1 -1
- package/dist/lean.d.cts +5 -2
- package/dist/lean.d.ts +5 -2
- package/dist/lean.js +1 -1
- package/dist/lean.js.map +1 -1
- package/dist/lexical.cjs +1 -1
- package/dist/lexical.cjs.map +1 -1
- package/dist/lexical.d.cts +32 -11
- package/dist/lexical.d.ts +32 -11
- package/dist/lexical.js +1 -1
- package/dist/lexical.js.map +1 -1
- package/dist/{locals-CNUGYbA_.d.ts → locals-B1mF3aTb.d.ts} +1 -1
- package/dist/{middleware-CWObDO37.d.ts → middleware-BGobLN9P.d.ts} +11 -5
- package/dist/migrate.js +1 -1
- package/dist/migrate.js.map +1 -1
- package/dist/{options-uHKrDM2U.d.ts → options-Cm8fC7dj.d.ts} +31 -1
- package/dist/payload.cjs +1 -1
- package/dist/payload.cjs.map +1 -1
- package/dist/payload.d.cts +18 -2
- package/dist/payload.d.ts +18 -2
- package/dist/payload.js +1 -1
- package/dist/payload.js.map +1 -1
- package/dist/plugin.cjs +2 -0
- package/dist/plugin.cjs.map +1 -0
- package/dist/plugin.d.cts +30 -0
- package/dist/plugin.d.ts +30 -0
- package/dist/plugin.js +2 -0
- package/dist/plugin.js.map +1 -0
- package/dist/plugins.cjs +1 -1
- package/dist/plugins.cjs.map +1 -1
- package/dist/plugins.d.cts +102 -29
- package/dist/plugins.d.ts +102 -29
- package/dist/plugins.js +1 -1
- package/dist/plugins.js.map +1 -1
- package/dist/{probe-Bzm4fYFL.d.ts → probe-DGPDsnbj.d.ts} +6 -2
- package/dist/server.cjs +1 -1
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +51 -7
- package/dist/server.d.ts +51 -7
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/dist/structural.cjs +2 -2
- package/dist/structural.cjs.map +1 -1
- package/dist/structural.d.cts +61 -13
- package/dist/structural.d.ts +61 -13
- package/dist/structural.js +2 -2
- package/dist/structural.js.map +1 -1
- package/package.json +33 -4
package/dist/server.d.cts
CHANGED
|
@@ -24,6 +24,25 @@
|
|
|
24
24
|
*/
|
|
25
25
|
declare const AUTHORIZED_PREVIEW_BRAND_KEY = "payload-live-preview.authorized-preview-context";
|
|
26
26
|
type PreviewAuthorizationStrategyName = 'payload-session' | 'signed-token' | 'verifier';
|
|
27
|
+
/** One server-authorized Payload document, not a query selected by the browser. */
|
|
28
|
+
type AuthorizedPreviewDocument = {
|
|
29
|
+
readonly kind: 'collection';
|
|
30
|
+
readonly slug: string;
|
|
31
|
+
readonly id: string | number;
|
|
32
|
+
} | {
|
|
33
|
+
readonly kind: 'global';
|
|
34
|
+
readonly slug: string;
|
|
35
|
+
};
|
|
36
|
+
/** Opt-in data capability, enforced by the fragment endpoint and `definePreview`. */
|
|
37
|
+
interface AuthorizedPreviewPayloadScope {
|
|
38
|
+
/** Payload base URL, distinct from the preview site's audience. */
|
|
39
|
+
readonly serverURL: string;
|
|
40
|
+
/** REST prefix, default `/api`. */
|
|
41
|
+
readonly apiRoute?: string;
|
|
42
|
+
readonly document: AuthorizedPreviewDocument;
|
|
43
|
+
/** Maximum requested population depth, not a field-level ACL. */
|
|
44
|
+
readonly maxDepth: number;
|
|
45
|
+
}
|
|
27
46
|
/** What a context is bound to; every field is optional because every strategy binds differently. */
|
|
28
47
|
interface AuthorizedPreviewScope {
|
|
29
48
|
/** The site origin the credential was issued for. */
|
|
@@ -31,6 +50,8 @@ interface AuthorizedPreviewScope {
|
|
|
31
50
|
/** The request pathname the credential is valid for. */
|
|
32
51
|
readonly path?: string;
|
|
33
52
|
readonly locale?: string;
|
|
53
|
+
/** Requires a finite context expiry. Unscoped contexts keep their existing behavior. */
|
|
54
|
+
readonly payload?: AuthorizedPreviewPayloadScope;
|
|
34
55
|
}
|
|
35
56
|
/** The verdict of a successful `authorizePreviewRequest()`: frozen, branded, produced only there. Carry it, do not rebuild it. */
|
|
36
57
|
interface AuthorizedPreviewContext {
|
|
@@ -46,7 +67,11 @@ interface AuthorizedPreviewContext {
|
|
|
46
67
|
/** The minimal request material a draft read forwards to Payload — one verified cookie, never the whole `Cookie` header. */
|
|
47
68
|
readonly payloadHeaders: Readonly<Record<string, string>>;
|
|
48
69
|
}
|
|
49
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* Whether `value` carries the brand `authorizePreviewRequest()` gives it. A JSON round trip or an
|
|
72
|
+
* unfrozen copy does not; a frozen spread copy keeps the registry symbol and passes, because the
|
|
73
|
+
* brand is a runtime check, not a cryptographic one (ADR 0006).
|
|
74
|
+
*/
|
|
50
75
|
declare function isAuthorizedPreviewContext(value: unknown): value is AuthorizedPreviewContext;
|
|
51
76
|
|
|
52
77
|
/**
|
|
@@ -73,11 +98,15 @@ interface PreviewAuthorizationRequest {
|
|
|
73
98
|
readonly headers: {
|
|
74
99
|
get(name: string): string | null;
|
|
75
100
|
};
|
|
101
|
+
/** Request lifetime, when supplied by the caller. Session fetches follow it. */
|
|
102
|
+
readonly signal?: AbortSignal;
|
|
76
103
|
}
|
|
77
104
|
|
|
78
105
|
/**
|
|
79
106
|
* The `payload-session` strategy: forward exactly one named cookie to
|
|
80
107
|
* `GET <serverURL>/api/<usersSlug>/me` and authorize when a user comes back.
|
|
108
|
+
* A cookie-only request preserves working Payload 2 sessions. When `/me`
|
|
109
|
+
* reports no user, one retry adds an `Origin` for Payload 3's CSRF check.
|
|
81
110
|
*/
|
|
82
111
|
|
|
83
112
|
/** A `fetch`-compatible function, injectable for tests. */
|
|
@@ -97,6 +126,12 @@ interface PayloadSessionStrategy {
|
|
|
97
126
|
readonly usersSlug?: string;
|
|
98
127
|
/** Cookie name; Payload's default is `payload-token`. */
|
|
99
128
|
readonly cookieName?: string;
|
|
129
|
+
/**
|
|
130
|
+
* Send this listed origin with the cookie from the first request. Without
|
|
131
|
+
* it, try the existing cookie-only request, then the origin of `serverURL`
|
|
132
|
+
* once when `/me` reports a null user. Draft reads keep the accepted headers.
|
|
133
|
+
*/
|
|
134
|
+
readonly origin?: string;
|
|
100
135
|
/** Upper bound for the `/me` round trip. Below 250 ms is raised to 250 ms; default 3000. */
|
|
101
136
|
readonly timeoutMs?: number;
|
|
102
137
|
/** Longest cookie value accepted; longer ones are refused as `missing-credential`. Default 4096. */
|
|
@@ -151,7 +186,7 @@ interface PreviewTokenReplayStore {
|
|
|
151
186
|
consume(id: string, expiresAt: number): Promise<boolean> | boolean;
|
|
152
187
|
}
|
|
153
188
|
/**
|
|
154
|
-
* @deprecated The
|
|
189
|
+
* @deprecated The store shape of the 2.0 beta, removed in 3.0. Its two calls cannot be one
|
|
155
190
|
* step, so two requests with the same token that arrive together both pass
|
|
156
191
|
* `isUsed` before either reaches `markUsed`. Implement
|
|
157
192
|
* {@link PreviewTokenReplayStore.consume} instead.
|
|
@@ -280,7 +315,7 @@ interface BindOptions {
|
|
|
280
315
|
readonly attribute?: string;
|
|
281
316
|
/** Explicit field type, bypassing schema detection. */
|
|
282
317
|
readonly type?: string;
|
|
283
|
-
/** Mark the binding as Lexical rich text; needed
|
|
318
|
+
/** Mark the binding as Lexical rich text; needed for an element that can hold uploads or relationships, or when the initial render is empty. */
|
|
284
319
|
readonly richtext?: boolean;
|
|
285
320
|
/** Render the value as sanitised HTML rather than text. */
|
|
286
321
|
readonly html?: boolean;
|
|
@@ -342,6 +377,12 @@ interface PreviewBindings {
|
|
|
342
377
|
* The registry behind `id` is the endpoint's (docs/hybrid.md).
|
|
343
378
|
*/
|
|
344
379
|
boundary: (id: string, options?: FragmentBoundaryOptions) => FragmentBoundaryAttributes | SuppressedBinding;
|
|
380
|
+
/**
|
|
381
|
+
* Field paths the page accounts for without binding them, and everything
|
|
382
|
+
* below them (`data-payload-covers`, ADR 0022), or nothing while
|
|
383
|
+
* unauthorized.
|
|
384
|
+
*/
|
|
385
|
+
covers: (...paths: readonly string[]) => Readonly<Record<'data-payload-covers', string>> | SuppressedBinding;
|
|
345
386
|
}
|
|
346
387
|
/** Request-scoped `bind`, `bindByPath` and `owner`, all suppressed unless `authorization` is a real context. */
|
|
347
388
|
declare function createPreviewBindings(options: PreviewBindingsOptions): PreviewBindings;
|
|
@@ -416,7 +457,7 @@ interface PreviewServerConfig {
|
|
|
416
457
|
readonly onDiagnostic?: (diagnostic: PreviewFetchDiagnostic) => void;
|
|
417
458
|
}
|
|
418
459
|
/** Why a read did not produce a document. */
|
|
419
|
-
type PreviewFetchFailureReason = 'http' | 'network' | 'timeout' | 'aborted' | 'invalid-json' | 'no-fetch';
|
|
460
|
+
type PreviewFetchFailureReason = 'http' | 'network' | 'timeout' | 'aborted' | 'invalid-json' | 'no-fetch' | 'scope';
|
|
420
461
|
type PreviewFetchResult<T> = {
|
|
421
462
|
readonly ok: true;
|
|
422
463
|
/** The document, or `null` when the query matched nothing. */
|
|
@@ -451,8 +492,9 @@ interface PreviewWhere {
|
|
|
451
492
|
/** Options every read shares; `fetchDocument` and `fetchGlobal` add their target. */
|
|
452
493
|
interface PreviewReadOptions {
|
|
453
494
|
/**
|
|
454
|
-
* The verdict from `authorizePreviewRequest()`. Only a
|
|
455
|
-
* draft; `null`,
|
|
495
|
+
* The verdict from `authorizePreviewRequest()`. Only a context that passes
|
|
496
|
+
* `isAuthorizedPreviewContext()` reads the draft; `null`, an unfrozen copy or a
|
|
497
|
+
* JSON round trip reads the published document.
|
|
456
498
|
*/
|
|
457
499
|
readonly authorization: AuthorizedPreviewContext | null;
|
|
458
500
|
readonly locale?: string;
|
|
@@ -465,6 +507,8 @@ interface PreviewReadOptions {
|
|
|
465
507
|
}
|
|
466
508
|
interface ReadDocumentOptions extends PreviewReadOptions {
|
|
467
509
|
readonly collection: string;
|
|
510
|
+
/** Direct REST document read. Required with `scope.payload`; cannot be combined with `where`. */
|
|
511
|
+
readonly id?: string | number;
|
|
468
512
|
readonly where?: PreviewWhere;
|
|
469
513
|
}
|
|
470
514
|
interface ReadGlobalOptions extends PreviewReadOptions {
|
|
@@ -486,4 +530,4 @@ interface PreviewServer {
|
|
|
486
530
|
/** Bind origin, route and depth once; read documents and globals with an explicit authorization verdict. */
|
|
487
531
|
declare function definePreview(config: PreviewServerConfig): PreviewServer;
|
|
488
532
|
|
|
489
|
-
export { AUTHORIZED_PREVIEW_BRAND_KEY, type AuthorizedPreviewContext, type AuthorizedPreviewScope, type BindOptions, type FetchLike, type FieldBindingAttributes, type FieldName, type FieldPath, type FragmentBoundaryAttributes, type FragmentBoundaryOptions, type IssuePreviewTokenOptions, type OwnerBindingAttributes, type PayloadSessionStrategy, type PreviewAuthorization, type PreviewAuthorizationOutcome, type PreviewAuthorizationRequest, type PreviewAuthorizationStrategy, type PreviewAuthorizationStrategyName, type PreviewBindings, type PreviewBindingsOptions, type PreviewFetchDiagnostic, PreviewFetchError, type PreviewFetchFailureReason, type PreviewFetchFunction, type PreviewFetchResult, type PreviewReadOptions, type PreviewRequestLike, type PreviewRequestOptions, type PreviewRuntimeOptions, type PreviewServer, type PreviewServerConfig, type PreviewSignal, type PreviewTokenClaims, type PreviewTokenReplayChecks, type PreviewTokenReplayStore, type PreviewTokenTransport, type PreviewVerifierClaims, type PreviewWhere, type PreviewWhereValue, type ReadDocumentOptions, type ReadGlobalOptions, type SignedTokenStrategy, type SubtleCryptoLike, type SuppressedBinding, type ValueAt, type VerifierStrategy, authorizePreviewRequest, bind, bindByPath, createPreviewBindings, definePreview, extractCookie, hasPreviewIntent, isAuthorizedPreviewContext, issuePreviewToken, previewBindingsFromLocals };
|
|
533
|
+
export { AUTHORIZED_PREVIEW_BRAND_KEY, type AuthorizedPreviewContext, type AuthorizedPreviewDocument, type AuthorizedPreviewPayloadScope, type AuthorizedPreviewScope, type BindOptions, type FetchLike, type FieldBindingAttributes, type FieldName, type FieldPath, type FragmentBoundaryAttributes, type FragmentBoundaryOptions, type IssuePreviewTokenOptions, type OwnerBindingAttributes, type PayloadSessionStrategy, type PreviewAuthorization, type PreviewAuthorizationOutcome, type PreviewAuthorizationRequest, type PreviewAuthorizationStrategy, type PreviewAuthorizationStrategyName, type PreviewBindings, type PreviewBindingsOptions, type PreviewFetchDiagnostic, PreviewFetchError, type PreviewFetchFailureReason, type PreviewFetchFunction, type PreviewFetchResult, type PreviewReadOptions, type PreviewRequestLike, type PreviewRequestOptions, type PreviewRuntimeOptions, type PreviewServer, type PreviewServerConfig, type PreviewSignal, type PreviewTokenClaims, type PreviewTokenReplayChecks, type PreviewTokenReplayStore, type PreviewTokenTransport, type PreviewVerifierClaims, type PreviewWhere, type PreviewWhereValue, type ReadDocumentOptions, type ReadGlobalOptions, type SignedTokenStrategy, type SubtleCryptoLike, type SuppressedBinding, type ValueAt, type VerifierStrategy, authorizePreviewRequest, bind, bindByPath, createPreviewBindings, definePreview, extractCookie, hasPreviewIntent, isAuthorizedPreviewContext, issuePreviewToken, previewBindingsFromLocals };
|
package/dist/server.d.ts
CHANGED
|
@@ -24,6 +24,25 @@
|
|
|
24
24
|
*/
|
|
25
25
|
declare const AUTHORIZED_PREVIEW_BRAND_KEY = "payload-live-preview.authorized-preview-context";
|
|
26
26
|
type PreviewAuthorizationStrategyName = 'payload-session' | 'signed-token' | 'verifier';
|
|
27
|
+
/** One server-authorized Payload document, not a query selected by the browser. */
|
|
28
|
+
type AuthorizedPreviewDocument = {
|
|
29
|
+
readonly kind: 'collection';
|
|
30
|
+
readonly slug: string;
|
|
31
|
+
readonly id: string | number;
|
|
32
|
+
} | {
|
|
33
|
+
readonly kind: 'global';
|
|
34
|
+
readonly slug: string;
|
|
35
|
+
};
|
|
36
|
+
/** Opt-in data capability, enforced by the fragment endpoint and `definePreview`. */
|
|
37
|
+
interface AuthorizedPreviewPayloadScope {
|
|
38
|
+
/** Payload base URL, distinct from the preview site's audience. */
|
|
39
|
+
readonly serverURL: string;
|
|
40
|
+
/** REST prefix, default `/api`. */
|
|
41
|
+
readonly apiRoute?: string;
|
|
42
|
+
readonly document: AuthorizedPreviewDocument;
|
|
43
|
+
/** Maximum requested population depth, not a field-level ACL. */
|
|
44
|
+
readonly maxDepth: number;
|
|
45
|
+
}
|
|
27
46
|
/** What a context is bound to; every field is optional because every strategy binds differently. */
|
|
28
47
|
interface AuthorizedPreviewScope {
|
|
29
48
|
/** The site origin the credential was issued for. */
|
|
@@ -31,6 +50,8 @@ interface AuthorizedPreviewScope {
|
|
|
31
50
|
/** The request pathname the credential is valid for. */
|
|
32
51
|
readonly path?: string;
|
|
33
52
|
readonly locale?: string;
|
|
53
|
+
/** Requires a finite context expiry. Unscoped contexts keep their existing behavior. */
|
|
54
|
+
readonly payload?: AuthorizedPreviewPayloadScope;
|
|
34
55
|
}
|
|
35
56
|
/** The verdict of a successful `authorizePreviewRequest()`: frozen, branded, produced only there. Carry it, do not rebuild it. */
|
|
36
57
|
interface AuthorizedPreviewContext {
|
|
@@ -46,7 +67,11 @@ interface AuthorizedPreviewContext {
|
|
|
46
67
|
/** The minimal request material a draft read forwards to Payload — one verified cookie, never the whole `Cookie` header. */
|
|
47
68
|
readonly payloadHeaders: Readonly<Record<string, string>>;
|
|
48
69
|
}
|
|
49
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* Whether `value` carries the brand `authorizePreviewRequest()` gives it. A JSON round trip or an
|
|
72
|
+
* unfrozen copy does not; a frozen spread copy keeps the registry symbol and passes, because the
|
|
73
|
+
* brand is a runtime check, not a cryptographic one (ADR 0006).
|
|
74
|
+
*/
|
|
50
75
|
declare function isAuthorizedPreviewContext(value: unknown): value is AuthorizedPreviewContext;
|
|
51
76
|
|
|
52
77
|
/**
|
|
@@ -73,11 +98,15 @@ interface PreviewAuthorizationRequest {
|
|
|
73
98
|
readonly headers: {
|
|
74
99
|
get(name: string): string | null;
|
|
75
100
|
};
|
|
101
|
+
/** Request lifetime, when supplied by the caller. Session fetches follow it. */
|
|
102
|
+
readonly signal?: AbortSignal;
|
|
76
103
|
}
|
|
77
104
|
|
|
78
105
|
/**
|
|
79
106
|
* The `payload-session` strategy: forward exactly one named cookie to
|
|
80
107
|
* `GET <serverURL>/api/<usersSlug>/me` and authorize when a user comes back.
|
|
108
|
+
* A cookie-only request preserves working Payload 2 sessions. When `/me`
|
|
109
|
+
* reports no user, one retry adds an `Origin` for Payload 3's CSRF check.
|
|
81
110
|
*/
|
|
82
111
|
|
|
83
112
|
/** A `fetch`-compatible function, injectable for tests. */
|
|
@@ -97,6 +126,12 @@ interface PayloadSessionStrategy {
|
|
|
97
126
|
readonly usersSlug?: string;
|
|
98
127
|
/** Cookie name; Payload's default is `payload-token`. */
|
|
99
128
|
readonly cookieName?: string;
|
|
129
|
+
/**
|
|
130
|
+
* Send this listed origin with the cookie from the first request. Without
|
|
131
|
+
* it, try the existing cookie-only request, then the origin of `serverURL`
|
|
132
|
+
* once when `/me` reports a null user. Draft reads keep the accepted headers.
|
|
133
|
+
*/
|
|
134
|
+
readonly origin?: string;
|
|
100
135
|
/** Upper bound for the `/me` round trip. Below 250 ms is raised to 250 ms; default 3000. */
|
|
101
136
|
readonly timeoutMs?: number;
|
|
102
137
|
/** Longest cookie value accepted; longer ones are refused as `missing-credential`. Default 4096. */
|
|
@@ -151,7 +186,7 @@ interface PreviewTokenReplayStore {
|
|
|
151
186
|
consume(id: string, expiresAt: number): Promise<boolean> | boolean;
|
|
152
187
|
}
|
|
153
188
|
/**
|
|
154
|
-
* @deprecated The
|
|
189
|
+
* @deprecated The store shape of the 2.0 beta, removed in 3.0. Its two calls cannot be one
|
|
155
190
|
* step, so two requests with the same token that arrive together both pass
|
|
156
191
|
* `isUsed` before either reaches `markUsed`. Implement
|
|
157
192
|
* {@link PreviewTokenReplayStore.consume} instead.
|
|
@@ -280,7 +315,7 @@ interface BindOptions {
|
|
|
280
315
|
readonly attribute?: string;
|
|
281
316
|
/** Explicit field type, bypassing schema detection. */
|
|
282
317
|
readonly type?: string;
|
|
283
|
-
/** Mark the binding as Lexical rich text; needed
|
|
318
|
+
/** Mark the binding as Lexical rich text; needed for an element that can hold uploads or relationships, or when the initial render is empty. */
|
|
284
319
|
readonly richtext?: boolean;
|
|
285
320
|
/** Render the value as sanitised HTML rather than text. */
|
|
286
321
|
readonly html?: boolean;
|
|
@@ -342,6 +377,12 @@ interface PreviewBindings {
|
|
|
342
377
|
* The registry behind `id` is the endpoint's (docs/hybrid.md).
|
|
343
378
|
*/
|
|
344
379
|
boundary: (id: string, options?: FragmentBoundaryOptions) => FragmentBoundaryAttributes | SuppressedBinding;
|
|
380
|
+
/**
|
|
381
|
+
* Field paths the page accounts for without binding them, and everything
|
|
382
|
+
* below them (`data-payload-covers`, ADR 0022), or nothing while
|
|
383
|
+
* unauthorized.
|
|
384
|
+
*/
|
|
385
|
+
covers: (...paths: readonly string[]) => Readonly<Record<'data-payload-covers', string>> | SuppressedBinding;
|
|
345
386
|
}
|
|
346
387
|
/** Request-scoped `bind`, `bindByPath` and `owner`, all suppressed unless `authorization` is a real context. */
|
|
347
388
|
declare function createPreviewBindings(options: PreviewBindingsOptions): PreviewBindings;
|
|
@@ -416,7 +457,7 @@ interface PreviewServerConfig {
|
|
|
416
457
|
readonly onDiagnostic?: (diagnostic: PreviewFetchDiagnostic) => void;
|
|
417
458
|
}
|
|
418
459
|
/** Why a read did not produce a document. */
|
|
419
|
-
type PreviewFetchFailureReason = 'http' | 'network' | 'timeout' | 'aborted' | 'invalid-json' | 'no-fetch';
|
|
460
|
+
type PreviewFetchFailureReason = 'http' | 'network' | 'timeout' | 'aborted' | 'invalid-json' | 'no-fetch' | 'scope';
|
|
420
461
|
type PreviewFetchResult<T> = {
|
|
421
462
|
readonly ok: true;
|
|
422
463
|
/** The document, or `null` when the query matched nothing. */
|
|
@@ -451,8 +492,9 @@ interface PreviewWhere {
|
|
|
451
492
|
/** Options every read shares; `fetchDocument` and `fetchGlobal` add their target. */
|
|
452
493
|
interface PreviewReadOptions {
|
|
453
494
|
/**
|
|
454
|
-
* The verdict from `authorizePreviewRequest()`. Only a
|
|
455
|
-
* draft; `null`,
|
|
495
|
+
* The verdict from `authorizePreviewRequest()`. Only a context that passes
|
|
496
|
+
* `isAuthorizedPreviewContext()` reads the draft; `null`, an unfrozen copy or a
|
|
497
|
+
* JSON round trip reads the published document.
|
|
456
498
|
*/
|
|
457
499
|
readonly authorization: AuthorizedPreviewContext | null;
|
|
458
500
|
readonly locale?: string;
|
|
@@ -465,6 +507,8 @@ interface PreviewReadOptions {
|
|
|
465
507
|
}
|
|
466
508
|
interface ReadDocumentOptions extends PreviewReadOptions {
|
|
467
509
|
readonly collection: string;
|
|
510
|
+
/** Direct REST document read. Required with `scope.payload`; cannot be combined with `where`. */
|
|
511
|
+
readonly id?: string | number;
|
|
468
512
|
readonly where?: PreviewWhere;
|
|
469
513
|
}
|
|
470
514
|
interface ReadGlobalOptions extends PreviewReadOptions {
|
|
@@ -486,4 +530,4 @@ interface PreviewServer {
|
|
|
486
530
|
/** Bind origin, route and depth once; read documents and globals with an explicit authorization verdict. */
|
|
487
531
|
declare function definePreview(config: PreviewServerConfig): PreviewServer;
|
|
488
532
|
|
|
489
|
-
export { AUTHORIZED_PREVIEW_BRAND_KEY, type AuthorizedPreviewContext, type AuthorizedPreviewScope, type BindOptions, type FetchLike, type FieldBindingAttributes, type FieldName, type FieldPath, type FragmentBoundaryAttributes, type FragmentBoundaryOptions, type IssuePreviewTokenOptions, type OwnerBindingAttributes, type PayloadSessionStrategy, type PreviewAuthorization, type PreviewAuthorizationOutcome, type PreviewAuthorizationRequest, type PreviewAuthorizationStrategy, type PreviewAuthorizationStrategyName, type PreviewBindings, type PreviewBindingsOptions, type PreviewFetchDiagnostic, PreviewFetchError, type PreviewFetchFailureReason, type PreviewFetchFunction, type PreviewFetchResult, type PreviewReadOptions, type PreviewRequestLike, type PreviewRequestOptions, type PreviewRuntimeOptions, type PreviewServer, type PreviewServerConfig, type PreviewSignal, type PreviewTokenClaims, type PreviewTokenReplayChecks, type PreviewTokenReplayStore, type PreviewTokenTransport, type PreviewVerifierClaims, type PreviewWhere, type PreviewWhereValue, type ReadDocumentOptions, type ReadGlobalOptions, type SignedTokenStrategy, type SubtleCryptoLike, type SuppressedBinding, type ValueAt, type VerifierStrategy, authorizePreviewRequest, bind, bindByPath, createPreviewBindings, definePreview, extractCookie, hasPreviewIntent, isAuthorizedPreviewContext, issuePreviewToken, previewBindingsFromLocals };
|
|
533
|
+
export { AUTHORIZED_PREVIEW_BRAND_KEY, type AuthorizedPreviewContext, type AuthorizedPreviewDocument, type AuthorizedPreviewPayloadScope, type AuthorizedPreviewScope, type BindOptions, type FetchLike, type FieldBindingAttributes, type FieldName, type FieldPath, type FragmentBoundaryAttributes, type FragmentBoundaryOptions, type IssuePreviewTokenOptions, type OwnerBindingAttributes, type PayloadSessionStrategy, type PreviewAuthorization, type PreviewAuthorizationOutcome, type PreviewAuthorizationRequest, type PreviewAuthorizationStrategy, type PreviewAuthorizationStrategyName, type PreviewBindings, type PreviewBindingsOptions, type PreviewFetchDiagnostic, PreviewFetchError, type PreviewFetchFailureReason, type PreviewFetchFunction, type PreviewFetchResult, type PreviewReadOptions, type PreviewRequestLike, type PreviewRequestOptions, type PreviewRuntimeOptions, type PreviewServer, type PreviewServerConfig, type PreviewSignal, type PreviewTokenClaims, type PreviewTokenReplayChecks, type PreviewTokenReplayStore, type PreviewTokenTransport, type PreviewVerifierClaims, type PreviewWhere, type PreviewWhereValue, type ReadDocumentOptions, type ReadGlobalOptions, type SignedTokenStrategy, type SubtleCryptoLike, type SuppressedBinding, type ValueAt, type VerifierStrategy, authorizePreviewRequest, bind, bindByPath, createPreviewBindings, definePreview, extractCookie, hasPreviewIntent, isAuthorizedPreviewContext, issuePreviewToken, previewBindingsFromLocals };
|
package/dist/server.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var e="payload-live-preview.authorized-preview-context",t=Symbol.for(e);function isAuthorizedPreviewContext(e){return"object"==typeof e&&null!==e&&!0===e[t]&&Object.isFrozen(e)}function r(r){return Object.freeze({...r,scope:Object.freeze({...r.scope}),payloadHeaders:Object.freeze({...r.payloadHeaders}),[t]:!0,[e]:!0})}var n=class extends Error{name="PreviewConfigurationError"};function o(e){return{authorized:!1,outcome:e,context:null}}var a="users",i=/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/,s=/^[A-Za-z0-9._~-]+$/;function u(e,t,r=4096){if(null===e||0===e.length)return null;const n=`${t}=`,o=[];for(const t of e.split(";")){const e=t.trim();e.startsWith(n)&&o.push(e.slice(n.length))}if(1!==o.length)return null;const a=o[0]??"";return 0===a.length||a.length>r||!s.test(a)?null:a}var c,l="live-preview",d="v1";function p(){return c??=new TextEncoder,c}function f(e){const t="string"==typeof e?p().encode(e):e;if(t.byteLength<32)throw new n(`payload-live-preview: the preview token secret must be at least ${String(32)} bytes`);return t}function h(e){if(void 0!==e)return e;const t=globalThis.crypto;return"object"==typeof t?.subtle?t:void 0}async function y(e,t){return e.subtle.importKey("raw",t,{name:"HMAC",hash:"SHA-256"},!1,["sign","verify"])}function v(e){let t="";for(const r of e)t+=String.fromCharCode(r);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function w(e){if(!/^[A-Za-z0-9_-]*$/.test(e))return;const t=e.replace(/-/g,"+").replace(/_/g,"/")+"=".repeat((4-e.length%4)%4);try{const e=atob(t),r=new Uint8Array(e.length);for(let t=0;t<e.length;t+=1)r[t]=e.charCodeAt(t);return r}catch{return}}async function g(e,t){const r=h(t.crypto);if(void 0===r)throw new Error("payload-live-preview: no Web Crypto implementation available to sign tokens");const n=f(t.secret),o=(t.now??Date.now)(),a=Math.min(36e5,Math.max(1,e.ttlMs??6e5)),i=new Uint8Array(16);r.getRandomValues(i);const s={v:1,aud:e.audience,...void 0!==e.path?{pth:e.path}:{},...void 0!==e.locale?{loc:e.locale}:{},...void 0!==e.subject?{sub:e.subject}:{},pur:e.purpose??l,iat:o,exp:o+a,jti:v(i)},u=v(p().encode(JSON.stringify(s))),c=`${d}.${u}`,w=await y(r,n);return`${c}.${v(new Uint8Array(await r.subtle.sign("HMAC",w,p().encode(c))))}`}async function b(e,t){switch(t.type){case"payload-session":return async function(e,t){const s=function(e){const t=e??"payload-token";if(!i.test(t))throw new n(`payload-live-preview: cookie name "${t}" is not a valid cookie-name token`);return t}(t.cookieName),c=function(e){let t;try{t=new URL(e)}catch{throw new n(`payload-live-preview: serverURL "${e}" is not an absolute URL`)}if("https:"!==t.protocol&&"http:"!==t.protocol)throw new n(`payload-live-preview: serverURL must be http(s), got "${t.protocol}"`);return t.origin}(t.serverURL),l=u(e.headers.get("cookie"),s,t.maxCookieLength??4096);if(null===l)return o("missing-credential");const d=t.now??Date.now,p=Math.max(250,t.timeoutMs??3e3),f=t.fetch??globalThis.fetch,h=encodeURIComponent(t.usersSlug??a),y=`${s}=${l}`;let v;try{const e=await f(`${c}/api/${h}/me?depth=0`,{headers:{cookie:y,accept:"application/json"},signal:AbortSignal.timeout(p)});if(!e.ok)return o(401===e.status?"invalid":"unavailable");v=await e.json()}catch{return o("unavailable")}const w=function(e,t){if("object"!=typeof e||null===e||Array.isArray(e))return null;const r=e.user;if("object"!=typeof r||null===r||Array.isArray(r))return null;const n=e.collection??r.collection;if(void 0!==n&&n!==t)return null;const o=r.id;return"string"==typeof o&&o.length>0?o:"number"==typeof o&&Number.isFinite(o)?String(o):null}(v,t.usersSlug??a);if(null===w)return o("invalid");const g=function(e){const t=e.exp;return"number"!=typeof t||!Number.isFinite(t)||t<=0?void 0:t<1e11?1e3*t:t}(v);return void 0!==g&&g<=d()?o("expired"):{authorized:!0,outcome:"authorized",context:r({strategy:"payload-session",subject:w,authorizedAt:d(),expiresAt:g,scope:{},payloadHeaders:{cookie:y}})}}(e,t);case"signed-token":return async function(e,t){const n=f(t.secret),a=h(t.crypto);if(void 0===a)return o("unavailable");const i=function(e,t){if("header"===t?.kind)return e.headers.get(t.name??"x-preview-token");let r;try{r=new URL(e.url)}catch{return null}const n=r.searchParams.getAll(t?.param??"previewToken");return 1===n.length?n[0]??null:null}(e,t.transport);if(null===i||0===i.length)return o("missing-credential");if(i.length>4096)return o("invalid");const s=i.split(".");if(3!==s.length||s[0]!==d)return o("invalid");const u=s[1]??"",c=w(u),v=w(s[2]??"");if(void 0===c||32!==v?.byteLength)return o("invalid");let g;try{const e=await y(a,n);g=await a.subtle.verify("HMAC",e,v,p().encode(`${d}.${u}`))}catch{return o("unavailable")}if(!g)return o("invalid");const b=function(e){let t;try{t=JSON.parse((new TextDecoder).decode(e))}catch{return}if("object"!=typeof t||null===t)return;const r=t,n=e=>void 0===r[e]||"string"==typeof r[e];return 1===r.v&&"string"==typeof r.aud&&"string"==typeof r.pur&&"number"==typeof r.iat&&"number"==typeof r.exp&&"string"==typeof r.jti&&n("pth")&&n("loc")&&n("sub")?r:void 0}(c);if(void 0===b)return o("invalid");const m=(t.now??Date.now)();if(b.exp<=m||b.iat>b.exp)return o("expired");if(b.aud!==t.audience)return o("wrong-audience");if(b.pur!==(t.purpose??l))return o("wrong-purpose");if(void 0!==b.pth){let t;try{t=new URL(e.url).pathname}catch{return o("invalid")}if(b.pth!==t)return o("wrong-path")}if(void 0!==b.loc){const r=t.locale?.(e);if(r!==b.loc)return o("wrong-locale")}if(void 0!==t.replay){let e;try{e=await async function(e,t,r){return"consume"in e?!0===await e.consume(t,r):!await e.isUsed(t)&&(await e.markUsed(t,r),!0)}(t.replay,b.jti,b.exp)}catch{return o("unavailable")}if(!e)return o("replayed")}return{authorized:!0,outcome:"authorized",context:r({strategy:"signed-token",subject:b.sub,authorizedAt:m,expiresAt:b.exp,scope:{audience:b.aud,...void 0!==b.pth?{path:b.pth}:{},...void 0!==b.loc?{locale:b.loc}:{}},payloadHeaders:{}})}}(e,t);case"verifier":return async function(e,t){let n;try{n=await t.verify(e)}catch{return o("unavailable")}if(null===n)return o("invalid");const a=(t.now??Date.now)();return void 0!==n.expiresAt&&n.expiresAt<=a?o("expired"):{authorized:!0,outcome:"authorized",context:r({strategy:"verifier",subject:n.subject,authorizedAt:a,expiresAt:n.expiresAt,scope:n.scope??{},payloadHeaders:n.payloadHeaders??{}})}}(e,t)}}var m=["preview","draft","livePreview"];function $(e,t={}){const r=new Set(t.signals??["query","fetch-dest","referer"]),n=t.queryParams??m;if(r.has("query")){let t;try{t=new URL(e.url)}catch{t=void 0}if(void 0!==t)for(const e of n){const r=t.searchParams.getAll(e).at(-1);if("true"===r||"1"===r)return!0}}if(r.has("fetch-dest")&&"iframe"===e.headers.get("sec-fetch-dest"))return!0;const o=t.allowedOrigins??t.adminOrigins??[],a=r.has("referer")?o:[];if(a.length>0){const t=e.headers.get("referer");if(null!==t)try{const e=new URL(t).origin;for(const t of a)try{if(new URL(t).origin===e)return!0}catch{}}catch{}}return!1}function bind(e,t){return A(e,t)}function bindByPath(e,t){const r=function(e){const t=[],r=new Proxy({},{get(e,n){if("symbol"!=typeof n)return/^\d+$/.test(n)||t.push(n),r}});try{e(r)}catch{}return t}(e);if(0===r.length)throw new Error("bindByPath: the picker did not read any field — return data.<field> instead of a constant");return A(r.join("."),t)}function A(e,t){if(0===e.length)throw new Error("bind: field name must be a non-empty string");const r={"data-payload-field":e};return void 0!==t?.attribute&&(r["data-payload-attribute"]=t.attribute),void 0!==t?.type&&(r["data-payload-type"]=t.type),!0===t?.richtext&&(r["data-payload-richtext"]=""),!0===t?.html&&(r["data-payload-html"]=""),void 0!==t?.locale&&(r["data-payload-locale"]=t.locale),void 0!==t?.alt&&(r["data-payload-alt"]=t.alt),void 0!==t?.href&&(r["data-payload-href"]=t.href),void 0!==t?.arrayTemplate&&(r["data-payload-array-template"]=t.arrayTemplate),r}var x=/^[a-z][a-z0-9-]{0,63}$/u,j=Object.freeze({});function createPreviewBindings(e){const t=isAuthorizedPreviewContext(e.authorization),r=void 0!==e.owner&&e.owner.length>0?e.owner:void 0;return Object.freeze({authorized:t,bind:(e,r)=>t?bind(e,r):j,bindByPath:(e,r)=>t?bindByPath(e,r):j,owner:()=>t&&void 0!==r?{"data-payload-owner":r}:j,boundary:(e,r={})=>{const n=function(e,t){if(!x.test(e))throw new RangeError(`createPreviewBindings().boundary(): "${e}" is not a registry id — lowercase, starting with a letter, then letters, digits or hyphens (max 64). The runtime would post it and the endpoint would refuse it.`);const r=t.key;if(void 0!==r&&(0===r.length||r.length>128))throw new RangeError(`createPreviewBindings().boundary(): the key for "${e}" must be 1 to ${String(128)} characters.`);const n=t.dependsOn??[];return{"data-payload-fragment":e,...void 0!==r?{"data-payload-fragment-key":r}:{},...n.length>0?{"data-payload-depends":n.join(",")}:{}}}(e,r);return t?n:j}})}function z(e,t={}){return createPreviewBindings({authorization:("object"==typeof e&&null!==e?e.livePreviewAuthorization:void 0)??null,...void 0!==t.owner?{owner:t.owner}:{}})}var k=class extends Error{reason;status;url;constructor(e,t,r,n){super(`payload-live-preview: preview read failed (${e}${void 0===r?"":` ${String(r)}`}) for ${t}`,{cause:n}),this.name="PreviewFetchError",this.reason=e,this.status=r,this.url=t}};function R(e){const t=function(e){let t;try{t=new URL(e)}catch{throw new Error(`payload-live-preview: serverURL "${e}" is not an absolute URL`)}if("https:"!==t.protocol&&"http:"!==t.protocol)throw new Error(`payload-live-preview: serverURL must be http(s), got "${t.protocol}"`);return`${t.origin}${t.pathname.replace(/\/+$/,"")}`}(e.serverURL),r=e.apiRoute??"/api",n=r.startsWith("/")?r:`/${r}`;if(!Number.isInteger(e.depth)||e.depth<0)throw new Error(`payload-live-preview: depth must be a non-negative integer, got ${String(e.depth)}`);const o=Math.max(250,e.timeoutMs??5e3),a=Object.freeze({serverURL:t,apiRoute:n,depth:e.depth,timeoutMs:o}),i=Object.freeze({serverURL:t,apiRoute:n,mergeDepth:e.depth}),s=`${t}${n}`,u={config:e,timeoutMs:o};return Object.freeze({config:a,runtimeOptions:i,async fetchDocument(t){const r=U(t),n=P(e.depth,t,r);n.set("limit","1"),void 0!==t.where&&O(n,t.where,["where"]);const o=`${s}/${encodeURIComponent(t.collection)}?${n.toString()}`,a=await S(u,o,t,r);if(!a.ok)return a;const i=a.data?.docs?.[0];return{ok:!0,data:i??null,draft:r,status:a.status}},async fetchGlobal(t){const r=U(t),n=P(e.depth,t,r),o=`${s}/globals/${encodeURIComponent(t.global)}?${n.toString()}`;return S(u,o,t,r)}})}function U(e){return isAuthorizedPreviewContext(e.authorization)}function P(e,t,r){const n=new URLSearchParams;return n.set("depth",String(e)),r&&n.set("draft","true"),void 0!==t.locale&&n.set("locale",t.locale),n}async function S({config:e,timeoutMs:t},r,n,o){const a=e.fetch??("function"==typeof fetch?fetch:void 0),i={config:e,url:r,draft:o,errorMode:n.errorMode??"result",startedAt:Date.now()};if(void 0===a)return L(i,{ok:!1,reason:"no-fetch",status:void 0,draft:o,cause:void 0});const s=AbortSignal.timeout(t),u=void 0===n.signal?s:AbortSignal.any([s,n.signal]),c=isAuthorizedPreviewContext(n.authorization)?n.authorization:null;let l,d;try{l=await a(r,{headers:{accept:"application/json",...n.headers??{},...c?.payloadHeaders??{}},signal:u,cache:"no-store",redirect:"error"})}catch(e){return L(i,{ok:!1,reason:M(e,n.signal),status:void 0,draft:o,cause:e})}if(!l.ok)return L(i,{ok:!1,reason:"http",status:l.status,draft:o,cause:void 0});try{d=await l.json()}catch(e){return L(i,{ok:!1,reason:"invalid-json",status:l.status,draft:o,cause:e})}return L(i,{ok:!0,data:d,draft:o,status:l.status})}function L(e,t){const{config:r,url:n,draft:o}=e,a=Date.now()-e.startedAt;if(t.ok)return r.onDiagnostic?.({kind:"response",url:n,status:t.status,draft:o,durationMs:a}),t;if(r.onDiagnostic?.({kind:"failure",url:n,reason:t.reason,status:t.status,draft:o,durationMs:a}),"throw"===e.errorMode)throw new k(t.reason,n,t.status,t.cause);return t}function M(e,t){if(!0===t?.aborted)return"aborted";const r="object"==typeof e&&null!==e?e.name:void 0;return"TimeoutError"===r?"timeout":"AbortError"===r?"aborted":"network"}function C(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function E(e){return e.map((e,t)=>0===t?e:`[${e}]`).join("")}function O(e,t,r){for(const[n,o]of Object.entries(t)){const t=[...r,n];C(o)?O(e,o,t):Array.isArray(o)&&o.some(C)?o.forEach((r,n)=>{O(e,r,[...t,String(n)])}):Array.isArray(o)?e.set(E(t),o.map(String).join(",")):e.set(E(t),"object"==typeof o?"null":String(o))}}export{e as AUTHORIZED_PREVIEW_BRAND_KEY,k as PreviewFetchError,b as authorizePreviewRequest,bind,bindByPath,createPreviewBindings,R as definePreview,u as extractCookie,$ as hasPreviewIntent,isAuthorizedPreviewContext,g as issuePreviewToken,z as previewBindingsFromLocals};
|
|
1
|
+
var e="payload-live-preview.authorized-preview-context",t=Symbol.for(e);function isAuthorizedPreviewContext(e){return"object"==typeof e&&null!==e&&!0===e[t]&&Object.isFrozen(e)}function r(r){return Object.freeze({...r,scope:Object.freeze({...r.scope,...void 0===r.scope.payload?{}:{payload:Object.freeze({...r.scope.payload,document:Object.freeze({...r.scope.payload.document})})}}),payloadHeaders:Object.freeze({...r.payloadHeaders}),[t]:!0,[e]:!0})}var n=class extends Error{name="PreviewConfigurationError"};function o(e){return{authorized:!1,outcome:e,context:null}}var i="users",a=/^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/,s=/^[A-Za-z0-9._~-]+$/;function c(e,t,r=4096){if(null===e||0===e.length)return null;const n=`${t}=`,o=[];for(const t of e.split(";")){const e=t.trim();e.startsWith(n)&&o.push(e.slice(n.length))}if(1!==o.length)return null;const i=o[0]??"";return 0===i.length||i.length>r||!s.test(i)?null:i}function u(e,t){let r;try{r=new URL(e)}catch{throw new n(`payload-live-preview: ${t} "${e}" is not an absolute URL`)}if("https:"!==r.protocol&&"http:"!==r.protocol)throw new n(`payload-live-preview: ${t} must be http(s), got "${r.protocol}"`);return r.origin}function l(e,t){if(!function(){try{const e=globalThis.process?.env;return void 0!==e&&"production"!==e.NODE_ENV}catch{return!1}}())return;const r=globalThis,n=r.__payloadLivePreviewDeprecationsWarned??=/* @__PURE__ */new Set;if(!n.has(e)){n.add(e);try{console.warn(`[payload-live-preview] ${t}`)}catch{}}}var d,p="live-preview",f="v1";function h(){return d??=new TextEncoder,d}function v(e){const t="string"==typeof e?h().encode(e):e;if(t.byteLength<32)throw new n(`payload-live-preview: the preview token secret must be at least ${String(32)} bytes`);return t}function y(e){if(void 0!==e)return e;const t=globalThis.crypto;return"object"==typeof t?.subtle?t:void 0}async function g(e,t){return e.subtle.importKey("raw",t,{name:"HMAC",hash:"SHA-256"},!1,["sign","verify"])}function w(e){let t="";for(const r of e)t+=String.fromCharCode(r);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function b(e){if(!/^[A-Za-z0-9_-]*$/.test(e))return;const t=e.replace(/-/g,"+").replace(/_/g,"/")+"=".repeat((4-e.length%4)%4);try{const e=atob(t),r=new Uint8Array(e.length);for(let t=0;t<e.length;t+=1)r[t]=e.charCodeAt(t);return r}catch{return}}async function m(e,t){const r=y(t.crypto);if(void 0===r)throw new Error("payload-live-preview: no Web Crypto implementation available to sign tokens");const n=v(t.secret),o=(t.now??Date.now)(),i=Math.min(36e5,Math.max(1,e.ttlMs??6e5)),a=new Uint8Array(16);r.getRandomValues(a);const s={v:1,aud:e.audience,...void 0!==e.path?{pth:e.path}:{},...void 0!==e.locale?{loc:e.locale}:{},...void 0!==e.subject?{sub:e.subject}:{},pur:e.purpose??p,iat:o,exp:o+i,jti:w(a)},c=w(h().encode(JSON.stringify(s))),u=`${f}.${c}`,l=await g(r,n);return`${u}.${w(new Uint8Array(await r.subtle.sign("HMAC",l,h().encode(u))))}`}function $(e){return"string"==typeof e&&e.length>0&&"."!==e&&".."!==e&&!/[\s\p{Cc}\p{Cs}/\\?#%]/u.test(e)}function A(e){return"number"==typeof e?Number.isSafeInteger(e):$(e)}function x(e,t="/api"){if("string"!=typeof e||"string"!=typeof t)return null;try{const r=new URL(e);if(!["http:","https:"].includes(r.protocol)||r.username||r.password||r.search||r.hash)return null;const n=e.replace(/^[a-z]+:\/\/[^/]+/iu,"").replace(/\/+$/,""),o=t.replace(/^\//,"").replace(/\/$/,"");return""!==n&&!n.slice(1).split("/").every($)||!o.split("/").every($)?null:`${r.origin}${r.pathname.replace(/\/+$/,"")}/${o}`}catch{return null}}function j(e){return void 0!==e.expiresAt&&Number.isFinite(e.expiresAt)&&e.expiresAt>Date.now()}function k(e,t){return e.kind===t.kind&&e.slug===t.slug&&("global"===e.kind||A(t.id)&&String(e.id)===String(t.id))}async function z(e,t){switch(t.type){case"payload-session":return async function(e,t){const s=function(e){const t=e??"payload-token";if(!a.test(t))throw new n(`payload-live-preview: cookie name "${t}" is not a valid cookie-name token`);return t}(t.cookieName),l=u(t.serverURL,"serverURL"),d=void 0===t.origin?void 0:u(t.origin,"origin"),p=c(e.headers.get("cookie"),s,t.maxCookieLength??4096);if(null===p)return o("missing-credential");const f=t.now??Date.now,h=Math.max(250,t.timeoutMs??3e3);if(!Number.isSafeInteger(h))return o("unavailable");const v=t.fetch??globalThis.fetch,y=encodeURIComponent(t.usersSlug??i),g=`${s}=${p}`;let w={cookie:g};void 0!==d&&(w.origin=d);const b=new AbortController,m=()=>{b.abort(e.signal?.reason)},$=()=>b.signal.aborted;let A,x;e.signal?.addEventListener("abort",m,{once:!0}),!0===e.signal?.aborted&&m();try{if($())return o("unavailable");A=setTimeout(()=>{b.abort(new DOMException("The operation timed out","TimeoutError"))},h);for(let e=0;e<2;e++){const t=await v(`${l}/api/${y}/me?depth=0`,{headers:{...w,accept:"application/json"},signal:b.signal});if($())return o("unavailable");if(!t.ok)return o(401===t.status?"invalid":"unavailable");if(x=await t.json(),$())return o("unavailable");if(0!==e||void 0!==d||"object"!=typeof x||null===x||null!==x.user)break;w={cookie:g,origin:l}}}catch{return o("unavailable")}finally{void 0!==A&&clearTimeout(A),e.signal?.removeEventListener("abort",m)}const j=function(e,t){if("object"!=typeof e||null===e||Array.isArray(e))return null;const r=e.user;if("object"!=typeof r||null===r||Array.isArray(r))return null;const n=e.collection??r.collection;if(void 0!==n&&n!==t)return null;const o=r.id;return"string"==typeof o&&o.length>0?o:"number"==typeof o&&Number.isFinite(o)?String(o):null}(x,t.usersSlug??i);if(null===j)return o("invalid");const k=function(e){const t=e.exp;return"number"!=typeof t||!Number.isFinite(t)||t<=0?void 0:t<1e11?1e3*t:t}(x);return void 0!==k&&k<=f()?o("expired"):{authorized:!0,outcome:"authorized",context:r({strategy:"payload-session",subject:j,authorizedAt:f(),expiresAt:k,scope:{},payloadHeaders:w})}}(e,t);case"signed-token":return async function(e,t){const n=v(t.secret),i=y(t.crypto);if(void 0===i)return o("unavailable");const a=function(e,t){if("header"===t?.kind)return e.headers.get(t.name??"x-preview-token");let r;try{r=new URL(e.url)}catch{return null}const n=r.searchParams.getAll(t?.param??"previewToken");return 1===n.length?n[0]??null:null}(e,t.transport);if(null===a||0===a.length)return o("missing-credential");if(a.length>4096)return o("invalid");const s=a.split(".");if(3!==s.length||s[0]!==f)return o("invalid");const c=s[1]??"",u=b(c),d=b(s[2]??"");if(void 0===u||32!==d?.byteLength)return o("invalid");let w;try{const e=await g(i,n);w=await i.subtle.verify("HMAC",e,d,h().encode(`${f}.${c}`))}catch{return o("unavailable")}if(!w)return o("invalid");const m=function(e){let t;try{t=JSON.parse((new TextDecoder).decode(e))}catch{return}if("object"!=typeof t||null===t)return;const r=t,n=e=>void 0===r[e]||"string"==typeof r[e];return 1===r.v&&"string"==typeof r.aud&&"string"==typeof r.pur&&"number"==typeof r.iat&&"number"==typeof r.exp&&"string"==typeof r.jti&&n("pth")&&n("loc")&&n("sub")?r:void 0}(u);if(void 0===m)return o("invalid");const $=(t.now??Date.now)();if(m.exp<=$||m.iat>m.exp)return o("expired");if(m.aud!==t.audience)return o("wrong-audience");if(m.pur!==(t.purpose??p))return o("wrong-purpose");if(void 0!==m.pth){let t;try{t=new URL(e.url).pathname}catch{return o("invalid")}if(m.pth!==t)return o("wrong-path")}if(void 0!==m.loc){const r=t.locale?.(e);if(r!==m.loc)return o("wrong-locale")}if(void 0!==t.replay){let e;try{e=await async function(e,t,r){return"consume"in e?!0===await e.consume(t,r):(l("replay-is-used","A `signed-token` replay store with `isUsed` and `markUsed` is removed in 3.0: implement `consume(id, expiresAt)`, which checks and records in one step (ADR 0026)."),!await e.isUsed(t)&&(await e.markUsed(t,r),!0))}(t.replay,m.jti,m.exp)}catch{return o("unavailable")}if(!e)return o("replayed")}return{authorized:!0,outcome:"authorized",context:r({strategy:"signed-token",subject:m.sub,authorizedAt:$,expiresAt:m.exp,scope:{audience:m.aud,...void 0!==m.pth?{path:m.pth}:{},...void 0!==m.loc?{locale:m.loc}:{}},payloadHeaders:{}})}}(e,t);case"verifier":return async function(e,t){let n;try{n=await t.verify(e)}catch{return o("unavailable")}if(null===n)return o("invalid");if(!(void 0===n.scope?.payload||function(e){if("object"!=typeof e||null===e)return!1;const t=e,r=t.document;return!(null===x(t.serverURL,t.apiRoute)||"number"!=typeof t.maxDepth||!Number.isSafeInteger(t.maxDepth)||t.maxDepth<0||"object"!=typeof r||null===r||!$(r.slug))&&("global"===r.kind||"collection"===r.kind&&A(r.id))}(n.scope.payload)&&Number.isFinite(n.expiresAt)))return o("invalid");const i=(t.now??Date.now)();return void 0!==n.expiresAt&&n.expiresAt<=i?o("expired"):{authorized:!0,outcome:"authorized",context:r({strategy:"verifier",subject:n.subject,authorizedAt:i,expiresAt:n.expiresAt,scope:n.scope??{},payloadHeaders:n.payloadHeaders??{}})}}(e,t)}}var R=["preview","draft","livePreview"];function U(e,t={}){const r=new Set(t.signals??["query","fetch-dest","referer"]),n=t.queryParams??R;if(r.has("query")){let t;try{t=new URL(e.url)}catch{t=void 0}if(void 0!==t)for(const e of n){const r=t.searchParams.getAll(e).at(-1);if("true"===r||"1"===r)return!0}}if(r.has("fetch-dest")&&"iframe"===e.headers.get("sec-fetch-dest"))return!0;const o=t.adminOrigins;void 0!==o&&l("admin-origins","`hasPreviewIntent({ adminOrigins })` is removed in 3.0: name the option `allowedOrigins`; `pll migrate` renames it (ADR 0026).");const i=t.allowedOrigins??o??[],a=r.has("referer")?i:[];if(a.length>0){const t=e.headers.get("referer");if(null!==t)try{const e=new URL(t).origin;for(const t of a)try{if(new URL(t).origin===e)return!0}catch{}}catch{}}return!1}function bind(e,t){return O(e,t)}function bindByPath(e,t){const r=function(e){const t=[],r=new Proxy({},{get(e,n){if("symbol"!=typeof n)return/^\d+$/.test(n)||t.push(n),r}});try{e(r)}catch{}return t}(e);if(0===r.length)throw new Error("bindByPath: the picker did not read any field — return data.<field> instead of a constant");return O(r.join("."),t)}function O(e,t){if(0===e.length)throw new Error("bind: field name must be a non-empty string");const r={"data-payload-field":e};return void 0!==t?.attribute&&(r["data-payload-attribute"]=t.attribute),void 0!==t?.type&&(r["data-payload-type"]=t.type),!0===t?.richtext&&(r["data-payload-richtext"]=""),!0===t?.html&&(r["data-payload-html"]=""),void 0!==t?.locale&&(r["data-payload-locale"]=t.locale),void 0!==t?.alt&&(r["data-payload-alt"]=t.alt),void 0!==t?.href&&(r["data-payload-href"]=t.href),void 0!==t?.arrayTemplate&&(r["data-payload-array-template"]=t.arrayTemplate),r}var S=/^[a-z][a-z0-9-]{0,63}$/u,P=Object.freeze({});function createPreviewBindings(e){const t=isAuthorizedPreviewContext(e.authorization),r=void 0!==e.owner&&e.owner.length>0?e.owner:void 0;return Object.freeze({authorized:t,bind:(e,r)=>t?bind(e,r):P,bindByPath:(e,r)=>t?bindByPath(e,r):P,owner:()=>t&&void 0!==r?{"data-payload-owner":r}:P,boundary:(e,r={})=>{const n=function(e,t){if(!S.test(e))throw new RangeError(`createPreviewBindings().boundary(): "${e}" is not a registry id — lowercase, starting with a letter, then letters, digits or hyphens (max 64). The runtime would post it and the endpoint would refuse it.`);const r=t.key;if(void 0!==r&&(0===r.length||r.length>128))throw new RangeError(`createPreviewBindings().boundary(): the key for "${e}" must be 1 to ${String(128)} characters.`);const n=t.dependsOn??[];return{"data-payload-fragment":e,...void 0!==r?{"data-payload-fragment-key":r}:{},...n.length>0?{"data-payload-depends":n.join(",")}:{}}}(e,r);return t?n:P},covers:(...e)=>{const r=function(e){for(const t of e)if(0===t.length||/[\s,]/u.test(t))throw new RangeError(`createPreviewBindings().covers(): "${t}" is not a field path — a name or a dotted path without spaces or commas.`);return{"data-payload-covers":e.join(" ")}}(e);return t&&e.length>0?r:P}})}function L(e,t={}){return createPreviewBindings({authorization:("object"==typeof e&&null!==e?e.livePreviewAuthorization:void 0)??null,...void 0!==t.owner?{owner:t.owner}:{}})}var E=class extends Error{reason;status;url;constructor(e,t,r,n){super(`payload-live-preview: preview read failed (${e}${void 0===r?"":` ${String(r)}`}) for ${t}`,{cause:n}),this.name="PreviewFetchError",this.reason=e,this.status=r,this.url=t}};function C(e){const t=function(e){let t;try{t=new URL(e)}catch{throw new Error(`payload-live-preview: serverURL "${e}" is not an absolute URL`)}if("https:"!==t.protocol&&"http:"!==t.protocol)throw new Error(`payload-live-preview: serverURL must be http(s), got "${t.protocol}"`);return`${t.origin}${t.pathname.replace(/\/+$/,"")}`}(e.serverURL),r=e.apiRoute??"/api",n=r.startsWith("/")?r:`/${r}`;if(!Number.isInteger(e.depth)||e.depth<0)throw new Error(`payload-live-preview: depth must be a non-negative integer, got ${String(e.depth)}`);const o=Math.max(250,e.timeoutMs??5e3),i=Object.freeze({serverURL:t,apiRoute:n,depth:e.depth,timeoutMs:o}),a=Object.freeze({serverURL:t,apiRoute:n,mergeDepth:e.depth}),s=`${t}${n}`,c={config:Object.freeze({...e,...i}),timeoutMs:o};return Object.freeze({config:i,runtimeOptions:a,async fetchDocument(e){const t=T(e),r=void 0!==e.id,n=(!r||A(e.id)&&void 0===e.where)&&D(c.config,e,{kind:"collection",slug:e.collection,id:e.id});if(!n)return H(c,s,e,t,!1);const o=N(i.depth,e,t);r||(o.set("limit","1"),void 0!==e.where&&W(o,e.where,["where"]));const a=A(e.id)?`/${encodeURIComponent(String(e.id))}`:"",u=`${s}/${encodeURIComponent(e.collection)}${a}?${o.toString()}`;if(r)return H(c,u,e,t,n);const l=await H(c,u,e,t,n);if(!l.ok)return l;const d=l.data?.docs?.[0];return{ok:!0,data:d??null,draft:t,status:l.status}},async fetchGlobal(e){const t=T(e),r=N(i.depth,e,t),n=`${s}/globals/${encodeURIComponent(e.global)}?${r.toString()}`;return H(c,n,e,t,D(c.config,e,{kind:"global",slug:e.global}))}})}function D(e,t,r){const n=isAuthorizedPreviewContext(t.authorization)?t.authorization:null,o=n?.scope.payload;return void 0===o||null===n||j(n)&&(void 0===n.scope.locale||n.scope.locale===t.locale)&&x(e.serverURL,e.apiRoute)===x(o.serverURL,o.apiRoute)&&e.depth<=o.maxDepth&&k(o.document,r)}function M(e){return B(e,{ok:!1,reason:"scope",status:void 0,draft:e.draft,cause:void 0})}function T(e){return isAuthorizedPreviewContext(e.authorization)}function N(e,t,r){const n=new URLSearchParams;return n.set("depth",String(e)),r&&n.set("draft","true"),void 0!==t.locale&&n.set("locale",t.locale),n}async function H({config:e,timeoutMs:t},r,n,o,i){const a=e.fetch??("function"==typeof fetch?fetch:void 0),s={config:e,url:r,draft:o,errorMode:n.errorMode??"result",startedAt:Date.now()};if(!i)return M(s);if(void 0===a)return B(s,{ok:!1,reason:"no-fetch",status:void 0,draft:o,cause:void 0});const c=AbortSignal.timeout(t),u=void 0===n.signal?c:AbortSignal.any([c,n.signal]),l=isAuthorizedPreviewContext(n.authorization)?n.authorization:null;let d,p;try{const e=new Headers({accept:"application/json",...n.headers});for(const[t,r]of Object.entries(l?.payloadHeaders??{}))e.set(t,r);d=await a(r,{headers:Object.fromEntries(e),signal:u,cache:"no-store",redirect:"error"})}catch(e){return B(s,{ok:!1,reason:I(e,n.signal),status:void 0,draft:o,cause:e})}if(void 0!==l?.scope.payload&&!j(l))return M(s);if(!d.ok)return B(s,{ok:!1,reason:"http",status:d.status,draft:o,cause:void 0});try{p=await d.json()}catch(e){return B(s,{ok:!1,reason:"invalid-json",status:d.status,draft:o,cause:e})}return void 0!==l?.scope.payload&&!j(l)||void 0!==l?.scope.payload&&!function(e,t){if("object"!=typeof t||null===t||Array.isArray(t))return!1;const r=t;return"global"===e.kind?!Object.hasOwn(r,"globalType")||r.globalType===e.slug:Object.hasOwn(r,"id")&&k(e,{kind:"collection",slug:e.slug,id:r.id})}(l.scope.payload.document,p)?M(s):B(s,{ok:!0,data:p,draft:o,status:d.status})}function B(e,t){const{config:r,url:n,draft:o}=e,i=Date.now()-e.startedAt;if(t.ok)return r.onDiagnostic?.({kind:"response",url:n,status:t.status,draft:o,durationMs:i}),t;if(r.onDiagnostic?.({kind:"failure",url:n,reason:t.reason,status:t.status,draft:o,durationMs:i}),"throw"===e.errorMode)throw new E(t.reason,n,t.status,t.cause);return t}function I(e,t){if(!0===t?.aborted)return"aborted";const r="object"==typeof e&&null!==e?e.name:void 0;return"TimeoutError"===r?"timeout":"AbortError"===r?"aborted":"network"}function _(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function F(e){return e.map((e,t)=>0===t?e:`[${e}]`).join("")}function W(e,t,r){for(const[n,o]of Object.entries(t)){const t=[...r,n];_(o)?W(e,o,t):Array.isArray(o)&&o.some(_)?o.forEach((r,n)=>{W(e,r,[...t,String(n)])}):Array.isArray(o)?e.set(F(t),o.map(String).join(",")):e.set(F(t),"object"==typeof o?"null":String(o))}}export{e as AUTHORIZED_PREVIEW_BRAND_KEY,E as PreviewFetchError,z as authorizePreviewRequest,bind,bindByPath,createPreviewBindings,C as definePreview,c as extractCookie,U as hasPreviewIntent,isAuthorizedPreviewContext,m as issuePreviewToken,L as previewBindingsFromLocals};
|
|
2
2
|
//# sourceMappingURL=server.js.map
|