payload-live-preview 2.0.2 → 2.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 +13 -8
- package/dist/adapters/astro/index.js +1 -1
- package/dist/adapters/astro/index.js.map +1 -1
- package/dist/adapters/astro/middleware-entry.js +1 -1
- package/dist/adapters/astro/middleware-entry.js.map +1 -1
- package/dist/adapters/nextjs/index.js +1 -1
- package/dist/adapters/nextjs/index.js.map +1 -1
- package/dist/adapters/nuxt/index.js +1 -1
- package/dist/adapters/nuxt/index.js.map +1 -1
- package/dist/adapters/sveltekit/index.js +1 -1
- package/dist/adapters/sveltekit/index.js.map +1 -1
- package/dist/client.cjs +1 -1
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +10 -3
- package/dist/client.d.ts +10 -3
- package/dist/client.js +1 -1
- package/dist/client.js.map +1 -1
- package/dist/codegen-astro.js.map +1 -1
- package/dist/codegen-cli.js.map +1 -1
- package/dist/codegen.cjs +1 -1
- package/dist/codegen.cjs.map +1 -1
- package/dist/codegen.d.cts +1 -16
- package/dist/codegen.d.ts +1 -16
- package/dist/codegen.js +1 -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 +13 -5
- package/dist/core.d.ts +13 -5
- 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/fragment.cjs +1 -1
- package/dist/fragment.cjs.map +1 -1
- package/dist/fragment.d.cts +5 -1
- package/dist/fragment.d.ts +5 -1
- 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 +13 -5
- package/dist/index.d.ts +13 -5
- 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.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.js +1 -1
- package/dist/lexical.js.map +1 -1
- package/dist/plugins.cjs +1 -1
- package/dist/plugins.cjs.map +1 -1
- package/dist/plugins.d.cts +5 -1
- package/dist/plugins.d.ts +5 -1
- package/dist/plugins.js +1 -1
- package/dist/plugins.js.map +1 -1
- package/dist/{probe-DqaItcwX.d.ts → probe-Bzm4fYFL.d.ts} +11 -1
- package/dist/server.cjs +1 -1
- package/dist/server.cjs.map +1 -1
- 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.js +2 -2
- package/dist/structural.js.map +1 -1
- package/package.json +1 -1
|
@@ -27,10 +27,12 @@ declare const DIAGNOSTIC_CODES: Readonly<{
|
|
|
27
27
|
readonly PluginIncompatible: "LP0103";
|
|
28
28
|
/** The page carries the lean runtime and needs a feature that profile leaves out. */
|
|
29
29
|
readonly ProfileFeatureOmitted: "LP0104";
|
|
30
|
-
/** An update named
|
|
30
|
+
/** An update named an empty field with no binding anchor on the page; an edit to it has nowhere to land. */
|
|
31
31
|
readonly OrphanField: "LP0201";
|
|
32
32
|
/** Owner scoping is on and the update names no document it could belong to. */
|
|
33
33
|
readonly UnattributableUpdate: "LP0202";
|
|
34
|
+
/** An update carried a value for a field the page binds nowhere; editing it changes nothing on this page. */
|
|
35
|
+
readonly UnboundField: "LP0203";
|
|
34
36
|
/** The visibility gate held offscreen writes back until they scroll into view. */
|
|
35
37
|
readonly VisibilityGateDeferred: "LP0301";
|
|
36
38
|
/** A value was refused because the attribute or the value itself is unsafe. */
|
|
@@ -107,6 +109,8 @@ declare const DIAGNOSTIC_CODES: Readonly<{
|
|
|
107
109
|
readonly FragmentStrategyUnavailable: "LP0806";
|
|
108
110
|
/** A revision changed a field with no binding; `onUnfaithfulPatch` refreshed the route. */
|
|
109
111
|
readonly UnboundChangeRefresh: "LP0807";
|
|
112
|
+
/** `onUnfaithfulPatch: 'escalate'` found no route or fragment strategy to hand an unfaithful patch to; the page keeps it. */
|
|
113
|
+
readonly EscalationUnavailable: "LP0808";
|
|
110
114
|
/** A readiness row is not yet at its 2.0 value; `pll doctor --v2` reports it. */
|
|
111
115
|
readonly V2ReadinessGap: "LP0709";
|
|
112
116
|
/** The preview runtime is served to anonymous visitors, not only inside the admin frame. */
|
|
@@ -184,6 +188,12 @@ interface RunDoctorOptions {
|
|
|
184
188
|
* preview probe appends as `=true`; none of them reaches the visitor probe.
|
|
185
189
|
*/
|
|
186
190
|
readonly previewQueryParams?: readonly string[] | undefined;
|
|
191
|
+
/**
|
|
192
|
+
* The query parameter the `signed-token` strategy reads, when its transport
|
|
193
|
+
* names one other than `previewToken`. Treated like `previewToken`: dropped
|
|
194
|
+
* from the visitor probe, counted as the credential, its value never shown.
|
|
195
|
+
*/
|
|
196
|
+
readonly tokenQueryParam?: string | undefined;
|
|
187
197
|
}
|
|
188
198
|
/** Header names lowercased; every check downstream reads a lowercase key. @internal */
|
|
189
199
|
declare function lowercaseHeaders(headers: Headers): Record<string, string>;
|
package/dist/server.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";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}}var g=["preview","draft","livePreview"];function bind(e,t){return b(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 b(r.join("."),t)}function b(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 m=/^[a-z][a-z0-9-]{0,63}$/u,x=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):x,bindByPath:(e,r)=>t?bindByPath(e,r):x,owner:()=>t&&void 0!==r?{"data-payload-owner":r}:x,boundary:(e,r={})=>{const n=function(e,t){if(!m.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:x}})}var A=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 $(e){return isAuthorizedPreviewContext(e.authorization)}function z(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 j({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 k(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 k(i,{ok:!1,reason:P(e,n.signal),status:void 0,draft:o,cause:e})}if(!l.ok)return k(i,{ok:!1,reason:"http",status:l.status,draft:o,cause:void 0});try{d=await l.json()}catch(e){return k(i,{ok:!1,reason:"invalid-json",status:l.status,draft:o,cause:e})}return k(i,{ok:!0,data:d,draft:o,status:l.status})}function k(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 A(t.reason,n,t.status,t.cause);return t}function P(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 R(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function U(e){return e.map((e,t)=>0===t?e:`[${e}]`).join("")}function S(e,t,r){for(const[n,o]of Object.entries(t)){const t=[...r,n];R(o)?S(e,o,t):Array.isArray(o)&&o.some(R)?o.forEach((r,n)=>{S(e,r,[...t,String(n)])}):Array.isArray(o)?e.set(U(t),o.map(String).join(",")):e.set(U(t),"object"==typeof o?"null":String(o))}}exports.AUTHORIZED_PREVIEW_BRAND_KEY=e,exports.PreviewFetchError=A,exports.authorizePreviewRequest=async function(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)}},exports.bind=bind,exports.bindByPath=bindByPath,exports.createPreviewBindings=createPreviewBindings,exports.definePreview=function(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=$(t),n=z(e.depth,t,r);n.set("limit","1"),void 0!==t.where&&S(n,t.where,["where"]);const o=`${s}/${encodeURIComponent(t.collection)}?${n.toString()}`,a=await j(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=$(t),n=z(e.depth,t,r),o=`${s}/globals/${encodeURIComponent(t.global)}?${n.toString()}`;return j(u,o,t,r)}})},exports.extractCookie=u,exports.hasPreviewIntent=function(e,t={}){const r=new Set(t.signals??["query","fetch-dest","referer"]),n=t.queryParams??g;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.get(e);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},exports.isAuthorizedPreviewContext=isAuthorizedPreviewContext,exports.issuePreviewToken=async function(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))))}`},exports.previewBindingsFromLocals=function(e,t={}){return createPreviewBindings({authorization:("object"==typeof e&&null!==e?e.livePreviewAuthorization:void 0)??null,...void 0!==t.owner?{owner:t.owner}:{}})};
|
|
1
|
+
"use strict";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}}var g=["preview","draft","livePreview"];function bind(e,t){return b(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 b(r.join("."),t)}function b(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 m=/^[a-z][a-z0-9-]{0,63}$/u,x=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):x,bindByPath:(e,r)=>t?bindByPath(e,r):x,owner:()=>t&&void 0!==r?{"data-payload-owner":r}:x,boundary:(e,r={})=>{const n=function(e,t){if(!m.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:x}})}var A=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 $(e){return isAuthorizedPreviewContext(e.authorization)}function z(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 j({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 k(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 k(i,{ok:!1,reason:P(e,n.signal),status:void 0,draft:o,cause:e})}if(!l.ok)return k(i,{ok:!1,reason:"http",status:l.status,draft:o,cause:void 0});try{d=await l.json()}catch(e){return k(i,{ok:!1,reason:"invalid-json",status:l.status,draft:o,cause:e})}return k(i,{ok:!0,data:d,draft:o,status:l.status})}function k(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 A(t.reason,n,t.status,t.cause);return t}function P(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 R(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function U(e){return e.map((e,t)=>0===t?e:`[${e}]`).join("")}function S(e,t,r){for(const[n,o]of Object.entries(t)){const t=[...r,n];R(o)?S(e,o,t):Array.isArray(o)&&o.some(R)?o.forEach((r,n)=>{S(e,r,[...t,String(n)])}):Array.isArray(o)?e.set(U(t),o.map(String).join(",")):e.set(U(t),"object"==typeof o?"null":String(o))}}exports.AUTHORIZED_PREVIEW_BRAND_KEY=e,exports.PreviewFetchError=A,exports.authorizePreviewRequest=async function(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)}},exports.bind=bind,exports.bindByPath=bindByPath,exports.createPreviewBindings=createPreviewBindings,exports.definePreview=function(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=$(t),n=z(e.depth,t,r);n.set("limit","1"),void 0!==t.where&&S(n,t.where,["where"]);const o=`${s}/${encodeURIComponent(t.collection)}?${n.toString()}`,a=await j(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=$(t),n=z(e.depth,t,r),o=`${s}/globals/${encodeURIComponent(t.global)}?${n.toString()}`;return j(u,o,t,r)}})},exports.extractCookie=u,exports.hasPreviewIntent=function(e,t={}){const r=new Set(t.signals??["query","fetch-dest","referer"]),n=t.queryParams??g;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},exports.isAuthorizedPreviewContext=isAuthorizedPreviewContext,exports.issuePreviewToken=async function(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))))}`},exports.previewBindingsFromLocals=function(e,t={}){return createPreviewBindings({authorization:("object"==typeof e&&null!==e?e.livePreviewAuthorization:void 0)??null,...void 0!==t.owner?{owner:t.owner}:{}})};
|
|
2
2
|
//# sourceMappingURL=server.cjs.map
|
package/dist/server.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.cjs","names":["AUTHORIZED_PREVIEW_BRAND_KEY","BRAND","Symbol","for","isAuthorizedPreviewContext","value","Object","isFrozen","createAuthorizedPreviewContext","fields","freeze","scope","payloadHeaders","PreviewConfigurationError","Error","name","refused","outcome","authorized","context","DEFAULT_USERS_SLUG","COOKIE_NAME_PATTERN","COOKIE_VALUE_PATTERN","extractCookie","header","maxLength","length","prefix","values","part","split","trimmed","trim","startsWith","push","slice","test","cachedEncoder","DEFAULT_PURPOSE","TOKEN_VERSION","encoder","TextEncoder","secretBytes","secret","bytes","encode","byteLength","String","cryptoFor","configured","candidate","globalThis","crypto","subtle","async","hmacKey","importKey","hash","toBase64Url","binary","byte","fromCharCode","btoa","replace","fromBase64Url","text","padded","repeat","atob","Uint8Array","index","charCodeAt","DEFAULT_QUERY_PARAMS","bind","field","options","buildAttributes","bindByPath","picker","path","proxy","Proxy","get","_target","prop","recordPath","join","attrs","attribute","type","richtext","html","locale","alt","href","arrayTemplate","REGISTRY_ID","SUPPRESSED","createPreviewBindings","authorization","owner","bindOptions","boundary","id","boundaryOptions","attributes","RangeError","key","dependsOn","boundaryAttributes","PreviewFetchError","reason","status","url","constructor","cause","super","this","draftFor","params","depth","draft","query","URLSearchParams","set","read","config","timeoutMs","fetchImpl","fetch","attempt","errorMode","startedAt","Date","now","settle","ok","timeout","AbortSignal","signal","any","response","body","headers","accept","cache","redirect","failureReason","json","data","result","durationMs","onDiagnostic","kind","external","aborted","isWhereTree","Array","isArray","queryName","map","segment","appendWhere","node","entries","nextPath","some","forEach","branch","request","strategy","cookieName","cookieNameFor","origin","serverURL","URL","protocol","serverOriginFor","maxCookieLength","Math","max","usersSlug","encodeURIComponent","forwarded","cookie","user","collection","Number","isFinite","readUser","expiresAt","exp","readExpiry","subject","authorizedAt","authorizeSession","token","transport","searchParams","getAll","param","readToken","parts","payloadText","payloadBytes","signature","verified","verify","claims","parsed","JSON","parse","TextDecoder","decode","c","optionalString","parseClaims","iat","aud","audience","pur","purpose","pth","pathname","loc","replay","first","store","consume","isUsed","markUsed","consumeTokenId","jti","sub","authorizeToken","authorizeVerifier","normalizeServerURL","route","apiRoute","isInteger","resolved","runtimeOptions","mergeDepth","base","settings","fetchDocument","where","toString","docs","fetchGlobal","global","signals","Set","queryParams","has","allowedOrigins","adminOrigins","referer","refererOrigin","adminOrigin","ttl","min","ttlMs","getRandomValues","wire","v","payload","stringify","signed","sign","locals"],"sources":["../src/types/authorized-preview.ts","../src/security/preview-verdict.ts","../src/security/preview-session.ts","../src/security/preview-token.ts","../src/adapters/shared/preview-request.ts","../src/dsl/bind.ts","../src/dsl/preview-bindings.ts","../src/server/preview.ts","../src/security/preview-authorization.ts","../src/adapters/shared/locals.ts"],"sourcesContent":["/**\n * The authorized preview context's shape and brand, in the leaf `types` domain\n * so producer and consumers recognise the same context. See ADR 0006.\n */\n\n/**\n * The brand key, in the type and on the object.\n *\n * This was a `unique symbol` until 2026-08-30. A `unique symbol` is nominal per\n * *declaration*, and the published entries each carry their own declaration\n * file — so `AuthorizedPreviewContext` from the root barrel was a different\n * type from the one in `./server` or in an adapter, and the documented flow\n * (authorize once, hand the context to the adapter hook and to the draft read)\n * did not type-check in any combination a consumer can write.\n *\n * That is the same trap the runtime brand below already avoids by using\n * `Symbol.for` rather than `Symbol()`; the lesson simply had not reached the\n * type. A shared string literal is identical in every bundle for the same\n * reason a registry symbol is.\n *\n * It is not what makes a context trustworthy. `isAuthorizedPreviewContext`\n * decides that, on the registry symbol and on `Object.isFrozen`, and every\n * consumption point calls it — the adapter policy, the draft read and the\n * binding emitter. Writing this key by hand buys nothing.\n */\nexport const AUTHORIZED_PREVIEW_BRAND_KEY = 'payload-live-preview.authorized-preview-context';\n\nexport type PreviewAuthorizationStrategyName = 'payload-session' | 'signed-token' | 'verifier';\n\n/** What a context is bound to; every field is optional because every strategy binds differently. */\nexport interface AuthorizedPreviewScope {\n /** The site origin the credential was issued for. */\n readonly audience?: string;\n /** The request pathname the credential is valid for. */\n readonly path?: string;\n readonly locale?: string;\n}\n\n/** The verdict of a successful `authorizePreviewRequest()`: frozen, branded, produced only there. Carry it, do not rebuild it. */\nexport interface AuthorizedPreviewContext {\n readonly [AUTHORIZED_PREVIEW_BRAND_KEY]: true;\n readonly strategy: PreviewAuthorizationStrategyName;\n /** The user id, token subject, or whatever the verifier named. */\n readonly subject: string | undefined;\n /** Unix milliseconds. */\n readonly authorizedAt: number;\n /** Unix milliseconds, or `undefined` when the strategy does not know. */\n readonly expiresAt: number | undefined;\n readonly scope: AuthorizedPreviewScope;\n /** The minimal request material a draft read forwards to Payload — one verified cookie, never the whole `Cookie` header. */\n readonly payloadHeaders: Readonly<Record<string, string>>;\n}\n\n// A registry symbol: entries are separate bundles, and a per-bundle `Symbol()`\n// would refuse the other's contexts in the package while unit tests passed.\nconst BRAND = Symbol.for(AUTHORIZED_PREVIEW_BRAND_KEY);\n\n/** Whether `value` was produced by `authorizePreviewRequest()`; a copy or JSON round trip is not. */\nexport function isAuthorizedPreviewContext(value: unknown): value is AuthorizedPreviewContext {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as Record<PropertyKey, unknown>)[BRAND] === true &&\n Object.isFrozen(value)\n );\n}\n\n/** Internal: the one place a context is made. */\nexport function createAuthorizedPreviewContext(\n fields: Omit<AuthorizedPreviewContext, typeof AUTHORIZED_PREVIEW_BRAND_KEY>,\n): AuthorizedPreviewContext {\n const context = Object.freeze({\n ...fields,\n scope: Object.freeze({ ...fields.scope }),\n payloadHeaders: Object.freeze({ ...fields.payloadHeaders }),\n [BRAND]: true,\n [AUTHORIZED_PREVIEW_BRAND_KEY]: true,\n });\n // No cast: the frozen object carries the brand key the interface declares.\n return context;\n}\n","/**\n * The verdict shape every authorization strategy produces, and the one error\n * class that is allowed to escape it. Kept apart from the strategies so the\n * adapters can import the error without pulling in HMAC or session code.\n */\n\nimport type { AuthorizedPreviewContext } from '@/types/authorized-preview';\n\n/** Why a request was refused. `'authorized'` is the one non-refusal. */\nexport type PreviewAuthorizationOutcome =\n | 'authorized'\n | 'missing-credential'\n | 'invalid'\n | 'expired'\n | 'wrong-audience'\n | 'wrong-path'\n | 'wrong-locale'\n | 'wrong-purpose'\n | 'replayed'\n | 'unavailable';\n\n/** The result of `authorizePreviewRequest()`: a verdict, never an exception. */\nexport type PreviewAuthorization =\n | {\n readonly authorized: true;\n readonly outcome: 'authorized';\n readonly context: AuthorizedPreviewContext;\n }\n | {\n readonly authorized: false;\n readonly outcome: Exclude<PreviewAuthorizationOutcome, 'authorized'>;\n readonly context: null;\n };\n\n/** The request shape every strategy reads: a URL and a header getter. */\nexport interface PreviewAuthorizationRequest {\n readonly url: string;\n readonly headers: { get(name: string): string | null };\n}\n\n/**\n * A strategy was misconfigured — raised on first use and re-thrown by the\n * adapters rather than reported as `'unavailable'`, so it fails loudly.\n */\nexport class PreviewConfigurationError extends Error {\n override readonly name = 'PreviewConfigurationError';\n}\n\n/** Recognises the error across bundles, where `instanceof` sees a different class copy. */\nexport function isPreviewConfigurationError(error: unknown): error is PreviewConfigurationError {\n return (\n error instanceof PreviewConfigurationError ||\n (error instanceof Error && error.name === 'PreviewConfigurationError')\n );\n}\n\nexport function refused(\n outcome: Exclude<PreviewAuthorizationOutcome, 'authorized'>,\n): PreviewAuthorization {\n return { authorized: false, outcome, context: null };\n}\n","/**\n * The `payload-session` strategy: forward exactly one named cookie to\n * `GET <serverURL>/api/<usersSlug>/me` and authorize when a user comes back.\n */\n\nimport { createAuthorizedPreviewContext } from '@/types/authorized-preview';\nimport {\n PreviewConfigurationError,\n refused,\n type PreviewAuthorization,\n type PreviewAuthorizationRequest,\n} from './preview-verdict';\n\n/** A `fetch`-compatible function, injectable for tests. */\nexport type FetchLike = (\n input: string,\n init: { readonly headers: Record<string, string>; readonly signal: AbortSignal },\n) => Promise<{ readonly ok: boolean; readonly status: number; json(): Promise<unknown> }>;\n\nexport interface PayloadSessionStrategy {\n readonly type: 'payload-session';\n /** Payload server origin, e.g. `https://cms.example.com`. */\n readonly serverURL: string;\n /** Auth collection slug; `users` unless the project renamed it. */\n readonly usersSlug?: string;\n /** Cookie name; Payload's default is `payload-token`. */\n readonly cookieName?: string;\n /** Upper bound for the `/me` round trip. Below 250 ms is raised to 250 ms; default 3000. */\n readonly timeoutMs?: number;\n /** Longest cookie value accepted; longer ones are refused as `missing-credential`. Default 4096. */\n readonly maxCookieLength?: number;\n readonly fetch?: FetchLike;\n /** Clock, Unix milliseconds. Injectable for tests. */\n readonly now?: () => number;\n}\n\nconst DEFAULT_USERS_SLUG = 'users';\nconst DEFAULT_COOKIE_NAME = 'payload-token';\nconst DEFAULT_TIMEOUT_MS = 3_000;\nconst MIN_TIMEOUT_MS = 250;\nconst DEFAULT_MAX_COOKIE_LENGTH = 4_096;\nconst COOKIE_NAME_PATTERN = /^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$/;\nconst COOKIE_VALUE_PATTERN = /^[A-Za-z0-9._~-]+$/;\n/** Below this an `exp` is Unix seconds; a millisecond timestamp passed it in 1973. */\nconst SECONDS_CEILING = 1e11;\n\n/**\n * Exactly one value for `name`, or `null` when absent, empty, over `maxLength`\n * or repeated — a repeated cookie is ambiguous and is refused, not guessed.\n */\nexport function extractCookie(\n header: string | null,\n name: string,\n maxLength: number = DEFAULT_MAX_COOKIE_LENGTH,\n): string | null {\n if (header === null || header.length === 0) return null;\n const prefix = `${name}=`;\n const values: string[] = [];\n for (const part of header.split(';')) {\n const trimmed = part.trim();\n if (trimmed.startsWith(prefix)) values.push(trimmed.slice(prefix.length));\n }\n if (values.length !== 1) return null;\n const value = values[0] ?? '';\n // A JWT is `[A-Za-z0-9._~-]`; anything else must not be forwarded in a header.\n if (value.length === 0 || value.length > maxLength || !COOKIE_VALUE_PATTERN.test(value)) {\n return null;\n }\n return value;\n}\n\nfunction cookieNameFor(configured: string | undefined): string {\n const name = configured ?? DEFAULT_COOKIE_NAME;\n if (!COOKIE_NAME_PATTERN.test(name)) {\n throw new PreviewConfigurationError(\n `payload-live-preview: cookie name \"${name}\" is not a valid cookie-name token`,\n );\n }\n return name;\n}\n\nfunction serverOriginFor(serverURL: string): string {\n let url: URL;\n try {\n url = new URL(serverURL);\n } catch {\n throw new PreviewConfigurationError(\n `payload-live-preview: serverURL \"${serverURL}\" is not an absolute URL`,\n );\n }\n if (url.protocol !== 'https:' && url.protocol !== 'http:') {\n throw new PreviewConfigurationError(\n `payload-live-preview: serverURL must be http(s), got \"${url.protocol}\"`,\n );\n }\n return url.origin;\n}\n\nexport async function authorizeSession(\n request: PreviewAuthorizationRequest,\n strategy: PayloadSessionStrategy,\n): Promise<PreviewAuthorization> {\n const cookieName = cookieNameFor(strategy.cookieName);\n const origin = serverOriginFor(strategy.serverURL);\n const value = extractCookie(\n request.headers.get('cookie'),\n cookieName,\n strategy.maxCookieLength ?? DEFAULT_MAX_COOKIE_LENGTH,\n );\n if (value === null) return refused('missing-credential');\n const now = strategy.now ?? Date.now;\n const timeoutMs = Math.max(MIN_TIMEOUT_MS, strategy.timeoutMs ?? DEFAULT_TIMEOUT_MS);\n const fetchImpl: FetchLike = strategy.fetch ?? globalThis.fetch;\n const usersSlug = encodeURIComponent(strategy.usersSlug ?? DEFAULT_USERS_SLUG);\n const forwarded = `${cookieName}=${value}`;\n let body: unknown;\n try {\n // `depth=0`: the verdict needs the user's id, not its populated relations.\n const response = await fetchImpl(`${origin}/api/${usersSlug}/me?depth=0`, {\n headers: { cookie: forwarded, accept: 'application/json' },\n signal: AbortSignal.timeout(timeoutMs),\n });\n if (!response.ok) return refused(response.status === 401 ? 'invalid' : 'unavailable');\n body = await response.json();\n } catch {\n return refused('unavailable');\n }\n const user = readUser(body, strategy.usersSlug ?? DEFAULT_USERS_SLUG);\n if (user === null) return refused('invalid');\n const expiresAt = readExpiry(body);\n if (expiresAt !== undefined && expiresAt <= now()) return refused('expired');\n return {\n authorized: true,\n outcome: 'authorized',\n context: createAuthorizedPreviewContext({\n strategy: 'payload-session',\n subject: user,\n authorizedAt: now(),\n expiresAt,\n scope: {},\n payloadHeaders: { cookie: forwarded },\n }),\n };\n}\n\n/** The user id from a `/me` body, or `null` when absent or from another auth collection — Payload answers `/me` for every one of them. */\nfunction readUser(body: unknown, usersSlug: string): string | null {\n if (typeof body !== 'object' || body === null || Array.isArray(body)) return null;\n const user = (body as { user?: unknown }).user;\n if (typeof user !== 'object' || user === null || Array.isArray(user)) return null;\n const collection =\n (body as { collection?: unknown }).collection ?? (user as { collection?: unknown }).collection;\n if (collection !== undefined && collection !== usersSlug) return null;\n const id = (user as { id?: unknown }).id;\n if (typeof id === 'string' && id.length > 0) return id;\n if (typeof id === 'number' && Number.isFinite(id)) return String(id);\n return null;\n}\n\n/** `/me` reports `exp` in Unix seconds; units are decided by magnitude so an expired millisecond value stays expired. */\nfunction readExpiry(body: unknown): number | undefined {\n const exp = (body as { exp?: unknown }).exp;\n if (typeof exp !== 'number' || !Number.isFinite(exp) || exp <= 0) return undefined;\n return exp < SECONDS_CEILING ? exp * 1000 : exp;\n}\n","/**\n * The `signed-token` strategy: a short-lived HMAC-SHA256 token minted by the\n * Payload side and verified by the site. Format and bindings: ADR 0006 §3.\n */\n\nimport { createAuthorizedPreviewContext } from '@/types/authorized-preview';\nimport {\n PreviewConfigurationError,\n refused,\n type PreviewAuthorization,\n type PreviewAuthorizationRequest,\n} from './preview-verdict';\n\n/** The subset of Web Crypto the token strategy needs, injectable for environments that expose it elsewhere. */\nexport interface SubtleCryptoLike {\n readonly subtle: {\n importKey(\n format: 'raw',\n keyData: Uint8Array,\n algorithm: { readonly name: 'HMAC'; readonly hash: 'SHA-256' },\n extractable: false,\n keyUsages: readonly ('sign' | 'verify')[],\n ): Promise<CryptoKey>;\n sign(algorithm: 'HMAC', key: CryptoKey, data: Uint8Array): Promise<ArrayBuffer>;\n verify(\n algorithm: 'HMAC',\n key: CryptoKey,\n signature: Uint8Array,\n data: Uint8Array,\n ): Promise<boolean>;\n };\n getRandomValues<T extends Uint8Array>(array: T): T;\n}\n\n/** Where a signed token is read from on the request. */\nexport type PreviewTokenTransport =\n | { readonly kind: 'query'; readonly param?: string }\n | { readonly kind: 'header'; readonly name?: string };\n\n/**\n * Optional replay store. Not shipped: a per-process memory is useless behind\n * a load balancer, so the store is a deployment decision.\n */\nexport interface PreviewTokenReplayStore {\n /**\n * Record `id` as used until `expiresAt` and say whether this call was the\n * first: `true` admits the token, anything else refuses it as `replayed`, a\n * throw refuses it as `unavailable`. Check and record in one step (Redis\n * `SET NX PX`, a unique insert): a read followed by a write lets two requests\n * that carry the same token and arrive together both pass.\n */\n consume(id: string, expiresAt: number): Promise<boolean> | boolean;\n}\n\n/**\n * @deprecated The 1.x store shape, removed in 3.0. Its two calls cannot be one\n * step, so two requests with the same token that arrive together both pass\n * `isUsed` before either reaches `markUsed`. Implement\n * {@link PreviewTokenReplayStore.consume} instead.\n */\nexport interface PreviewTokenReplayChecks {\n isUsed(id: string): Promise<boolean> | boolean;\n markUsed(id: string, expiresAt: number): Promise<void> | void;\n}\n\nexport interface SignedTokenStrategy {\n readonly type: 'signed-token';\n /** Shared secret, at least 32 bytes; shorter ones are refused on first use. */\n readonly secret: string | Uint8Array;\n /** The site origin tokens must name, e.g. `https://www.example.com`. */\n readonly audience: string;\n /** Purpose string tokens must carry. Default `live-preview`. */\n readonly purpose?: string;\n /** Default: query parameter `previewToken`. */\n readonly transport?: PreviewTokenTransport;\n /** Resolve the request's locale for the `loc` binding. Unset: tokens with `loc` are refused as `wrong-locale`. */\n readonly locale?: (request: PreviewAuthorizationRequest) => string | undefined;\n // eslint-disable-next-line @typescript-eslint/no-deprecated -- the 1.x shape is accepted until 3.0\n readonly replay?: PreviewTokenReplayStore | PreviewTokenReplayChecks;\n readonly crypto?: SubtleCryptoLike;\n readonly now?: () => number;\n}\n\n/** What `issuePreviewToken()` binds a token to. */\nexport interface PreviewTokenClaims {\n /** The site origin the token is for. */\n readonly audience: string;\n /** Request pathname the token opens. Recommended; omit only for a single-page preview. */\n readonly path?: string;\n readonly locale?: string;\n readonly subject?: string;\n readonly purpose?: string;\n /** Lifetime in milliseconds. Default ten minutes; capped at one hour. */\n readonly ttlMs?: number;\n}\n\nexport interface IssuePreviewTokenOptions {\n readonly secret: string | Uint8Array;\n readonly crypto?: SubtleCryptoLike;\n readonly now?: () => number;\n}\n\ninterface WireClaims {\n readonly v: 1;\n readonly aud: string;\n readonly pth?: string;\n readonly loc?: string;\n readonly sub?: string;\n readonly pur: string;\n readonly iat: number;\n readonly exp: number;\n readonly jti: string;\n}\n\nconst DEFAULT_PURPOSE = 'live-preview';\nconst DEFAULT_QUERY_PARAM = 'previewToken';\nconst DEFAULT_HEADER_NAME = 'x-preview-token';\nconst DEFAULT_TTL_MS = 10 * 60_000;\nconst MAX_TTL_MS = 60 * 60_000;\nconst MIN_SECRET_BYTES = 32;\nconst TOKEN_VERSION = 'v1';\n/** A real token is a few hundred bytes. */\nconst MAX_TOKEN_LENGTH = 4_096;\n\n// Lazy: a module-scope `new TextEncoder()` is a side effect a bundler must keep.\nlet cachedEncoder: TextEncoder | undefined;\nfunction encoder(): TextEncoder {\n cachedEncoder ??= new TextEncoder();\n return cachedEncoder;\n}\n\nfunction secretBytes(secret: string | Uint8Array): Uint8Array {\n const bytes = typeof secret === 'string' ? encoder().encode(secret) : secret;\n if (bytes.byteLength < MIN_SECRET_BYTES) {\n throw new PreviewConfigurationError(\n `payload-live-preview: the preview token secret must be at least ${String(MIN_SECRET_BYTES)} bytes`,\n );\n }\n return bytes;\n}\n\nfunction cryptoFor(configured: SubtleCryptoLike | undefined): SubtleCryptoLike | undefined {\n if (configured !== undefined) return configured;\n const candidate = (globalThis as { crypto?: unknown }).crypto;\n if (typeof (candidate as { subtle?: unknown } | undefined)?.subtle === 'object') {\n return candidate as SubtleCryptoLike;\n }\n return undefined;\n}\n\nasync function hmacKey(crypto: SubtleCryptoLike, secret: Uint8Array): Promise<CryptoKey> {\n return crypto.subtle.importKey('raw', secret, { name: 'HMAC', hash: 'SHA-256' }, false, [\n 'sign',\n 'verify',\n ]);\n}\n\nfunction toBase64Url(bytes: Uint8Array): string {\n let binary = '';\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\nfunction fromBase64Url(text: string): Uint8Array | undefined {\n if (!/^[A-Za-z0-9_-]*$/.test(text)) return undefined;\n const padded =\n text.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat((4 - (text.length % 4)) % 4);\n try {\n const binary = atob(padded);\n const bytes = new Uint8Array(binary.length);\n for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);\n return bytes;\n } catch {\n return undefined;\n }\n}\n\n/** Mint a preview token on the Payload side; pair with `buildLivePreviewUrl`. */\nexport async function issuePreviewToken(\n claims: PreviewTokenClaims,\n options: IssuePreviewTokenOptions,\n): Promise<string> {\n const crypto = cryptoFor(options.crypto);\n if (crypto === undefined) {\n throw new Error('payload-live-preview: no Web Crypto implementation available to sign tokens');\n }\n const secret = secretBytes(options.secret);\n const now = (options.now ?? Date.now)();\n const ttl = Math.min(MAX_TTL_MS, Math.max(1, claims.ttlMs ?? DEFAULT_TTL_MS));\n const id = new Uint8Array(16);\n crypto.getRandomValues(id);\n const wire: WireClaims = {\n v: 1,\n aud: claims.audience,\n ...(claims.path !== undefined ? { pth: claims.path } : {}),\n ...(claims.locale !== undefined ? { loc: claims.locale } : {}),\n ...(claims.subject !== undefined ? { sub: claims.subject } : {}),\n pur: claims.purpose ?? DEFAULT_PURPOSE,\n iat: now,\n exp: now + ttl,\n jti: toBase64Url(id),\n };\n const payload = toBase64Url(encoder().encode(JSON.stringify(wire)));\n const signed = `${TOKEN_VERSION}.${payload}`;\n const key = await hmacKey(crypto, secret);\n const signature = new Uint8Array(await crypto.subtle.sign('HMAC', key, encoder().encode(signed)));\n return `${signed}.${toBase64Url(signature)}`;\n}\n\nfunction readToken(\n request: PreviewAuthorizationRequest,\n transport: PreviewTokenTransport | undefined,\n): string | null {\n if (transport?.kind === 'header') {\n return request.headers.get(transport.name ?? DEFAULT_HEADER_NAME);\n }\n let url: URL;\n try {\n url = new URL(request.url);\n } catch {\n return null;\n }\n const values = url.searchParams.getAll(transport?.param ?? DEFAULT_QUERY_PARAM);\n return values.length === 1 ? (values[0] ?? null) : null;\n}\n\nfunction parseClaims(bytes: Uint8Array): WireClaims | undefined {\n let parsed: unknown;\n try {\n parsed = JSON.parse(new TextDecoder().decode(bytes));\n } catch {\n return undefined;\n }\n if (typeof parsed !== 'object' || parsed === null) return undefined;\n const c = parsed as Record<string, unknown>;\n const optionalString = (key: string): boolean =>\n c[key] === undefined || typeof c[key] === 'string';\n if (\n c['v'] !== 1 ||\n typeof c['aud'] !== 'string' ||\n typeof c['pur'] !== 'string' ||\n typeof c['iat'] !== 'number' ||\n typeof c['exp'] !== 'number' ||\n typeof c['jti'] !== 'string' ||\n !optionalString('pth') ||\n !optionalString('loc') ||\n !optionalString('sub')\n ) {\n return undefined;\n }\n return c as unknown as WireClaims;\n}\n\nexport async function authorizeToken(\n request: PreviewAuthorizationRequest,\n strategy: SignedTokenStrategy,\n): Promise<PreviewAuthorization> {\n const secret = secretBytes(strategy.secret);\n const crypto = cryptoFor(strategy.crypto);\n if (crypto === undefined) return refused('unavailable');\n const token = readToken(request, strategy.transport);\n if (token === null || token.length === 0) return refused('missing-credential');\n if (token.length > MAX_TOKEN_LENGTH) return refused('invalid');\n const parts = token.split('.');\n if (parts.length !== 3 || parts[0] !== TOKEN_VERSION) return refused('invalid');\n const payloadText = parts[1] ?? '';\n const payloadBytes = fromBase64Url(payloadText);\n const signature = fromBase64Url(parts[2] ?? '');\n if (payloadBytes === undefined || signature?.byteLength !== 32) return refused('invalid');\n let verified: boolean;\n try {\n const key = await hmacKey(crypto, secret);\n verified = await crypto.subtle.verify(\n 'HMAC',\n key,\n signature,\n encoder().encode(`${TOKEN_VERSION}.${payloadText}`),\n );\n } catch {\n return refused('unavailable');\n }\n if (!verified) return refused('invalid');\n const claims = parseClaims(payloadBytes);\n if (claims === undefined) return refused('invalid');\n const now = (strategy.now ?? Date.now)();\n if (claims.exp <= now || claims.iat > claims.exp) return refused('expired');\n if (claims.aud !== strategy.audience) return refused('wrong-audience');\n if (claims.pur !== (strategy.purpose ?? DEFAULT_PURPOSE)) return refused('wrong-purpose');\n if (claims.pth !== undefined) {\n let pathname: string;\n try {\n pathname = new URL(request.url).pathname;\n } catch {\n return refused('invalid');\n }\n if (claims.pth !== pathname) return refused('wrong-path');\n }\n if (claims.loc !== undefined) {\n const locale = strategy.locale?.(request);\n if (locale !== claims.loc) return refused('wrong-locale');\n }\n if (strategy.replay !== undefined) {\n let first: boolean;\n try {\n first = await consumeTokenId(strategy.replay, claims.jti, claims.exp);\n } catch {\n return refused('unavailable');\n }\n if (!first) return refused('replayed');\n }\n return {\n authorized: true,\n outcome: 'authorized',\n context: createAuthorizedPreviewContext({\n strategy: 'signed-token',\n subject: claims.sub,\n authorizedAt: now,\n expiresAt: claims.exp,\n scope: {\n audience: claims.aud,\n ...(claims.pth !== undefined ? { path: claims.pth } : {}),\n ...(claims.loc !== undefined ? { locale: claims.loc } : {}),\n },\n payloadHeaders: {},\n }),\n };\n}\n\n/**\n * `true` when `id` was not seen before. A store that returns anything but\n * `true` from `consume` refuses the token: fail closed, since a forgotten\n * return would otherwise admit every replay. The 1.x shape keeps its two\n * steps and its race; that is why it is deprecated rather than emulated.\n */\nasync function consumeTokenId(\n // eslint-disable-next-line @typescript-eslint/no-deprecated -- the 1.x shape is accepted until 3.0\n store: PreviewTokenReplayStore | PreviewTokenReplayChecks,\n id: string,\n expiresAt: number,\n): Promise<boolean> {\n if ('consume' in store) {\n // The store is consumer code; its answer is a value, not a type.\n const answer: unknown = await store.consume(id, expiresAt);\n return answer === true;\n }\n if (await store.isUsed(id)) return false;\n await store.markUsed(id, expiresAt);\n return true;\n}\n","/**\n * Server-side preview-intent detection. Every signal here is client-controlled:\n * a positive result is a delivery hint, never proof of identity or permission.\n * Verify with `authorizePreviewRequest()` before anything privileged.\n */\n\n/** `query`: `?preview=true`; `fetch-dest`: `Sec-Fetch-Dest: iframe`; `referer`: an admin-origin Referer. */\nexport type PreviewSignal = 'query' | 'fetch-dest' | 'referer';\n\nexport interface PreviewRequestOptions {\n /** Which signals count. Default: all three. `['query']` is the adapters' 2.0 default. */\n readonly signals?: readonly PreviewSignal[];\n /** Query parameters (value `true` or `1`) that signal intent. Default `['preview', 'draft', 'livePreview']`. */\n readonly queryParams?: readonly string[];\n /** Admin origins whose Referer signals intent, e.g. `['https://cms.example.com']` — the adapters' option of the same name. */\n readonly allowedOrigins?: readonly string[];\n /** @deprecated Use `allowedOrigins`; removed in 3.0. Ignored when `allowedOrigins` is given. */\n readonly adminOrigins?: readonly string[];\n}\n\nconst DEFAULT_QUERY_PARAMS = ['preview', 'draft', 'livePreview'] as const;\n\n/** The request surface the predicate reads; `Request` satisfies it, Nitro/H3 events need a shim. */\nexport interface PreviewRequestLike {\n readonly url: string;\n readonly headers: { get(name: string): string | null };\n}\n\n/** Whether `request` carries live-preview intent. Intent, not authorization. */\nexport function hasPreviewIntent(\n request: PreviewRequestLike,\n options: PreviewRequestOptions = {},\n): boolean {\n const signals = new Set<PreviewSignal>(options.signals ?? ['query', 'fetch-dest', 'referer']);\n const queryParams = options.queryParams ?? DEFAULT_QUERY_PARAMS;\n\n if (signals.has('query')) {\n let url: URL | undefined;\n try {\n url = new URL(request.url);\n } catch {\n url = undefined;\n }\n if (url !== undefined) {\n for (const param of queryParams) {\n const value = url.searchParams.get(param);\n if (value === 'true' || value === '1') return true;\n }\n }\n }\n\n if (signals.has('fetch-dest') && request.headers.get('sec-fetch-dest') === 'iframe') {\n return true;\n }\n\n // The 1.x spelling is honoured until 3.0; the canonical name wins when both are given.\n // eslint-disable-next-line @typescript-eslint/no-deprecated -- the alias is read exactly here\n const configured = options.allowedOrigins ?? options.adminOrigins ?? [];\n const adminOrigins = signals.has('referer') ? configured : [];\n if (adminOrigins.length > 0) {\n const referer = request.headers.get('referer');\n if (referer !== null) {\n try {\n const refererOrigin = new URL(referer).origin;\n for (const adminOrigin of adminOrigins) {\n try {\n if (new URL(adminOrigin).origin === refererOrigin) return true;\n } catch {\n // A malformed configured origin matches nothing.\n }\n }\n } catch {\n // A malformed referer is not a signal.\n }\n }\n }\n\n return false;\n}\n","/**\n * Typed helpers emitting the `data-payload-*` attributes the runtime reads.\n * `bindByPath` records the path through a Proxy, so a rename follows.\n */\n\nimport type { FieldName } from './paths';\n\n/** A spreadable attribute record: `<h1 {...bind<Homepage>('heroTitle')}>`. */\nexport interface FieldBindingAttributes {\n readonly 'data-payload-field': string;\n readonly 'data-payload-attribute'?: string;\n readonly 'data-payload-type'?: string;\n readonly 'data-payload-richtext'?: string;\n readonly 'data-payload-html'?: string;\n readonly 'data-payload-locale'?: string;\n readonly 'data-payload-alt'?: string;\n readonly 'data-payload-href'?: string;\n readonly 'data-payload-array-template'?: string;\n}\n\nexport interface BindOptions {\n /** Attribute to write instead of the text content, e.g. `'src'` for `<img>`. */\n readonly attribute?: string;\n /** Explicit field type, bypassing schema detection. */\n readonly type?: string;\n /** Mark the binding as Lexical rich text; needed only when the initial render is empty. */\n readonly richtext?: boolean;\n /** Render the value as sanitised HTML rather than text. */\n readonly html?: boolean;\n /** Lock this element to one locale, overriding the message locale. */\n readonly locale?: string;\n /** Field whose value becomes the image `alt`. */\n readonly alt?: string;\n /** Field whose value becomes the link `href`. */\n readonly href?: string;\n /** Markup per array item with `{{value}}` placeholders. */\n readonly arrayTemplate?: string;\n}\n\n/** `bind<Homepage>('heroTitle')`; without `T` any string is accepted. */\nexport function bind<T = Record<string, unknown>>(\n field: FieldName<T>,\n options?: BindOptions,\n): FieldBindingAttributes {\n return buildAttributes(field, options);\n}\n\n/** `bindByPath<Homepage>(d => d.slides[0].title)` → `slides.title`; array indices are dropped because bindings resolve against the schema. */\n// `T` is used once, by design: the caller names the document type so the\n// picker is checked against it instead of against an untyped `d`.\n// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters\nexport function bindByPath<T = Record<string, unknown>>(\n picker: (data: T) => unknown,\n options?: BindOptions,\n): FieldBindingAttributes {\n const path = recordPath(picker);\n if (path.length === 0) {\n throw new Error(\n 'bindByPath: the picker did not read any field — return data.<field> instead of a constant',\n );\n }\n return buildAttributes(path.join('.'), options);\n}\n\nfunction buildAttributes(field: string, options: BindOptions | undefined): FieldBindingAttributes {\n if (field.length === 0) {\n throw new Error('bind: field name must be a non-empty string');\n }\n const attrs: {\n 'data-payload-field': string;\n 'data-payload-attribute'?: string;\n 'data-payload-type'?: string;\n 'data-payload-richtext'?: string;\n 'data-payload-html'?: string;\n 'data-payload-locale'?: string;\n 'data-payload-alt'?: string;\n 'data-payload-href'?: string;\n 'data-payload-array-template'?: string;\n } = { 'data-payload-field': field };\n if (options?.attribute !== undefined) attrs['data-payload-attribute'] = options.attribute;\n if (options?.type !== undefined) attrs['data-payload-type'] = options.type;\n // Presence attributes: the runtime tests for the attribute, not its value.\n if (options?.richtext === true) attrs['data-payload-richtext'] = '';\n if (options?.html === true) attrs['data-payload-html'] = '';\n if (options?.locale !== undefined) attrs['data-payload-locale'] = options.locale;\n if (options?.alt !== undefined) attrs['data-payload-alt'] = options.alt;\n if (options?.href !== undefined) attrs['data-payload-href'] = options.href;\n if (options?.arrayTemplate !== undefined) {\n attrs['data-payload-array-template'] = options.arrayTemplate;\n }\n return attrs;\n}\n\nfunction recordPath(picker: (data: never) => unknown): string[] {\n const path: string[] = [];\n const proxy: unknown = new Proxy(\n {},\n {\n get(_target, prop: string | symbol): unknown {\n if (typeof prop === 'symbol') return undefined;\n if (/^\\d+$/.test(prop)) return proxy;\n path.push(prop);\n return proxy;\n },\n },\n );\n try {\n picker(proxy as never);\n } catch {\n // A picker doing math or JSX on the proxy throws after recording the path it meant.\n }\n return path;\n}\n","/**\n * Binding helpers gated on one authorization verdict per request: the\n * attributes publish the content model, so an unauthorized response carries\n * none and a binding is suppressed whole, never in part. See ADR 0006.\n */\n\nimport {\n isAuthorizedPreviewContext,\n type AuthorizedPreviewContext,\n} from '@/types/authorized-preview';\nimport { bind, bindByPath, type BindOptions, type FieldBindingAttributes } from './bind';\nimport type { FieldName } from './paths';\n\nexport interface OwnerBindingAttributes {\n readonly 'data-payload-owner': string;\n}\n\n/** The attributes marking a server-rendered boundary (ADR 0011, docs/hybrid.md). */\nexport interface FragmentBoundaryAttributes {\n readonly 'data-payload-fragment': string;\n readonly 'data-payload-fragment-key'?: string;\n readonly 'data-payload-depends'?: string;\n}\n\nexport interface FragmentBoundaryOptions {\n /** Distinguishes boundaries when one registry id renders several on a page. */\n readonly key?: string;\n /** The fields that re-render the boundary. Without it, every update does. */\n readonly dependsOn?: readonly string[];\n}\n\n/**\n * Stricter than the endpoint's own check, which accepts either case: an id is\n * written here, and one that only differs in case from the registry key is a\n * boundary that silently never renders.\n */\nconst REGISTRY_ID = /^[a-z][a-z0-9-]{0,63}$/u;\nconst MAX_KEY_LENGTH = 128;\n\n/** No attributes at all: an unauthorized response is byte-identical to one that never knew about live preview. */\nexport type SuppressedBinding = Readonly<Record<string, never>>;\n\nconst SUPPRESSED: SuppressedBinding = Object.freeze({});\n\n/** The verdict from `authorizePreviewRequest()`, or `null` for a public response. Only a branded context authorizes emission. */\nexport interface PreviewBindingsOptions {\n readonly authorization: AuthorizedPreviewContext | null;\n /** Document this subtree belongs to, emitted as `data-payload-owner`; needed with `scopeBindingsByOwner`. */\n readonly owner?: string;\n}\n\n/** Request-scoped binding helpers carrying one authorization decision. */\nexport interface PreviewBindings {\n readonly authorized: boolean;\n /** Typed field binding, or nothing while unauthorized. */\n bind: <T = Record<string, unknown>>(\n field: FieldName<T>,\n options?: BindOptions,\n ) => FieldBindingAttributes | SuppressedBinding;\n /** Rename-safe field binding, or nothing while unauthorized. */\n // `T` names the caller's document type; see bindByPath in ./bind.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters\n bindByPath: <T = Record<string, unknown>>(\n picker: (data: T) => unknown,\n options?: BindOptions,\n ) => FieldBindingAttributes | SuppressedBinding;\n /** Owner marker for this subtree, or nothing while unauthorized or without an owner. */\n owner: () => OwnerBindingAttributes | SuppressedBinding;\n /**\n * A server-rendered boundary, or nothing while unauthorized — the id and the\n * fields it depends on describe the content model as much as a binding does.\n * The registry behind `id` is the endpoint's (docs/hybrid.md).\n */\n boundary: (\n id: string,\n options?: FragmentBoundaryOptions,\n ) => FragmentBoundaryAttributes | SuppressedBinding;\n}\n\n/** Built whether or not the response is authorized, so a bad id fails everywhere, not only in preview. */\nfunction boundaryAttributes(\n id: string,\n options: FragmentBoundaryOptions,\n): FragmentBoundaryAttributes {\n if (!REGISTRY_ID.test(id)) {\n throw new RangeError(\n `createPreviewBindings().boundary(): \"${id}\" is not a registry id — lowercase, starting ` +\n 'with a letter, then letters, digits or hyphens (max 64). The runtime would post it and ' +\n 'the endpoint would refuse it.',\n );\n }\n const key = options.key;\n if (key !== undefined && (key.length === 0 || key.length > MAX_KEY_LENGTH)) {\n throw new RangeError(\n `createPreviewBindings().boundary(): the key for \"${id}\" must be 1 to ${String(MAX_KEY_LENGTH)} characters.`,\n );\n }\n const dependsOn = options.dependsOn ?? [];\n return {\n 'data-payload-fragment': id,\n ...(key !== undefined ? { 'data-payload-fragment-key': key } : {}),\n ...(dependsOn.length > 0 ? { 'data-payload-depends': dependsOn.join(',') } : {}),\n };\n}\n\n/** Request-scoped `bind`, `bindByPath` and `owner`, all suppressed unless `authorization` is a real context. */\nexport function createPreviewBindings(options: PreviewBindingsOptions): PreviewBindings {\n // A copied or hand-written context is a public response.\n const authorized = isAuthorizedPreviewContext(options.authorization);\n const owner = options.owner !== undefined && options.owner.length > 0 ? options.owner : undefined;\n\n return Object.freeze({\n authorized,\n bind: <T = Record<string, unknown>>(\n field: FieldName<T>,\n bindOptions?: BindOptions,\n ): FieldBindingAttributes | SuppressedBinding =>\n authorized ? bind<T>(field, bindOptions) : SUPPRESSED,\n // `T` names the caller's document type; see bindByPath in ./bind.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters\n bindByPath: <T = Record<string, unknown>>(\n picker: (data: T) => unknown,\n bindOptions?: BindOptions,\n ): FieldBindingAttributes | SuppressedBinding =>\n authorized ? bindByPath<T>(picker, bindOptions) : SUPPRESSED,\n owner: (): OwnerBindingAttributes | SuppressedBinding =>\n authorized && owner !== undefined ? { 'data-payload-owner': owner } : SUPPRESSED,\n boundary: (\n id: string,\n boundaryOptions: FragmentBoundaryOptions = {},\n ): FragmentBoundaryAttributes | SuppressedBinding => {\n const attributes = boundaryAttributes(id, boundaryOptions);\n return authorized ? attributes : SUPPRESSED;\n },\n });\n}\n","/**\n * `definePreview()`: origin, route and depth bound once, so the initial draft\n * read and the runtime's `mergeDepth` cannot drift apart. Every read takes an\n * explicit authorization verdict; only a real context reads the draft.\n */\n\nimport {\n isAuthorizedPreviewContext,\n type AuthorizedPreviewContext,\n} from '@/types/authorized-preview';\n\n/**\n * A `fetch`-compatible function. The read forwards a session cookie, so it is\n * `cache: 'no-store'` (never a framework data cache) and `redirect: 'error'`.\n */\nexport type PreviewFetchFunction = (\n input: string,\n init: {\n readonly headers: Record<string, string>;\n readonly signal: AbortSignal;\n readonly cache: 'no-store';\n readonly redirect: 'error';\n },\n) => Promise<{\n readonly ok: boolean;\n readonly status: number;\n json(): Promise<unknown>;\n}>;\n\n/** What a read or a failure looked like, for logs and metrics. Never carries a header value. */\nexport type PreviewFetchDiagnostic =\n | {\n readonly kind: 'response';\n readonly url: string;\n readonly status: number;\n readonly draft: boolean;\n readonly durationMs: number;\n }\n | {\n readonly kind: 'failure';\n readonly url: string;\n readonly reason: PreviewFetchFailureReason;\n readonly status: number | undefined;\n readonly draft: boolean;\n readonly durationMs: number;\n };\n\n/** The one configuration the initial read and the runtime merge share. */\nexport interface PreviewServerConfig {\n /** Payload origin, e.g. `https://cms.example.com`. */\n readonly serverURL: string;\n /** REST route prefix. Default `/api`. */\n readonly apiRoute?: string;\n /** Population depth for the read and for the runtime's `mergeDepth`. Required so the two cannot drift. */\n readonly depth: number;\n /** Upper bound for one read. Default 5000 ms. */\n readonly timeoutMs?: number;\n readonly fetch?: PreviewFetchFunction;\n /** Observe every read and failure — for logs and metrics, not for control flow. */\n readonly onDiagnostic?: (diagnostic: PreviewFetchDiagnostic) => void;\n}\n\n/** Why a read did not produce a document. */\nexport type PreviewFetchFailureReason =\n 'http' | 'network' | 'timeout' | 'aborted' | 'invalid-json' | 'no-fetch';\n\nexport type PreviewFetchResult<T> =\n | {\n readonly ok: true;\n /** The document, or `null` when the query matched nothing. */\n readonly data: T | null;\n /** Whether the draft was requested — `true` exactly when `authorization` was a real context. */\n readonly draft: boolean;\n readonly status: number;\n }\n | {\n readonly ok: false;\n readonly reason: PreviewFetchFailureReason;\n readonly status: number | undefined;\n readonly draft: boolean;\n /** The underlying error for `network`, `invalid-json` and `aborted`. */\n readonly cause: unknown;\n };\n\n/** Thrown in `errorMode: 'throw'` instead of returning the failed result. */\nexport class PreviewFetchError extends Error {\n readonly reason: PreviewFetchFailureReason;\n readonly status: number | undefined;\n readonly url: string;\n constructor(\n reason: PreviewFetchFailureReason,\n url: string,\n status: number | undefined,\n cause: unknown,\n ) {\n super(\n `payload-live-preview: preview read failed (${reason}${status === undefined ? '' : ` ${String(status)}`}) for ${url}`,\n { cause },\n );\n this.name = 'PreviewFetchError';\n this.reason = reason;\n this.status = status;\n this.url = url;\n }\n}\n\n/** A scalar operand of a `where` operator; `null` is sent as the literal `null`. */\nexport type PreviewWhereValue = string | number | boolean | null | readonly (string | number)[];\n\n/**\n * A `where` tree in Payload's REST query shape: `{ slug: { equals: 'about' } }`,\n * `{ or: [{ … }, { … }] }`. Arrays of trees are indexed (`where[or][0][…]`).\n */\nexport interface PreviewWhere {\n readonly [key: string]: PreviewWhere | readonly PreviewWhere[] | PreviewWhereValue;\n}\n\n/** Options every read shares; `fetchDocument` and `fetchGlobal` add their target. */\nexport interface PreviewReadOptions {\n /**\n * The verdict from `authorizePreviewRequest()`. Only a real context reads the\n * draft; `null`, a copy or a JSON round trip reads the published document.\n */\n readonly authorization: AuthorizedPreviewContext | null;\n readonly locale?: string;\n /** Extra headers for this read; the context's headers win on conflict. */\n readonly headers?: Readonly<Record<string, string>>;\n /** Abort the read from the outside, e.g. with the request's own signal. */\n readonly signal?: AbortSignal;\n /** `'result'` (default) returns the failed result; `'throw'` throws `PreviewFetchError`. */\n readonly errorMode?: 'result' | 'throw';\n}\n\nexport interface ReadDocumentOptions extends PreviewReadOptions {\n readonly collection: string;\n readonly where?: PreviewWhere;\n}\n\nexport interface ReadGlobalOptions extends PreviewReadOptions {\n readonly global: string;\n}\n\n/** The runtime/adapter options this configuration implies — spread them into the adapter or client options. */\nexport interface PreviewRuntimeOptions {\n readonly serverURL: string;\n readonly apiRoute: string;\n readonly mergeDepth: number;\n}\n\nexport interface PreviewServer {\n readonly config: Readonly<\n Required<Pick<PreviewServerConfig, 'serverURL' | 'apiRoute' | 'depth' | 'timeoutMs'>>\n >;\n /** `{ serverURL, apiRoute, mergeDepth }` — the same depth the reads use. */\n readonly runtimeOptions: PreviewRuntimeOptions;\n fetchDocument<T = Record<string, unknown>>(\n options: ReadDocumentOptions,\n ): Promise<PreviewFetchResult<T>>;\n fetchGlobal<T = Record<string, unknown>>(\n options: ReadGlobalOptions,\n ): Promise<PreviewFetchResult<T>>;\n}\n\nconst DEFAULT_TIMEOUT_MS = 5_000;\nconst MIN_TIMEOUT_MS = 250;\n\n/** Bind origin, route and depth once; read documents and globals with an explicit authorization verdict. */\nexport function definePreview(config: PreviewServerConfig): PreviewServer {\n const serverURL = normalizeServerURL(config.serverURL);\n const route = config.apiRoute ?? '/api';\n const apiRoute = route.startsWith('/') ? route : `/${route}`;\n if (!Number.isInteger(config.depth) || config.depth < 0) {\n throw new Error(\n `payload-live-preview: depth must be a non-negative integer, got ${String(config.depth)}`,\n );\n }\n const timeoutMs = Math.max(MIN_TIMEOUT_MS, config.timeoutMs ?? DEFAULT_TIMEOUT_MS);\n const resolved = Object.freeze({ serverURL, apiRoute, depth: config.depth, timeoutMs });\n const runtimeOptions = Object.freeze({ serverURL, apiRoute, mergeDepth: config.depth });\n const base = `${serverURL}${apiRoute}`;\n const settings: ReadSettings = { config, timeoutMs };\n\n return Object.freeze({\n config: resolved,\n runtimeOptions,\n async fetchDocument<T = Record<string, unknown>>(\n options: ReadDocumentOptions,\n ): Promise<PreviewFetchResult<T>> {\n const draft = draftFor(options);\n const query = params(config.depth, options, draft);\n query.set('limit', '1');\n if (options.where !== undefined) appendWhere(query, options.where, ['where']);\n const url = `${base}/${encodeURIComponent(options.collection)}?${query.toString()}`;\n const result = await read<{ docs?: T[] }>(settings, url, options, draft);\n if (!result.ok) return result;\n const first = result.data?.docs?.[0];\n return { ok: true, data: first ?? null, draft, status: result.status };\n },\n async fetchGlobal<T = Record<string, unknown>>(\n options: ReadGlobalOptions,\n ): Promise<PreviewFetchResult<T>> {\n const draft = draftFor(options);\n const query = params(config.depth, options, draft);\n const url = `${base}/globals/${encodeURIComponent(options.global)}?${query.toString()}`;\n return read<T>(settings, url, options, draft);\n },\n });\n}\n\n/** What every read takes from the bound configuration. */\ninterface ReadSettings {\n readonly config: PreviewServerConfig;\n readonly timeoutMs: number;\n}\n\n/** One read in flight: what the diagnostic and a thrown failure need to say. */\ninterface ReadAttempt {\n readonly config: PreviewServerConfig;\n readonly url: string;\n readonly draft: boolean;\n readonly errorMode: 'result' | 'throw';\n readonly startedAt: number;\n}\n\nfunction draftFor(options: PreviewReadOptions): boolean {\n return isAuthorizedPreviewContext(options.authorization);\n}\n\nfunction params(depth: number, options: PreviewReadOptions, draft: boolean): URLSearchParams {\n const query = new URLSearchParams();\n query.set('depth', String(depth));\n if (draft) query.set('draft', 'true');\n if (options.locale !== undefined) query.set('locale', options.locale);\n return query;\n}\n\n/** One GET against the REST API; every exit reports to `onDiagnostic` and is shaped as a result. */\nasync function read<T>(\n { config, timeoutMs }: ReadSettings,\n url: string,\n options: PreviewReadOptions,\n draft: boolean,\n): Promise<PreviewFetchResult<T>> {\n const fetchImpl: PreviewFetchFunction | undefined =\n config.fetch ?? (typeof fetch === 'function' ? fetch : undefined);\n const attempt: ReadAttempt = {\n config,\n url,\n draft,\n errorMode: options.errorMode ?? 'result',\n startedAt: Date.now(),\n };\n if (fetchImpl === undefined) {\n return settle(attempt, {\n ok: false,\n reason: 'no-fetch',\n status: undefined,\n draft,\n cause: undefined,\n });\n }\n const timeout = AbortSignal.timeout(timeoutMs);\n const signal =\n options.signal === undefined ? timeout : AbortSignal.any([timeout, options.signal]);\n const context = isAuthorizedPreviewContext(options.authorization) ? options.authorization : null;\n let response: Awaited<ReturnType<PreviewFetchFunction>>;\n try {\n response = await fetchImpl(url, {\n headers: {\n accept: 'application/json',\n ...(options.headers ?? {}),\n ...(context?.payloadHeaders ?? {}),\n },\n signal,\n cache: 'no-store',\n redirect: 'error',\n });\n } catch (cause) {\n return settle(attempt, {\n ok: false,\n reason: failureReason(cause, options.signal),\n status: undefined,\n draft,\n cause,\n });\n }\n if (!response.ok) {\n return settle(attempt, {\n ok: false,\n reason: 'http',\n status: response.status,\n draft,\n cause: undefined,\n });\n }\n let body: unknown;\n try {\n body = await response.json();\n } catch (cause) {\n return settle(attempt, {\n ok: false,\n reason: 'invalid-json',\n status: response.status,\n draft,\n cause,\n });\n }\n return settle(attempt, { ok: true, data: body as T, draft, status: response.status });\n}\n\n/** Report the outcome, then hand it back — or throw it, in `errorMode: 'throw'`. */\nfunction settle<T>(attempt: ReadAttempt, result: PreviewFetchResult<T>): PreviewFetchResult<T> {\n const { config, url, draft } = attempt;\n const durationMs = Date.now() - attempt.startedAt;\n if (result.ok) {\n config.onDiagnostic?.({ kind: 'response', url, status: result.status, draft, durationMs });\n return result;\n }\n config.onDiagnostic?.({\n kind: 'failure',\n url,\n reason: result.reason,\n status: result.status,\n draft,\n durationMs,\n });\n if (attempt.errorMode === 'throw') {\n throw new PreviewFetchError(result.reason, url, result.status, result.cause);\n }\n return result;\n}\n\nfunction normalizeServerURL(serverURL: string): string {\n let url: URL;\n try {\n url = new URL(serverURL);\n } catch {\n throw new Error(`payload-live-preview: serverURL \"${serverURL}\" is not an absolute URL`);\n }\n if (url.protocol !== 'https:' && url.protocol !== 'http:') {\n throw new Error(`payload-live-preview: serverURL must be http(s), got \"${url.protocol}\"`);\n }\n return `${url.origin}${url.pathname.replace(/\\/+$/, '')}`;\n}\n\nfunction failureReason(\n cause: unknown,\n external: AbortSignal | undefined,\n): PreviewFetchFailureReason {\n if (external?.aborted === true) return 'aborted';\n const name =\n typeof cause === 'object' && cause !== null ? (cause as { name?: unknown }).name : undefined;\n if (name === 'TimeoutError') return 'timeout';\n if (name === 'AbortError') return 'aborted';\n return 'network';\n}\n\nfunction isWhereTree(value: unknown): value is PreviewWhere {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction queryName(path: readonly string[]): string {\n return path.map((segment, index) => (index === 0 ? segment : `[${segment}]`)).join('');\n}\n\nfunction appendWhere(query: URLSearchParams, node: PreviewWhere, path: readonly string[]): void {\n for (const [key, value] of Object.entries(node)) {\n const nextPath = [...path, key];\n if (isWhereTree(value)) {\n appendWhere(query, value, nextPath);\n } else if (Array.isArray(value) && value.some(isWhereTree)) {\n // `or` / `and`: Payload's query parser turns bracketed indices back into an array.\n (value as readonly PreviewWhere[]).forEach((branch, index) => {\n appendWhere(query, branch, [...nextPath, String(index)]);\n });\n } else if (Array.isArray(value)) {\n query.set(queryName(nextPath), (value as readonly (string | number)[]).map(String).join(','));\n } else {\n // Only `null` is still an object here; Payload's query sanitizers read the literal back.\n query.set(queryName(nextPath), typeof value === 'object' ? 'null' : String(value));\n }\n }\n}\n","/**\n * Authorized preview context: the one verdict every privileged preview\n * decision is keyed on. Intent (`?preview=true`, an iframe destination, a\n * referer) is client-chosen; this turns a session, a signed token or the\n * application's own verifier into a branded context. Threat model: ADR 0006.\n */\n\nimport {\n AUTHORIZED_PREVIEW_BRAND_KEY,\n createAuthorizedPreviewContext,\n isAuthorizedPreviewContext,\n type AuthorizedPreviewContext,\n type AuthorizedPreviewScope,\n type PreviewAuthorizationStrategyName,\n} from '@/types/authorized-preview';\nimport {\n refused,\n type PreviewAuthorization,\n type PreviewAuthorizationRequest,\n} from './preview-verdict';\nimport { authorizeSession, type PayloadSessionStrategy } from './preview-session';\nimport { authorizeToken, type SignedTokenStrategy } from './preview-token';\n\nexport {\n AUTHORIZED_PREVIEW_BRAND_KEY,\n isAuthorizedPreviewContext,\n type AuthorizedPreviewContext,\n type AuthorizedPreviewScope,\n type PreviewAuthorizationStrategyName,\n};\nexport {\n PreviewConfigurationError,\n type PreviewAuthorization,\n type PreviewAuthorizationOutcome,\n type PreviewAuthorizationRequest,\n} from './preview-verdict';\nexport { extractCookie, type FetchLike, type PayloadSessionStrategy } from './preview-session';\nexport {\n issuePreviewToken,\n type IssuePreviewTokenOptions,\n type PreviewTokenClaims,\n // eslint-disable-next-line @typescript-eslint/no-deprecated -- the 1.x shape exists to be re-exported\n type PreviewTokenReplayChecks,\n type PreviewTokenReplayStore,\n type PreviewTokenTransport,\n type SignedTokenStrategy,\n type SubtleCryptoLike,\n} from './preview-token';\n\n/** Claims a consumer verifier returns for a request it accepts. */\nexport interface PreviewVerifierClaims {\n readonly subject?: string;\n readonly expiresAt?: number;\n readonly scope?: AuthorizedPreviewScope;\n readonly payloadHeaders?: Readonly<Record<string, string>>;\n}\n\n/** The application's own verification: claims authorize, `null` refuses, a throw is `unavailable`. */\nexport interface VerifierStrategy {\n readonly type: 'verifier';\n readonly verify: (\n request: PreviewAuthorizationRequest,\n ) => Promise<PreviewVerifierClaims | null> | PreviewVerifierClaims | null;\n readonly now?: () => number;\n}\n\nexport type PreviewAuthorizationStrategy =\n PayloadSessionStrategy | SignedTokenStrategy | VerifierStrategy;\n\n/** Decide whether `request` is an authorized preview; refusals resolve to `{ authorized: false }` and only a `PreviewConfigurationError` throws. */\nexport async function authorizePreviewRequest(\n request: PreviewAuthorizationRequest,\n strategy: PreviewAuthorizationStrategy,\n): Promise<PreviewAuthorization> {\n switch (strategy.type) {\n case 'payload-session':\n return authorizeSession(request, strategy);\n case 'signed-token':\n return authorizeToken(request, strategy);\n case 'verifier':\n return authorizeVerifier(request, strategy);\n }\n}\n\nasync function authorizeVerifier(\n request: PreviewAuthorizationRequest,\n strategy: VerifierStrategy,\n): Promise<PreviewAuthorization> {\n let claims: PreviewVerifierClaims | null;\n try {\n claims = await strategy.verify(request);\n } catch {\n return refused('unavailable');\n }\n if (claims === null) return refused('invalid');\n const now = (strategy.now ?? Date.now)();\n if (claims.expiresAt !== undefined && claims.expiresAt <= now) return refused('expired');\n return {\n authorized: true,\n outcome: 'authorized',\n context: createAuthorizedPreviewContext({\n strategy: 'verifier',\n subject: claims.subject,\n authorizedAt: now,\n expiresAt: claims.expiresAt,\n scope: claims.scope ?? {},\n payloadHeaders: claims.payloadHeaders ?? {},\n }),\n };\n}\n","/** What a decision publishes for templates on `locals` (Astro, SvelteKit) or `event.context` (Nuxt). */\n\nimport { createPreviewBindings, type PreviewBindings } from '@dsl/preview-bindings';\nimport type { PreviewAuthorizationOutcome } from '@security/preview-verdict';\nimport type { AuthorizedPreviewContext } from '@/types/authorized-preview';\nimport type { PreviewDecision } from './policy';\n\n/**\n * The keys the adapters publish on the request-scoped context — `Astro.locals`,\n * SvelteKit's `event.locals`, Nuxt's `event.context`. Extend the framework's\n * `App.Locals` with it and templates read them typed. Every key is optional:\n * a refused preview withholds them all, and Next.js has no locals at all.\n */\nexport interface LivePreviewLocals {\n /**\n * The request-scoped CSP nonce, for templates that nonce their own scripts.\n * Set by the Astro middleware (prerendered pages included), the SvelteKit\n * handle and the Nuxt server handler or plugin on every request — except a\n * preview the `authorizePreview` hook refused, which gets no preview artefact.\n */\n readonly livePreviewNonce?: string;\n /** The verified `AuthorizedPreviewContext`. Set only when `authorizePreview` authorized an intent-bearing request. */\n readonly livePreviewAuthorization?: AuthorizedPreviewContext;\n /** The hook's `PreviewAuthorizationOutcome`, refusals included. Set whenever the hook ran: on intent, with `authorizePreview` configured. */\n readonly livePreviewAuthorizationOutcome?: PreviewAuthorizationOutcome;\n}\n\n/** The request-scoped nonce, for templates that nonce their own scripts. @internal */\nexport const NONCE_LOCALS_KEY = 'livePreviewNonce' satisfies keyof LivePreviewLocals;\n\n/** The verified `AuthorizedPreviewContext`; absent unless `authorizePreview` authorized. @internal */\nexport const AUTHORIZATION_LOCALS_KEY =\n 'livePreviewAuthorization' satisfies keyof LivePreviewLocals;\n\n/** The hook's `PreviewAuthorizationOutcome`; absent when no hook ran. @internal */\nexport const AUTHORIZATION_OUTCOME_LOCALS_KEY =\n 'livePreviewAuthorizationOutcome' satisfies keyof LivePreviewLocals;\n\n/**\n * What an adapter needs of a framework's request context: the three keys it\n * writes, and nothing else.\n *\n * This is what the adapters' event types declare rather than\n * `Record<string, unknown>`. A framework's own `App.Locals` is an interface\n * without an index signature, so it is not assignable to a record — which made\n * composing our handle with SvelteKit's `Handle` type fail to compile, even\n * though the values matched. The adapters assign through the interface's own\n * keys, so the published type cannot drift from what the code writes either.\n */\nexport type LivePreviewLocalsSink = {\n -readonly [K in keyof LivePreviewLocals]: LivePreviewLocals[K];\n};\n\n/** Publish the nonce alone — a prerendered page has no request to decide for. */\nexport function exposeNonce(locals: LivePreviewLocalsSink, nonce: string): void {\n locals.livePreviewNonce = nonce;\n}\n\n/** Publish what templates may read; after a refusal the nonce is withheld like every other preview artefact. */\nexport function exposeDecision(\n locals: LivePreviewLocalsSink,\n decision: PreviewDecision,\n nonce: string,\n): void {\n if (decision.exposeNonce || !decision.isPreview) locals.livePreviewNonce = nonce;\n if (decision.authorization !== null) locals.livePreviewAuthorization = decision.authorization;\n if (decision.outcome !== undefined) locals.livePreviewAuthorizationOutcome = decision.outcome;\n}\n\n/**\n * The binding helpers for one request, read straight off whatever the framework\n * calls locals — `Astro.locals`, SvelteKit's `event.locals`, Nuxt's\n * `event.context`.\n *\n * `createPreviewBindings({ authorization: locals.livePreviewAuthorization })`\n * says the same thing, and a template that spells it out is welcome to. This\n * exists because generated code should not have to: the build-time annotator\n * writes one call per component, and a helper that takes `unknown` needs no\n * `App.Locals` declaration in the project it writes into. The verdict is\n * checked, not trusted — only a context `authorizePreviewRequest()` produced\n * emits anything (ADR 0006).\n * @internal\n */\nexport function previewBindingsFromLocals(\n locals: unknown,\n options: { readonly owner?: string } = {},\n): PreviewBindings {\n const value =\n typeof locals === 'object' && locals !== null\n ? (locals as Record<string, unknown>)[AUTHORIZATION_LOCALS_KEY]\n : undefined;\n return createPreviewBindings({\n authorization: (value ?? null) as AuthorizedPreviewContext | null,\n ...(options.owner !== undefined ? { owner: options.owner } : {}),\n });\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"server.cjs","names":["AUTHORIZED_PREVIEW_BRAND_KEY","BRAND","Symbol","for","isAuthorizedPreviewContext","value","Object","isFrozen","createAuthorizedPreviewContext","fields","freeze","scope","payloadHeaders","PreviewConfigurationError","Error","name","refused","outcome","authorized","context","DEFAULT_USERS_SLUG","COOKIE_NAME_PATTERN","COOKIE_VALUE_PATTERN","extractCookie","header","maxLength","length","prefix","values","part","split","trimmed","trim","startsWith","push","slice","test","cachedEncoder","DEFAULT_PURPOSE","TOKEN_VERSION","encoder","TextEncoder","secretBytes","secret","bytes","encode","byteLength","String","cryptoFor","configured","candidate","globalThis","crypto","subtle","async","hmacKey","importKey","hash","toBase64Url","binary","byte","fromCharCode","btoa","replace","fromBase64Url","text","padded","repeat","atob","Uint8Array","index","charCodeAt","DEFAULT_QUERY_PARAMS","bind","field","options","buildAttributes","bindByPath","picker","path","proxy","Proxy","get","_target","prop","recordPath","join","attrs","attribute","type","richtext","html","locale","alt","href","arrayTemplate","REGISTRY_ID","SUPPRESSED","createPreviewBindings","authorization","owner","bindOptions","boundary","id","boundaryOptions","attributes","RangeError","key","dependsOn","boundaryAttributes","PreviewFetchError","reason","status","url","constructor","cause","super","this","draftFor","params","depth","draft","query","URLSearchParams","set","read","config","timeoutMs","fetchImpl","fetch","attempt","errorMode","startedAt","Date","now","settle","ok","timeout","AbortSignal","signal","any","response","body","headers","accept","cache","redirect","failureReason","json","data","result","durationMs","onDiagnostic","kind","external","aborted","isWhereTree","Array","isArray","queryName","map","segment","appendWhere","node","entries","nextPath","some","forEach","branch","request","strategy","cookieName","cookieNameFor","origin","serverURL","URL","protocol","serverOriginFor","maxCookieLength","Math","max","usersSlug","encodeURIComponent","forwarded","cookie","user","collection","Number","isFinite","readUser","expiresAt","exp","readExpiry","subject","authorizedAt","authorizeSession","token","transport","searchParams","getAll","param","readToken","parts","payloadText","payloadBytes","signature","verified","verify","claims","parsed","JSON","parse","TextDecoder","decode","c","optionalString","parseClaims","iat","aud","audience","pur","purpose","pth","pathname","loc","replay","first","store","consume","isUsed","markUsed","consumeTokenId","jti","sub","authorizeToken","authorizeVerifier","normalizeServerURL","route","apiRoute","isInteger","resolved","runtimeOptions","mergeDepth","base","settings","fetchDocument","where","toString","docs","fetchGlobal","global","signals","Set","queryParams","has","at","allowedOrigins","adminOrigins","referer","refererOrigin","adminOrigin","ttl","min","ttlMs","getRandomValues","wire","v","payload","stringify","signed","sign","locals"],"sources":["../src/types/authorized-preview.ts","../src/security/preview-verdict.ts","../src/security/preview-session.ts","../src/security/preview-token.ts","../src/adapters/shared/preview-request.ts","../src/dsl/bind.ts","../src/dsl/preview-bindings.ts","../src/server/preview.ts","../src/security/preview-authorization.ts","../src/adapters/shared/locals.ts"],"sourcesContent":["/**\n * The authorized preview context's shape and brand, in the leaf `types` domain\n * so producer and consumers recognise the same context. See ADR 0006.\n */\n\n/**\n * The brand key, in the type and on the object.\n *\n * This was a `unique symbol` until 2026-08-30. A `unique symbol` is nominal per\n * *declaration*, and the published entries each carry their own declaration\n * file — so `AuthorizedPreviewContext` from the root barrel was a different\n * type from the one in `./server` or in an adapter, and the documented flow\n * (authorize once, hand the context to the adapter hook and to the draft read)\n * did not type-check in any combination a consumer can write.\n *\n * That is the same trap the runtime brand below already avoids by using\n * `Symbol.for` rather than `Symbol()`; the lesson simply had not reached the\n * type. A shared string literal is identical in every bundle for the same\n * reason a registry symbol is.\n *\n * It is not what makes a context trustworthy. `isAuthorizedPreviewContext`\n * decides that, on the registry symbol and on `Object.isFrozen`, and every\n * consumption point calls it — the adapter policy, the draft read and the\n * binding emitter. Writing this key by hand buys nothing.\n */\nexport const AUTHORIZED_PREVIEW_BRAND_KEY = 'payload-live-preview.authorized-preview-context';\n\nexport type PreviewAuthorizationStrategyName = 'payload-session' | 'signed-token' | 'verifier';\n\n/** What a context is bound to; every field is optional because every strategy binds differently. */\nexport interface AuthorizedPreviewScope {\n /** The site origin the credential was issued for. */\n readonly audience?: string;\n /** The request pathname the credential is valid for. */\n readonly path?: string;\n readonly locale?: string;\n}\n\n/** The verdict of a successful `authorizePreviewRequest()`: frozen, branded, produced only there. Carry it, do not rebuild it. */\nexport interface AuthorizedPreviewContext {\n readonly [AUTHORIZED_PREVIEW_BRAND_KEY]: true;\n readonly strategy: PreviewAuthorizationStrategyName;\n /** The user id, token subject, or whatever the verifier named. */\n readonly subject: string | undefined;\n /** Unix milliseconds. */\n readonly authorizedAt: number;\n /** Unix milliseconds, or `undefined` when the strategy does not know. */\n readonly expiresAt: number | undefined;\n readonly scope: AuthorizedPreviewScope;\n /** The minimal request material a draft read forwards to Payload — one verified cookie, never the whole `Cookie` header. */\n readonly payloadHeaders: Readonly<Record<string, string>>;\n}\n\n// A registry symbol: entries are separate bundles, and a per-bundle `Symbol()`\n// would refuse the other's contexts in the package while unit tests passed.\nconst BRAND = Symbol.for(AUTHORIZED_PREVIEW_BRAND_KEY);\n\n/** Whether `value` was produced by `authorizePreviewRequest()`; a copy or JSON round trip is not. */\nexport function isAuthorizedPreviewContext(value: unknown): value is AuthorizedPreviewContext {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as Record<PropertyKey, unknown>)[BRAND] === true &&\n Object.isFrozen(value)\n );\n}\n\n/** Internal: the one place a context is made. */\nexport function createAuthorizedPreviewContext(\n fields: Omit<AuthorizedPreviewContext, typeof AUTHORIZED_PREVIEW_BRAND_KEY>,\n): AuthorizedPreviewContext {\n const context = Object.freeze({\n ...fields,\n scope: Object.freeze({ ...fields.scope }),\n payloadHeaders: Object.freeze({ ...fields.payloadHeaders }),\n [BRAND]: true,\n [AUTHORIZED_PREVIEW_BRAND_KEY]: true,\n });\n // No cast: the frozen object carries the brand key the interface declares.\n return context;\n}\n","/**\n * The verdict shape every authorization strategy produces, and the one error\n * class that is allowed to escape it. Kept apart from the strategies so the\n * adapters can import the error without pulling in HMAC or session code.\n */\n\nimport type { AuthorizedPreviewContext } from '@/types/authorized-preview';\n\n/** Why a request was refused. `'authorized'` is the one non-refusal. */\nexport type PreviewAuthorizationOutcome =\n | 'authorized'\n | 'missing-credential'\n | 'invalid'\n | 'expired'\n | 'wrong-audience'\n | 'wrong-path'\n | 'wrong-locale'\n | 'wrong-purpose'\n | 'replayed'\n | 'unavailable';\n\n/** The result of `authorizePreviewRequest()`: a verdict, never an exception. */\nexport type PreviewAuthorization =\n | {\n readonly authorized: true;\n readonly outcome: 'authorized';\n readonly context: AuthorizedPreviewContext;\n }\n | {\n readonly authorized: false;\n readonly outcome: Exclude<PreviewAuthorizationOutcome, 'authorized'>;\n readonly context: null;\n };\n\n/** The request shape every strategy reads: a URL and a header getter. */\nexport interface PreviewAuthorizationRequest {\n readonly url: string;\n readonly headers: { get(name: string): string | null };\n}\n\n/**\n * A strategy was misconfigured — raised on first use and re-thrown by the\n * adapters rather than reported as `'unavailable'`, so it fails loudly.\n */\nexport class PreviewConfigurationError extends Error {\n override readonly name = 'PreviewConfigurationError';\n}\n\n/** Recognises the error across bundles, where `instanceof` sees a different class copy. */\nexport function isPreviewConfigurationError(error: unknown): error is PreviewConfigurationError {\n return (\n error instanceof PreviewConfigurationError ||\n (error instanceof Error && error.name === 'PreviewConfigurationError')\n );\n}\n\nexport function refused(\n outcome: Exclude<PreviewAuthorizationOutcome, 'authorized'>,\n): PreviewAuthorization {\n return { authorized: false, outcome, context: null };\n}\n","/**\n * The `payload-session` strategy: forward exactly one named cookie to\n * `GET <serverURL>/api/<usersSlug>/me` and authorize when a user comes back.\n */\n\nimport { createAuthorizedPreviewContext } from '@/types/authorized-preview';\nimport {\n PreviewConfigurationError,\n refused,\n type PreviewAuthorization,\n type PreviewAuthorizationRequest,\n} from './preview-verdict';\n\n/** A `fetch`-compatible function, injectable for tests. */\nexport type FetchLike = (\n input: string,\n init: { readonly headers: Record<string, string>; readonly signal: AbortSignal },\n) => Promise<{ readonly ok: boolean; readonly status: number; json(): Promise<unknown> }>;\n\nexport interface PayloadSessionStrategy {\n readonly type: 'payload-session';\n /** Payload server origin, e.g. `https://cms.example.com`. */\n readonly serverURL: string;\n /** Auth collection slug; `users` unless the project renamed it. */\n readonly usersSlug?: string;\n /** Cookie name; Payload's default is `payload-token`. */\n readonly cookieName?: string;\n /** Upper bound for the `/me` round trip. Below 250 ms is raised to 250 ms; default 3000. */\n readonly timeoutMs?: number;\n /** Longest cookie value accepted; longer ones are refused as `missing-credential`. Default 4096. */\n readonly maxCookieLength?: number;\n readonly fetch?: FetchLike;\n /** Clock, Unix milliseconds. Injectable for tests. */\n readonly now?: () => number;\n}\n\nconst DEFAULT_USERS_SLUG = 'users';\nconst DEFAULT_COOKIE_NAME = 'payload-token';\nconst DEFAULT_TIMEOUT_MS = 3_000;\nconst MIN_TIMEOUT_MS = 250;\nconst DEFAULT_MAX_COOKIE_LENGTH = 4_096;\nconst COOKIE_NAME_PATTERN = /^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$/;\nconst COOKIE_VALUE_PATTERN = /^[A-Za-z0-9._~-]+$/;\n/** Below this an `exp` is Unix seconds; a millisecond timestamp passed it in 1973. */\nconst SECONDS_CEILING = 1e11;\n\n/**\n * Exactly one value for `name`, or `null` when absent, empty, over `maxLength`\n * or repeated — a repeated cookie is ambiguous and is refused, not guessed.\n */\nexport function extractCookie(\n header: string | null,\n name: string,\n maxLength: number = DEFAULT_MAX_COOKIE_LENGTH,\n): string | null {\n if (header === null || header.length === 0) return null;\n const prefix = `${name}=`;\n const values: string[] = [];\n for (const part of header.split(';')) {\n const trimmed = part.trim();\n if (trimmed.startsWith(prefix)) values.push(trimmed.slice(prefix.length));\n }\n if (values.length !== 1) return null;\n const value = values[0] ?? '';\n // A JWT is `[A-Za-z0-9._~-]`; anything else must not be forwarded in a header.\n if (value.length === 0 || value.length > maxLength || !COOKIE_VALUE_PATTERN.test(value)) {\n return null;\n }\n return value;\n}\n\nfunction cookieNameFor(configured: string | undefined): string {\n const name = configured ?? DEFAULT_COOKIE_NAME;\n if (!COOKIE_NAME_PATTERN.test(name)) {\n throw new PreviewConfigurationError(\n `payload-live-preview: cookie name \"${name}\" is not a valid cookie-name token`,\n );\n }\n return name;\n}\n\nfunction serverOriginFor(serverURL: string): string {\n let url: URL;\n try {\n url = new URL(serverURL);\n } catch {\n throw new PreviewConfigurationError(\n `payload-live-preview: serverURL \"${serverURL}\" is not an absolute URL`,\n );\n }\n if (url.protocol !== 'https:' && url.protocol !== 'http:') {\n throw new PreviewConfigurationError(\n `payload-live-preview: serverURL must be http(s), got \"${url.protocol}\"`,\n );\n }\n return url.origin;\n}\n\nexport async function authorizeSession(\n request: PreviewAuthorizationRequest,\n strategy: PayloadSessionStrategy,\n): Promise<PreviewAuthorization> {\n const cookieName = cookieNameFor(strategy.cookieName);\n const origin = serverOriginFor(strategy.serverURL);\n const value = extractCookie(\n request.headers.get('cookie'),\n cookieName,\n strategy.maxCookieLength ?? DEFAULT_MAX_COOKIE_LENGTH,\n );\n if (value === null) return refused('missing-credential');\n const now = strategy.now ?? Date.now;\n const timeoutMs = Math.max(MIN_TIMEOUT_MS, strategy.timeoutMs ?? DEFAULT_TIMEOUT_MS);\n const fetchImpl: FetchLike = strategy.fetch ?? globalThis.fetch;\n const usersSlug = encodeURIComponent(strategy.usersSlug ?? DEFAULT_USERS_SLUG);\n const forwarded = `${cookieName}=${value}`;\n let body: unknown;\n try {\n // `depth=0`: the verdict needs the user's id, not its populated relations.\n const response = await fetchImpl(`${origin}/api/${usersSlug}/me?depth=0`, {\n headers: { cookie: forwarded, accept: 'application/json' },\n signal: AbortSignal.timeout(timeoutMs),\n });\n if (!response.ok) return refused(response.status === 401 ? 'invalid' : 'unavailable');\n body = await response.json();\n } catch {\n return refused('unavailable');\n }\n const user = readUser(body, strategy.usersSlug ?? DEFAULT_USERS_SLUG);\n if (user === null) return refused('invalid');\n const expiresAt = readExpiry(body);\n if (expiresAt !== undefined && expiresAt <= now()) return refused('expired');\n return {\n authorized: true,\n outcome: 'authorized',\n context: createAuthorizedPreviewContext({\n strategy: 'payload-session',\n subject: user,\n authorizedAt: now(),\n expiresAt,\n scope: {},\n payloadHeaders: { cookie: forwarded },\n }),\n };\n}\n\n/** The user id from a `/me` body, or `null` when absent or from another auth collection — Payload answers `/me` for every one of them. */\nfunction readUser(body: unknown, usersSlug: string): string | null {\n if (typeof body !== 'object' || body === null || Array.isArray(body)) return null;\n const user = (body as { user?: unknown }).user;\n if (typeof user !== 'object' || user === null || Array.isArray(user)) return null;\n const collection =\n (body as { collection?: unknown }).collection ?? (user as { collection?: unknown }).collection;\n if (collection !== undefined && collection !== usersSlug) return null;\n const id = (user as { id?: unknown }).id;\n if (typeof id === 'string' && id.length > 0) return id;\n if (typeof id === 'number' && Number.isFinite(id)) return String(id);\n return null;\n}\n\n/** `/me` reports `exp` in Unix seconds; units are decided by magnitude so an expired millisecond value stays expired. */\nfunction readExpiry(body: unknown): number | undefined {\n const exp = (body as { exp?: unknown }).exp;\n if (typeof exp !== 'number' || !Number.isFinite(exp) || exp <= 0) return undefined;\n return exp < SECONDS_CEILING ? exp * 1000 : exp;\n}\n","/**\n * The `signed-token` strategy: a short-lived HMAC-SHA256 token minted by the\n * Payload side and verified by the site. Format and bindings: ADR 0006 §3.\n */\n\nimport { createAuthorizedPreviewContext } from '@/types/authorized-preview';\nimport {\n PreviewConfigurationError,\n refused,\n type PreviewAuthorization,\n type PreviewAuthorizationRequest,\n} from './preview-verdict';\n\n/** The subset of Web Crypto the token strategy needs, injectable for environments that expose it elsewhere. */\nexport interface SubtleCryptoLike {\n readonly subtle: {\n importKey(\n format: 'raw',\n keyData: Uint8Array,\n algorithm: { readonly name: 'HMAC'; readonly hash: 'SHA-256' },\n extractable: false,\n keyUsages: readonly ('sign' | 'verify')[],\n ): Promise<CryptoKey>;\n sign(algorithm: 'HMAC', key: CryptoKey, data: Uint8Array): Promise<ArrayBuffer>;\n verify(\n algorithm: 'HMAC',\n key: CryptoKey,\n signature: Uint8Array,\n data: Uint8Array,\n ): Promise<boolean>;\n };\n getRandomValues<T extends Uint8Array>(array: T): T;\n}\n\n/** Where a signed token is read from on the request. */\nexport type PreviewTokenTransport =\n | { readonly kind: 'query'; readonly param?: string }\n | { readonly kind: 'header'; readonly name?: string };\n\n/**\n * Optional replay store. Not shipped: a per-process memory is useless behind\n * a load balancer, so the store is a deployment decision.\n */\nexport interface PreviewTokenReplayStore {\n /**\n * Record `id` as used until `expiresAt` and say whether this call was the\n * first: `true` admits the token, anything else refuses it as `replayed`, a\n * throw refuses it as `unavailable`. Check and record in one step (Redis\n * `SET NX PX`, a unique insert): a read followed by a write lets two requests\n * that carry the same token and arrive together both pass.\n */\n consume(id: string, expiresAt: number): Promise<boolean> | boolean;\n}\n\n/**\n * @deprecated The 1.x store shape, removed in 3.0. Its two calls cannot be one\n * step, so two requests with the same token that arrive together both pass\n * `isUsed` before either reaches `markUsed`. Implement\n * {@link PreviewTokenReplayStore.consume} instead.\n */\nexport interface PreviewTokenReplayChecks {\n isUsed(id: string): Promise<boolean> | boolean;\n markUsed(id: string, expiresAt: number): Promise<void> | void;\n}\n\nexport interface SignedTokenStrategy {\n readonly type: 'signed-token';\n /** Shared secret, at least 32 bytes; shorter ones are refused on first use. */\n readonly secret: string | Uint8Array;\n /** The site origin tokens must name, e.g. `https://www.example.com`. */\n readonly audience: string;\n /** Purpose string tokens must carry. Default `live-preview`. */\n readonly purpose?: string;\n /** Default: query parameter `previewToken`. */\n readonly transport?: PreviewTokenTransport;\n /** Resolve the request's locale for the `loc` binding. Unset: tokens with `loc` are refused as `wrong-locale`. */\n readonly locale?: (request: PreviewAuthorizationRequest) => string | undefined;\n // eslint-disable-next-line @typescript-eslint/no-deprecated -- the 1.x shape is accepted until 3.0\n readonly replay?: PreviewTokenReplayStore | PreviewTokenReplayChecks;\n readonly crypto?: SubtleCryptoLike;\n readonly now?: () => number;\n}\n\n/** What `issuePreviewToken()` binds a token to. */\nexport interface PreviewTokenClaims {\n /** The site origin the token is for. */\n readonly audience: string;\n /** Request pathname the token opens. Recommended; omit only for a single-page preview. */\n readonly path?: string;\n readonly locale?: string;\n readonly subject?: string;\n readonly purpose?: string;\n /** Lifetime in milliseconds. Default ten minutes; capped at one hour. */\n readonly ttlMs?: number;\n}\n\nexport interface IssuePreviewTokenOptions {\n readonly secret: string | Uint8Array;\n readonly crypto?: SubtleCryptoLike;\n readonly now?: () => number;\n}\n\ninterface WireClaims {\n readonly v: 1;\n readonly aud: string;\n readonly pth?: string;\n readonly loc?: string;\n readonly sub?: string;\n readonly pur: string;\n readonly iat: number;\n readonly exp: number;\n readonly jti: string;\n}\n\nconst DEFAULT_PURPOSE = 'live-preview';\nconst DEFAULT_QUERY_PARAM = 'previewToken';\nconst DEFAULT_HEADER_NAME = 'x-preview-token';\nconst DEFAULT_TTL_MS = 10 * 60_000;\nconst MAX_TTL_MS = 60 * 60_000;\nconst MIN_SECRET_BYTES = 32;\nconst TOKEN_VERSION = 'v1';\n/** A real token is a few hundred bytes. */\nconst MAX_TOKEN_LENGTH = 4_096;\n\n// Lazy: a module-scope `new TextEncoder()` is a side effect a bundler must keep.\nlet cachedEncoder: TextEncoder | undefined;\nfunction encoder(): TextEncoder {\n cachedEncoder ??= new TextEncoder();\n return cachedEncoder;\n}\n\nfunction secretBytes(secret: string | Uint8Array): Uint8Array {\n const bytes = typeof secret === 'string' ? encoder().encode(secret) : secret;\n if (bytes.byteLength < MIN_SECRET_BYTES) {\n throw new PreviewConfigurationError(\n `payload-live-preview: the preview token secret must be at least ${String(MIN_SECRET_BYTES)} bytes`,\n );\n }\n return bytes;\n}\n\nfunction cryptoFor(configured: SubtleCryptoLike | undefined): SubtleCryptoLike | undefined {\n if (configured !== undefined) return configured;\n const candidate = (globalThis as { crypto?: unknown }).crypto;\n if (typeof (candidate as { subtle?: unknown } | undefined)?.subtle === 'object') {\n return candidate as SubtleCryptoLike;\n }\n return undefined;\n}\n\nasync function hmacKey(crypto: SubtleCryptoLike, secret: Uint8Array): Promise<CryptoKey> {\n return crypto.subtle.importKey('raw', secret, { name: 'HMAC', hash: 'SHA-256' }, false, [\n 'sign',\n 'verify',\n ]);\n}\n\nfunction toBase64Url(bytes: Uint8Array): string {\n let binary = '';\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\nfunction fromBase64Url(text: string): Uint8Array | undefined {\n if (!/^[A-Za-z0-9_-]*$/.test(text)) return undefined;\n const padded =\n text.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat((4 - (text.length % 4)) % 4);\n try {\n const binary = atob(padded);\n const bytes = new Uint8Array(binary.length);\n for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);\n return bytes;\n } catch {\n return undefined;\n }\n}\n\n/** Mint a preview token on the Payload side; pair with `buildLivePreviewUrl`. */\nexport async function issuePreviewToken(\n claims: PreviewTokenClaims,\n options: IssuePreviewTokenOptions,\n): Promise<string> {\n const crypto = cryptoFor(options.crypto);\n if (crypto === undefined) {\n throw new Error('payload-live-preview: no Web Crypto implementation available to sign tokens');\n }\n const secret = secretBytes(options.secret);\n const now = (options.now ?? Date.now)();\n const ttl = Math.min(MAX_TTL_MS, Math.max(1, claims.ttlMs ?? DEFAULT_TTL_MS));\n const id = new Uint8Array(16);\n crypto.getRandomValues(id);\n const wire: WireClaims = {\n v: 1,\n aud: claims.audience,\n ...(claims.path !== undefined ? { pth: claims.path } : {}),\n ...(claims.locale !== undefined ? { loc: claims.locale } : {}),\n ...(claims.subject !== undefined ? { sub: claims.subject } : {}),\n pur: claims.purpose ?? DEFAULT_PURPOSE,\n iat: now,\n exp: now + ttl,\n jti: toBase64Url(id),\n };\n const payload = toBase64Url(encoder().encode(JSON.stringify(wire)));\n const signed = `${TOKEN_VERSION}.${payload}`;\n const key = await hmacKey(crypto, secret);\n const signature = new Uint8Array(await crypto.subtle.sign('HMAC', key, encoder().encode(signed)));\n return `${signed}.${toBase64Url(signature)}`;\n}\n\nfunction readToken(\n request: PreviewAuthorizationRequest,\n transport: PreviewTokenTransport | undefined,\n): string | null {\n if (transport?.kind === 'header') {\n return request.headers.get(transport.name ?? DEFAULT_HEADER_NAME);\n }\n let url: URL;\n try {\n url = new URL(request.url);\n } catch {\n return null;\n }\n const values = url.searchParams.getAll(transport?.param ?? DEFAULT_QUERY_PARAM);\n return values.length === 1 ? (values[0] ?? null) : null;\n}\n\nfunction parseClaims(bytes: Uint8Array): WireClaims | undefined {\n let parsed: unknown;\n try {\n parsed = JSON.parse(new TextDecoder().decode(bytes));\n } catch {\n return undefined;\n }\n if (typeof parsed !== 'object' || parsed === null) return undefined;\n const c = parsed as Record<string, unknown>;\n const optionalString = (key: string): boolean =>\n c[key] === undefined || typeof c[key] === 'string';\n if (\n c['v'] !== 1 ||\n typeof c['aud'] !== 'string' ||\n typeof c['pur'] !== 'string' ||\n typeof c['iat'] !== 'number' ||\n typeof c['exp'] !== 'number' ||\n typeof c['jti'] !== 'string' ||\n !optionalString('pth') ||\n !optionalString('loc') ||\n !optionalString('sub')\n ) {\n return undefined;\n }\n return c as unknown as WireClaims;\n}\n\nexport async function authorizeToken(\n request: PreviewAuthorizationRequest,\n strategy: SignedTokenStrategy,\n): Promise<PreviewAuthorization> {\n const secret = secretBytes(strategy.secret);\n const crypto = cryptoFor(strategy.crypto);\n if (crypto === undefined) return refused('unavailable');\n const token = readToken(request, strategy.transport);\n if (token === null || token.length === 0) return refused('missing-credential');\n if (token.length > MAX_TOKEN_LENGTH) return refused('invalid');\n const parts = token.split('.');\n if (parts.length !== 3 || parts[0] !== TOKEN_VERSION) return refused('invalid');\n const payloadText = parts[1] ?? '';\n const payloadBytes = fromBase64Url(payloadText);\n const signature = fromBase64Url(parts[2] ?? '');\n if (payloadBytes === undefined || signature?.byteLength !== 32) return refused('invalid');\n let verified: boolean;\n try {\n const key = await hmacKey(crypto, secret);\n verified = await crypto.subtle.verify(\n 'HMAC',\n key,\n signature,\n encoder().encode(`${TOKEN_VERSION}.${payloadText}`),\n );\n } catch {\n return refused('unavailable');\n }\n if (!verified) return refused('invalid');\n const claims = parseClaims(payloadBytes);\n if (claims === undefined) return refused('invalid');\n const now = (strategy.now ?? Date.now)();\n if (claims.exp <= now || claims.iat > claims.exp) return refused('expired');\n if (claims.aud !== strategy.audience) return refused('wrong-audience');\n if (claims.pur !== (strategy.purpose ?? DEFAULT_PURPOSE)) return refused('wrong-purpose');\n if (claims.pth !== undefined) {\n let pathname: string;\n try {\n pathname = new URL(request.url).pathname;\n } catch {\n return refused('invalid');\n }\n if (claims.pth !== pathname) return refused('wrong-path');\n }\n if (claims.loc !== undefined) {\n const locale = strategy.locale?.(request);\n if (locale !== claims.loc) return refused('wrong-locale');\n }\n if (strategy.replay !== undefined) {\n let first: boolean;\n try {\n first = await consumeTokenId(strategy.replay, claims.jti, claims.exp);\n } catch {\n return refused('unavailable');\n }\n if (!first) return refused('replayed');\n }\n return {\n authorized: true,\n outcome: 'authorized',\n context: createAuthorizedPreviewContext({\n strategy: 'signed-token',\n subject: claims.sub,\n authorizedAt: now,\n expiresAt: claims.exp,\n scope: {\n audience: claims.aud,\n ...(claims.pth !== undefined ? { path: claims.pth } : {}),\n ...(claims.loc !== undefined ? { locale: claims.loc } : {}),\n },\n payloadHeaders: {},\n }),\n };\n}\n\n/**\n * `true` when `id` was not seen before. A store that returns anything but\n * `true` from `consume` refuses the token: fail closed, since a forgotten\n * return would otherwise admit every replay. The 1.x shape keeps its two\n * steps and its race; that is why it is deprecated rather than emulated.\n */\nasync function consumeTokenId(\n // eslint-disable-next-line @typescript-eslint/no-deprecated -- the 1.x shape is accepted until 3.0\n store: PreviewTokenReplayStore | PreviewTokenReplayChecks,\n id: string,\n expiresAt: number,\n): Promise<boolean> {\n if ('consume' in store) {\n // The store is consumer code; its answer is a value, not a type.\n const answer: unknown = await store.consume(id, expiresAt);\n return answer === true;\n }\n if (await store.isUsed(id)) return false;\n await store.markUsed(id, expiresAt);\n return true;\n}\n","/**\n * Server-side preview-intent detection. Every signal here is client-controlled:\n * a positive result is a delivery hint, never proof of identity or permission.\n * Verify with `authorizePreviewRequest()` before anything privileged.\n */\n\n/** `query`: `?preview=true`; `fetch-dest`: `Sec-Fetch-Dest: iframe`; `referer`: an admin-origin Referer. */\nexport type PreviewSignal = 'query' | 'fetch-dest' | 'referer';\n\nexport interface PreviewRequestOptions {\n /** Which signals count. Default: all three. `['query']` is the adapters' 2.0 default. */\n readonly signals?: readonly PreviewSignal[];\n /** Query parameters (value `true` or `1`) that signal intent. Default `['preview', 'draft', 'livePreview']`. */\n readonly queryParams?: readonly string[];\n /** Admin origins whose Referer signals intent, e.g. `['https://cms.example.com']` — the adapters' option of the same name. */\n readonly allowedOrigins?: readonly string[];\n /** @deprecated Use `allowedOrigins`; removed in 3.0. Ignored when `allowedOrigins` is given. */\n readonly adminOrigins?: readonly string[];\n}\n\nconst DEFAULT_QUERY_PARAMS = ['preview', 'draft', 'livePreview'] as const;\n\n/** The request surface the predicate reads; `Request` satisfies it, Nitro/H3 events need a shim. */\nexport interface PreviewRequestLike {\n readonly url: string;\n readonly headers: { get(name: string): string | null };\n}\n\n/** Whether `request` carries live-preview intent. Intent, not authorization. */\nexport function hasPreviewIntent(\n request: PreviewRequestLike,\n options: PreviewRequestOptions = {},\n): boolean {\n const signals = new Set<PreviewSignal>(options.signals ?? ['query', 'fetch-dest', 'referer']);\n const queryParams = options.queryParams ?? DEFAULT_QUERY_PARAMS;\n\n if (signals.has('query')) {\n let url: URL | undefined;\n try {\n url = new URL(request.url);\n } catch {\n url = undefined;\n }\n if (url !== undefined) {\n for (const param of queryParams) {\n // The last value of a repeated key, as Next.js matches a `has` rule:\n // `withLivePreview()` marks the response by that reading, and the two\n // have to agree on `?preview=true&preview=0`.\n const value = url.searchParams.getAll(param).at(-1);\n if (value === 'true' || value === '1') return true;\n }\n }\n }\n\n if (signals.has('fetch-dest') && request.headers.get('sec-fetch-dest') === 'iframe') {\n return true;\n }\n\n // The 1.x spelling is honoured until 3.0; the canonical name wins when both are given.\n // eslint-disable-next-line @typescript-eslint/no-deprecated -- the alias is read exactly here\n const configured = options.allowedOrigins ?? options.adminOrigins ?? [];\n const adminOrigins = signals.has('referer') ? configured : [];\n if (adminOrigins.length > 0) {\n const referer = request.headers.get('referer');\n if (referer !== null) {\n try {\n const refererOrigin = new URL(referer).origin;\n for (const adminOrigin of adminOrigins) {\n try {\n if (new URL(adminOrigin).origin === refererOrigin) return true;\n } catch {\n // A malformed configured origin matches nothing.\n }\n }\n } catch {\n // A malformed referer is not a signal.\n }\n }\n }\n\n return false;\n}\n","/**\n * Typed helpers emitting the `data-payload-*` attributes the runtime reads.\n * `bindByPath` records the path through a Proxy, so a rename follows.\n */\n\nimport type { FieldName } from './paths';\n\n/** A spreadable attribute record: `<h1 {...bind<Homepage>('heroTitle')}>`. */\nexport interface FieldBindingAttributes {\n readonly 'data-payload-field': string;\n readonly 'data-payload-attribute'?: string;\n readonly 'data-payload-type'?: string;\n readonly 'data-payload-richtext'?: string;\n readonly 'data-payload-html'?: string;\n readonly 'data-payload-locale'?: string;\n readonly 'data-payload-alt'?: string;\n readonly 'data-payload-href'?: string;\n readonly 'data-payload-array-template'?: string;\n}\n\nexport interface BindOptions {\n /** Attribute to write instead of the text content, e.g. `'src'` for `<img>`. */\n readonly attribute?: string;\n /** Explicit field type, bypassing schema detection. */\n readonly type?: string;\n /** Mark the binding as Lexical rich text; needed only when the initial render is empty. */\n readonly richtext?: boolean;\n /** Render the value as sanitised HTML rather than text. */\n readonly html?: boolean;\n /** Lock this element to one locale, overriding the message locale. */\n readonly locale?: string;\n /** Field whose value becomes the image `alt`. */\n readonly alt?: string;\n /** Field whose value becomes the link `href`. */\n readonly href?: string;\n /** Markup per array item with `{{value}}` placeholders. */\n readonly arrayTemplate?: string;\n}\n\n/** `bind<Homepage>('heroTitle')`; without `T` any string is accepted. */\nexport function bind<T = Record<string, unknown>>(\n field: FieldName<T>,\n options?: BindOptions,\n): FieldBindingAttributes {\n return buildAttributes(field, options);\n}\n\n/** `bindByPath<Homepage>(d => d.slides[0].title)` → `slides.title`; array indices are dropped because bindings resolve against the schema. */\n// `T` is used once, by design: the caller names the document type so the\n// picker is checked against it instead of against an untyped `d`.\n// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters\nexport function bindByPath<T = Record<string, unknown>>(\n picker: (data: T) => unknown,\n options?: BindOptions,\n): FieldBindingAttributes {\n const path = recordPath(picker);\n if (path.length === 0) {\n throw new Error(\n 'bindByPath: the picker did not read any field — return data.<field> instead of a constant',\n );\n }\n return buildAttributes(path.join('.'), options);\n}\n\nfunction buildAttributes(field: string, options: BindOptions | undefined): FieldBindingAttributes {\n if (field.length === 0) {\n throw new Error('bind: field name must be a non-empty string');\n }\n const attrs: {\n 'data-payload-field': string;\n 'data-payload-attribute'?: string;\n 'data-payload-type'?: string;\n 'data-payload-richtext'?: string;\n 'data-payload-html'?: string;\n 'data-payload-locale'?: string;\n 'data-payload-alt'?: string;\n 'data-payload-href'?: string;\n 'data-payload-array-template'?: string;\n } = { 'data-payload-field': field };\n if (options?.attribute !== undefined) attrs['data-payload-attribute'] = options.attribute;\n if (options?.type !== undefined) attrs['data-payload-type'] = options.type;\n // Presence attributes: the runtime tests for the attribute, not its value.\n if (options?.richtext === true) attrs['data-payload-richtext'] = '';\n if (options?.html === true) attrs['data-payload-html'] = '';\n if (options?.locale !== undefined) attrs['data-payload-locale'] = options.locale;\n if (options?.alt !== undefined) attrs['data-payload-alt'] = options.alt;\n if (options?.href !== undefined) attrs['data-payload-href'] = options.href;\n if (options?.arrayTemplate !== undefined) {\n attrs['data-payload-array-template'] = options.arrayTemplate;\n }\n return attrs;\n}\n\nfunction recordPath(picker: (data: never) => unknown): string[] {\n const path: string[] = [];\n const proxy: unknown = new Proxy(\n {},\n {\n get(_target, prop: string | symbol): unknown {\n if (typeof prop === 'symbol') return undefined;\n if (/^\\d+$/.test(prop)) return proxy;\n path.push(prop);\n return proxy;\n },\n },\n );\n try {\n picker(proxy as never);\n } catch {\n // A picker doing math or JSX on the proxy throws after recording the path it meant.\n }\n return path;\n}\n","/**\n * Binding helpers gated on one authorization verdict per request: the\n * attributes publish the content model, so an unauthorized response carries\n * none and a binding is suppressed whole, never in part. See ADR 0006.\n */\n\nimport {\n isAuthorizedPreviewContext,\n type AuthorizedPreviewContext,\n} from '@/types/authorized-preview';\nimport { bind, bindByPath, type BindOptions, type FieldBindingAttributes } from './bind';\nimport type { FieldName } from './paths';\n\nexport interface OwnerBindingAttributes {\n readonly 'data-payload-owner': string;\n}\n\n/** The attributes marking a server-rendered boundary (ADR 0011, docs/hybrid.md). */\nexport interface FragmentBoundaryAttributes {\n readonly 'data-payload-fragment': string;\n readonly 'data-payload-fragment-key'?: string;\n readonly 'data-payload-depends'?: string;\n}\n\nexport interface FragmentBoundaryOptions {\n /** Distinguishes boundaries when one registry id renders several on a page. */\n readonly key?: string;\n /** The fields that re-render the boundary. Without it, every update does. */\n readonly dependsOn?: readonly string[];\n}\n\n/**\n * Stricter than the endpoint's own check, which accepts either case: an id is\n * written here, and one that only differs in case from the registry key is a\n * boundary that silently never renders.\n */\nconst REGISTRY_ID = /^[a-z][a-z0-9-]{0,63}$/u;\nconst MAX_KEY_LENGTH = 128;\n\n/** No attributes at all: an unauthorized response is byte-identical to one that never knew about live preview. */\nexport type SuppressedBinding = Readonly<Record<string, never>>;\n\nconst SUPPRESSED: SuppressedBinding = Object.freeze({});\n\n/** The verdict from `authorizePreviewRequest()`, or `null` for a public response. Only a branded context authorizes emission. */\nexport interface PreviewBindingsOptions {\n readonly authorization: AuthorizedPreviewContext | null;\n /** Document this subtree belongs to, emitted as `data-payload-owner`; needed with `scopeBindingsByOwner`. */\n readonly owner?: string;\n}\n\n/** Request-scoped binding helpers carrying one authorization decision. */\nexport interface PreviewBindings {\n readonly authorized: boolean;\n /** Typed field binding, or nothing while unauthorized. */\n bind: <T = Record<string, unknown>>(\n field: FieldName<T>,\n options?: BindOptions,\n ) => FieldBindingAttributes | SuppressedBinding;\n /** Rename-safe field binding, or nothing while unauthorized. */\n // `T` names the caller's document type; see bindByPath in ./bind.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters\n bindByPath: <T = Record<string, unknown>>(\n picker: (data: T) => unknown,\n options?: BindOptions,\n ) => FieldBindingAttributes | SuppressedBinding;\n /** Owner marker for this subtree, or nothing while unauthorized or without an owner. */\n owner: () => OwnerBindingAttributes | SuppressedBinding;\n /**\n * A server-rendered boundary, or nothing while unauthorized — the id and the\n * fields it depends on describe the content model as much as a binding does.\n * The registry behind `id` is the endpoint's (docs/hybrid.md).\n */\n boundary: (\n id: string,\n options?: FragmentBoundaryOptions,\n ) => FragmentBoundaryAttributes | SuppressedBinding;\n}\n\n/** Built whether or not the response is authorized, so a bad id fails everywhere, not only in preview. */\nfunction boundaryAttributes(\n id: string,\n options: FragmentBoundaryOptions,\n): FragmentBoundaryAttributes {\n if (!REGISTRY_ID.test(id)) {\n throw new RangeError(\n `createPreviewBindings().boundary(): \"${id}\" is not a registry id — lowercase, starting ` +\n 'with a letter, then letters, digits or hyphens (max 64). The runtime would post it and ' +\n 'the endpoint would refuse it.',\n );\n }\n const key = options.key;\n if (key !== undefined && (key.length === 0 || key.length > MAX_KEY_LENGTH)) {\n throw new RangeError(\n `createPreviewBindings().boundary(): the key for \"${id}\" must be 1 to ${String(MAX_KEY_LENGTH)} characters.`,\n );\n }\n const dependsOn = options.dependsOn ?? [];\n return {\n 'data-payload-fragment': id,\n ...(key !== undefined ? { 'data-payload-fragment-key': key } : {}),\n ...(dependsOn.length > 0 ? { 'data-payload-depends': dependsOn.join(',') } : {}),\n };\n}\n\n/** Request-scoped `bind`, `bindByPath` and `owner`, all suppressed unless `authorization` is a real context. */\nexport function createPreviewBindings(options: PreviewBindingsOptions): PreviewBindings {\n // A copied or hand-written context is a public response.\n const authorized = isAuthorizedPreviewContext(options.authorization);\n const owner = options.owner !== undefined && options.owner.length > 0 ? options.owner : undefined;\n\n return Object.freeze({\n authorized,\n bind: <T = Record<string, unknown>>(\n field: FieldName<T>,\n bindOptions?: BindOptions,\n ): FieldBindingAttributes | SuppressedBinding =>\n authorized ? bind<T>(field, bindOptions) : SUPPRESSED,\n // `T` names the caller's document type; see bindByPath in ./bind.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters\n bindByPath: <T = Record<string, unknown>>(\n picker: (data: T) => unknown,\n bindOptions?: BindOptions,\n ): FieldBindingAttributes | SuppressedBinding =>\n authorized ? bindByPath<T>(picker, bindOptions) : SUPPRESSED,\n owner: (): OwnerBindingAttributes | SuppressedBinding =>\n authorized && owner !== undefined ? { 'data-payload-owner': owner } : SUPPRESSED,\n boundary: (\n id: string,\n boundaryOptions: FragmentBoundaryOptions = {},\n ): FragmentBoundaryAttributes | SuppressedBinding => {\n const attributes = boundaryAttributes(id, boundaryOptions);\n return authorized ? attributes : SUPPRESSED;\n },\n });\n}\n","/**\n * `definePreview()`: origin, route and depth bound once, so the initial draft\n * read and the runtime's `mergeDepth` cannot drift apart. Every read takes an\n * explicit authorization verdict; only a real context reads the draft.\n */\n\nimport {\n isAuthorizedPreviewContext,\n type AuthorizedPreviewContext,\n} from '@/types/authorized-preview';\n\n/**\n * A `fetch`-compatible function. The read forwards a session cookie, so it is\n * `cache: 'no-store'` (never a framework data cache) and `redirect: 'error'`.\n */\nexport type PreviewFetchFunction = (\n input: string,\n init: {\n readonly headers: Record<string, string>;\n readonly signal: AbortSignal;\n readonly cache: 'no-store';\n readonly redirect: 'error';\n },\n) => Promise<{\n readonly ok: boolean;\n readonly status: number;\n json(): Promise<unknown>;\n}>;\n\n/** What a read or a failure looked like, for logs and metrics. Never carries a header value. */\nexport type PreviewFetchDiagnostic =\n | {\n readonly kind: 'response';\n readonly url: string;\n readonly status: number;\n readonly draft: boolean;\n readonly durationMs: number;\n }\n | {\n readonly kind: 'failure';\n readonly url: string;\n readonly reason: PreviewFetchFailureReason;\n readonly status: number | undefined;\n readonly draft: boolean;\n readonly durationMs: number;\n };\n\n/** The one configuration the initial read and the runtime merge share. */\nexport interface PreviewServerConfig {\n /** Payload origin, e.g. `https://cms.example.com`. */\n readonly serverURL: string;\n /** REST route prefix. Default `/api`. */\n readonly apiRoute?: string;\n /** Population depth for the read and for the runtime's `mergeDepth`. Required so the two cannot drift. */\n readonly depth: number;\n /** Upper bound for one read. Default 5000 ms. */\n readonly timeoutMs?: number;\n readonly fetch?: PreviewFetchFunction;\n /** Observe every read and failure — for logs and metrics, not for control flow. */\n readonly onDiagnostic?: (diagnostic: PreviewFetchDiagnostic) => void;\n}\n\n/** Why a read did not produce a document. */\nexport type PreviewFetchFailureReason =\n 'http' | 'network' | 'timeout' | 'aborted' | 'invalid-json' | 'no-fetch';\n\nexport type PreviewFetchResult<T> =\n | {\n readonly ok: true;\n /** The document, or `null` when the query matched nothing. */\n readonly data: T | null;\n /** Whether the draft was requested — `true` exactly when `authorization` was a real context. */\n readonly draft: boolean;\n readonly status: number;\n }\n | {\n readonly ok: false;\n readonly reason: PreviewFetchFailureReason;\n readonly status: number | undefined;\n readonly draft: boolean;\n /** The underlying error for `network`, `invalid-json` and `aborted`. */\n readonly cause: unknown;\n };\n\n/** Thrown in `errorMode: 'throw'` instead of returning the failed result. */\nexport class PreviewFetchError extends Error {\n readonly reason: PreviewFetchFailureReason;\n readonly status: number | undefined;\n readonly url: string;\n constructor(\n reason: PreviewFetchFailureReason,\n url: string,\n status: number | undefined,\n cause: unknown,\n ) {\n super(\n `payload-live-preview: preview read failed (${reason}${status === undefined ? '' : ` ${String(status)}`}) for ${url}`,\n { cause },\n );\n this.name = 'PreviewFetchError';\n this.reason = reason;\n this.status = status;\n this.url = url;\n }\n}\n\n/** A scalar operand of a `where` operator; `null` is sent as the literal `null`. */\nexport type PreviewWhereValue = string | number | boolean | null | readonly (string | number)[];\n\n/**\n * A `where` tree in Payload's REST query shape: `{ slug: { equals: 'about' } }`,\n * `{ or: [{ … }, { … }] }`. Arrays of trees are indexed (`where[or][0][…]`).\n */\nexport interface PreviewWhere {\n readonly [key: string]: PreviewWhere | readonly PreviewWhere[] | PreviewWhereValue;\n}\n\n/** Options every read shares; `fetchDocument` and `fetchGlobal` add their target. */\nexport interface PreviewReadOptions {\n /**\n * The verdict from `authorizePreviewRequest()`. Only a real context reads the\n * draft; `null`, a copy or a JSON round trip reads the published document.\n */\n readonly authorization: AuthorizedPreviewContext | null;\n readonly locale?: string;\n /** Extra headers for this read; the context's headers win on conflict. */\n readonly headers?: Readonly<Record<string, string>>;\n /** Abort the read from the outside, e.g. with the request's own signal. */\n readonly signal?: AbortSignal;\n /** `'result'` (default) returns the failed result; `'throw'` throws `PreviewFetchError`. */\n readonly errorMode?: 'result' | 'throw';\n}\n\nexport interface ReadDocumentOptions extends PreviewReadOptions {\n readonly collection: string;\n readonly where?: PreviewWhere;\n}\n\nexport interface ReadGlobalOptions extends PreviewReadOptions {\n readonly global: string;\n}\n\n/** The runtime/adapter options this configuration implies — spread them into the adapter or client options. */\nexport interface PreviewRuntimeOptions {\n readonly serverURL: string;\n readonly apiRoute: string;\n readonly mergeDepth: number;\n}\n\nexport interface PreviewServer {\n readonly config: Readonly<\n Required<Pick<PreviewServerConfig, 'serverURL' | 'apiRoute' | 'depth' | 'timeoutMs'>>\n >;\n /** `{ serverURL, apiRoute, mergeDepth }` — the same depth the reads use. */\n readonly runtimeOptions: PreviewRuntimeOptions;\n fetchDocument<T = Record<string, unknown>>(\n options: ReadDocumentOptions,\n ): Promise<PreviewFetchResult<T>>;\n fetchGlobal<T = Record<string, unknown>>(\n options: ReadGlobalOptions,\n ): Promise<PreviewFetchResult<T>>;\n}\n\nconst DEFAULT_TIMEOUT_MS = 5_000;\nconst MIN_TIMEOUT_MS = 250;\n\n/** Bind origin, route and depth once; read documents and globals with an explicit authorization verdict. */\nexport function definePreview(config: PreviewServerConfig): PreviewServer {\n const serverURL = normalizeServerURL(config.serverURL);\n const route = config.apiRoute ?? '/api';\n const apiRoute = route.startsWith('/') ? route : `/${route}`;\n if (!Number.isInteger(config.depth) || config.depth < 0) {\n throw new Error(\n `payload-live-preview: depth must be a non-negative integer, got ${String(config.depth)}`,\n );\n }\n const timeoutMs = Math.max(MIN_TIMEOUT_MS, config.timeoutMs ?? DEFAULT_TIMEOUT_MS);\n const resolved = Object.freeze({ serverURL, apiRoute, depth: config.depth, timeoutMs });\n const runtimeOptions = Object.freeze({ serverURL, apiRoute, mergeDepth: config.depth });\n const base = `${serverURL}${apiRoute}`;\n const settings: ReadSettings = { config, timeoutMs };\n\n return Object.freeze({\n config: resolved,\n runtimeOptions,\n async fetchDocument<T = Record<string, unknown>>(\n options: ReadDocumentOptions,\n ): Promise<PreviewFetchResult<T>> {\n const draft = draftFor(options);\n const query = params(config.depth, options, draft);\n query.set('limit', '1');\n if (options.where !== undefined) appendWhere(query, options.where, ['where']);\n const url = `${base}/${encodeURIComponent(options.collection)}?${query.toString()}`;\n const result = await read<{ docs?: T[] }>(settings, url, options, draft);\n if (!result.ok) return result;\n const first = result.data?.docs?.[0];\n return { ok: true, data: first ?? null, draft, status: result.status };\n },\n async fetchGlobal<T = Record<string, unknown>>(\n options: ReadGlobalOptions,\n ): Promise<PreviewFetchResult<T>> {\n const draft = draftFor(options);\n const query = params(config.depth, options, draft);\n const url = `${base}/globals/${encodeURIComponent(options.global)}?${query.toString()}`;\n return read<T>(settings, url, options, draft);\n },\n });\n}\n\n/** What every read takes from the bound configuration. */\ninterface ReadSettings {\n readonly config: PreviewServerConfig;\n readonly timeoutMs: number;\n}\n\n/** One read in flight: what the diagnostic and a thrown failure need to say. */\ninterface ReadAttempt {\n readonly config: PreviewServerConfig;\n readonly url: string;\n readonly draft: boolean;\n readonly errorMode: 'result' | 'throw';\n readonly startedAt: number;\n}\n\nfunction draftFor(options: PreviewReadOptions): boolean {\n return isAuthorizedPreviewContext(options.authorization);\n}\n\nfunction params(depth: number, options: PreviewReadOptions, draft: boolean): URLSearchParams {\n const query = new URLSearchParams();\n query.set('depth', String(depth));\n if (draft) query.set('draft', 'true');\n if (options.locale !== undefined) query.set('locale', options.locale);\n return query;\n}\n\n/** One GET against the REST API; every exit reports to `onDiagnostic` and is shaped as a result. */\nasync function read<T>(\n { config, timeoutMs }: ReadSettings,\n url: string,\n options: PreviewReadOptions,\n draft: boolean,\n): Promise<PreviewFetchResult<T>> {\n const fetchImpl: PreviewFetchFunction | undefined =\n config.fetch ?? (typeof fetch === 'function' ? fetch : undefined);\n const attempt: ReadAttempt = {\n config,\n url,\n draft,\n errorMode: options.errorMode ?? 'result',\n startedAt: Date.now(),\n };\n if (fetchImpl === undefined) {\n return settle(attempt, {\n ok: false,\n reason: 'no-fetch',\n status: undefined,\n draft,\n cause: undefined,\n });\n }\n const timeout = AbortSignal.timeout(timeoutMs);\n const signal =\n options.signal === undefined ? timeout : AbortSignal.any([timeout, options.signal]);\n const context = isAuthorizedPreviewContext(options.authorization) ? options.authorization : null;\n let response: Awaited<ReturnType<PreviewFetchFunction>>;\n try {\n response = await fetchImpl(url, {\n headers: {\n accept: 'application/json',\n ...(options.headers ?? {}),\n ...(context?.payloadHeaders ?? {}),\n },\n signal,\n cache: 'no-store',\n redirect: 'error',\n });\n } catch (cause) {\n return settle(attempt, {\n ok: false,\n reason: failureReason(cause, options.signal),\n status: undefined,\n draft,\n cause,\n });\n }\n if (!response.ok) {\n return settle(attempt, {\n ok: false,\n reason: 'http',\n status: response.status,\n draft,\n cause: undefined,\n });\n }\n let body: unknown;\n try {\n body = await response.json();\n } catch (cause) {\n return settle(attempt, {\n ok: false,\n reason: 'invalid-json',\n status: response.status,\n draft,\n cause,\n });\n }\n return settle(attempt, { ok: true, data: body as T, draft, status: response.status });\n}\n\n/** Report the outcome, then hand it back — or throw it, in `errorMode: 'throw'`. */\nfunction settle<T>(attempt: ReadAttempt, result: PreviewFetchResult<T>): PreviewFetchResult<T> {\n const { config, url, draft } = attempt;\n const durationMs = Date.now() - attempt.startedAt;\n if (result.ok) {\n config.onDiagnostic?.({ kind: 'response', url, status: result.status, draft, durationMs });\n return result;\n }\n config.onDiagnostic?.({\n kind: 'failure',\n url,\n reason: result.reason,\n status: result.status,\n draft,\n durationMs,\n });\n if (attempt.errorMode === 'throw') {\n throw new PreviewFetchError(result.reason, url, result.status, result.cause);\n }\n return result;\n}\n\nfunction normalizeServerURL(serverURL: string): string {\n let url: URL;\n try {\n url = new URL(serverURL);\n } catch {\n throw new Error(`payload-live-preview: serverURL \"${serverURL}\" is not an absolute URL`);\n }\n if (url.protocol !== 'https:' && url.protocol !== 'http:') {\n throw new Error(`payload-live-preview: serverURL must be http(s), got \"${url.protocol}\"`);\n }\n return `${url.origin}${url.pathname.replace(/\\/+$/, '')}`;\n}\n\nfunction failureReason(\n cause: unknown,\n external: AbortSignal | undefined,\n): PreviewFetchFailureReason {\n if (external?.aborted === true) return 'aborted';\n const name =\n typeof cause === 'object' && cause !== null ? (cause as { name?: unknown }).name : undefined;\n if (name === 'TimeoutError') return 'timeout';\n if (name === 'AbortError') return 'aborted';\n return 'network';\n}\n\nfunction isWhereTree(value: unknown): value is PreviewWhere {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction queryName(path: readonly string[]): string {\n return path.map((segment, index) => (index === 0 ? segment : `[${segment}]`)).join('');\n}\n\nfunction appendWhere(query: URLSearchParams, node: PreviewWhere, path: readonly string[]): void {\n for (const [key, value] of Object.entries(node)) {\n const nextPath = [...path, key];\n if (isWhereTree(value)) {\n appendWhere(query, value, nextPath);\n } else if (Array.isArray(value) && value.some(isWhereTree)) {\n // `or` / `and`: Payload's query parser turns bracketed indices back into an array.\n (value as readonly PreviewWhere[]).forEach((branch, index) => {\n appendWhere(query, branch, [...nextPath, String(index)]);\n });\n } else if (Array.isArray(value)) {\n query.set(queryName(nextPath), (value as readonly (string | number)[]).map(String).join(','));\n } else {\n // Only `null` is still an object here; Payload's query sanitizers read the literal back.\n query.set(queryName(nextPath), typeof value === 'object' ? 'null' : String(value));\n }\n }\n}\n","/**\n * Authorized preview context: the one verdict every privileged preview\n * decision is keyed on. Intent (`?preview=true`, an iframe destination, a\n * referer) is client-chosen; this turns a session, a signed token or the\n * application's own verifier into a branded context. Threat model: ADR 0006.\n */\n\nimport {\n AUTHORIZED_PREVIEW_BRAND_KEY,\n createAuthorizedPreviewContext,\n isAuthorizedPreviewContext,\n type AuthorizedPreviewContext,\n type AuthorizedPreviewScope,\n type PreviewAuthorizationStrategyName,\n} from '@/types/authorized-preview';\nimport {\n refused,\n type PreviewAuthorization,\n type PreviewAuthorizationRequest,\n} from './preview-verdict';\nimport { authorizeSession, type PayloadSessionStrategy } from './preview-session';\nimport { authorizeToken, type SignedTokenStrategy } from './preview-token';\n\nexport {\n AUTHORIZED_PREVIEW_BRAND_KEY,\n isAuthorizedPreviewContext,\n type AuthorizedPreviewContext,\n type AuthorizedPreviewScope,\n type PreviewAuthorizationStrategyName,\n};\nexport {\n PreviewConfigurationError,\n type PreviewAuthorization,\n type PreviewAuthorizationOutcome,\n type PreviewAuthorizationRequest,\n} from './preview-verdict';\nexport { extractCookie, type FetchLike, type PayloadSessionStrategy } from './preview-session';\nexport {\n issuePreviewToken,\n type IssuePreviewTokenOptions,\n type PreviewTokenClaims,\n // eslint-disable-next-line @typescript-eslint/no-deprecated -- the 1.x shape exists to be re-exported\n type PreviewTokenReplayChecks,\n type PreviewTokenReplayStore,\n type PreviewTokenTransport,\n type SignedTokenStrategy,\n type SubtleCryptoLike,\n} from './preview-token';\n\n/** Claims a consumer verifier returns for a request it accepts. */\nexport interface PreviewVerifierClaims {\n readonly subject?: string;\n readonly expiresAt?: number;\n readonly scope?: AuthorizedPreviewScope;\n readonly payloadHeaders?: Readonly<Record<string, string>>;\n}\n\n/** The application's own verification: claims authorize, `null` refuses, a throw is `unavailable`. */\nexport interface VerifierStrategy {\n readonly type: 'verifier';\n readonly verify: (\n request: PreviewAuthorizationRequest,\n ) => Promise<PreviewVerifierClaims | null> | PreviewVerifierClaims | null;\n readonly now?: () => number;\n}\n\nexport type PreviewAuthorizationStrategy =\n PayloadSessionStrategy | SignedTokenStrategy | VerifierStrategy;\n\n/** Decide whether `request` is an authorized preview; refusals resolve to `{ authorized: false }` and only a `PreviewConfigurationError` throws. */\nexport async function authorizePreviewRequest(\n request: PreviewAuthorizationRequest,\n strategy: PreviewAuthorizationStrategy,\n): Promise<PreviewAuthorization> {\n switch (strategy.type) {\n case 'payload-session':\n return authorizeSession(request, strategy);\n case 'signed-token':\n return authorizeToken(request, strategy);\n case 'verifier':\n return authorizeVerifier(request, strategy);\n }\n}\n\nasync function authorizeVerifier(\n request: PreviewAuthorizationRequest,\n strategy: VerifierStrategy,\n): Promise<PreviewAuthorization> {\n let claims: PreviewVerifierClaims | null;\n try {\n claims = await strategy.verify(request);\n } catch {\n return refused('unavailable');\n }\n if (claims === null) return refused('invalid');\n const now = (strategy.now ?? Date.now)();\n if (claims.expiresAt !== undefined && claims.expiresAt <= now) return refused('expired');\n return {\n authorized: true,\n outcome: 'authorized',\n context: createAuthorizedPreviewContext({\n strategy: 'verifier',\n subject: claims.subject,\n authorizedAt: now,\n expiresAt: claims.expiresAt,\n scope: claims.scope ?? {},\n payloadHeaders: claims.payloadHeaders ?? {},\n }),\n };\n}\n","/** What a decision publishes for templates on `locals` (Astro, SvelteKit) or `event.context` (Nuxt). */\n\nimport { createPreviewBindings, type PreviewBindings } from '@dsl/preview-bindings';\nimport type { PreviewAuthorizationOutcome } from '@security/preview-verdict';\nimport type { AuthorizedPreviewContext } from '@/types/authorized-preview';\nimport type { PreviewDecision } from './policy';\n\n/**\n * The keys the adapters publish on the request-scoped context — `Astro.locals`,\n * SvelteKit's `event.locals`, Nuxt's `event.context`. Extend the framework's\n * `App.Locals` with it and templates read them typed. Every key is optional:\n * a refused preview withholds them all, and Next.js has no locals at all.\n */\nexport interface LivePreviewLocals {\n /**\n * The request-scoped CSP nonce, for templates that nonce their own scripts.\n * Set by the Astro middleware (prerendered pages included), the SvelteKit\n * handle and the Nuxt server handler or plugin on every request — except a\n * preview the `authorizePreview` hook refused, which gets no preview artefact.\n */\n readonly livePreviewNonce?: string;\n /** The verified `AuthorizedPreviewContext`. Set only when `authorizePreview` authorized an intent-bearing request. */\n readonly livePreviewAuthorization?: AuthorizedPreviewContext;\n /** The hook's `PreviewAuthorizationOutcome`, refusals included. Set whenever the hook ran: on intent, with `authorizePreview` configured. */\n readonly livePreviewAuthorizationOutcome?: PreviewAuthorizationOutcome;\n}\n\n/** The request-scoped nonce, for templates that nonce their own scripts. @internal */\nexport const NONCE_LOCALS_KEY = 'livePreviewNonce' satisfies keyof LivePreviewLocals;\n\n/** The verified `AuthorizedPreviewContext`; absent unless `authorizePreview` authorized. @internal */\nexport const AUTHORIZATION_LOCALS_KEY =\n 'livePreviewAuthorization' satisfies keyof LivePreviewLocals;\n\n/** The hook's `PreviewAuthorizationOutcome`; absent when no hook ran. @internal */\nexport const AUTHORIZATION_OUTCOME_LOCALS_KEY =\n 'livePreviewAuthorizationOutcome' satisfies keyof LivePreviewLocals;\n\n/**\n * What an adapter needs of a framework's request context: the three keys it\n * writes, and nothing else.\n *\n * This is what the adapters' event types declare rather than\n * `Record<string, unknown>`. A framework's own `App.Locals` is an interface\n * without an index signature, so it is not assignable to a record — which made\n * composing our handle with SvelteKit's `Handle` type fail to compile, even\n * though the values matched. The adapters assign through the interface's own\n * keys, so the published type cannot drift from what the code writes either.\n */\nexport type LivePreviewLocalsSink = {\n -readonly [K in keyof LivePreviewLocals]: LivePreviewLocals[K];\n};\n\n/** Publish the nonce alone — a prerendered page has no request to decide for. */\nexport function exposeNonce(locals: LivePreviewLocalsSink, nonce: string): void {\n locals.livePreviewNonce = nonce;\n}\n\n/** Publish what templates may read; after a refusal the nonce is withheld like every other preview artefact. */\nexport function exposeDecision(\n locals: LivePreviewLocalsSink,\n decision: PreviewDecision,\n nonce: string,\n): void {\n if (decision.exposeNonce || !decision.isPreview) locals.livePreviewNonce = nonce;\n if (decision.authorization !== null) locals.livePreviewAuthorization = decision.authorization;\n if (decision.outcome !== undefined) locals.livePreviewAuthorizationOutcome = decision.outcome;\n}\n\n/**\n * The binding helpers for one request, read straight off whatever the framework\n * calls locals — `Astro.locals`, SvelteKit's `event.locals`, Nuxt's\n * `event.context`.\n *\n * `createPreviewBindings({ authorization: locals.livePreviewAuthorization })`\n * says the same thing, and a template that spells it out is welcome to. This\n * exists because generated code should not have to: the build-time annotator\n * writes one call per component, and a helper that takes `unknown` needs no\n * `App.Locals` declaration in the project it writes into. The verdict is\n * checked, not trusted — only a context `authorizePreviewRequest()` produced\n * emits anything (ADR 0006).\n * @internal\n */\nexport function previewBindingsFromLocals(\n locals: unknown,\n options: { readonly owner?: string } = {},\n): PreviewBindings {\n const value =\n typeof locals === 'object' && locals !== null\n ? (locals as Record<string, unknown>)[AUTHORIZATION_LOCALS_KEY]\n : undefined;\n return createPreviewBindings({\n authorization: (value ?? null) as AuthorizedPreviewContext | null,\n ...(options.owner !== undefined ? { owner: options.owner } : {}),\n });\n}\n"],"mappings":"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","ignoreList":[]}
|
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.get(e);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}),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};
|
|
2
2
|
//# sourceMappingURL=server.js.map
|