payload-live-preview 2.2.2 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -30
- package/dist/adapters/astro/PreviewBoundary.astro +17 -4
- package/dist/adapters/astro/RichText.astro +13 -2
- package/dist/adapters/astro/index.d.ts +7 -7
- package/dist/adapters/astro/index.js +1 -1
- package/dist/adapters/astro/index.js.map +1 -1
- package/dist/adapters/astro/middleware-entry.d.ts +3 -3
- package/dist/adapters/astro/middleware-entry.js +1 -1
- package/dist/adapters/astro/middleware-entry.js.map +1 -1
- package/dist/adapters/nextjs/index.d.ts +2 -2
- package/dist/adapters/nextjs/index.js +1 -1
- package/dist/adapters/nextjs/index.js.map +1 -1
- package/dist/adapters/nuxt/index.d.ts +4 -4
- package/dist/adapters/nuxt/index.js +1 -1
- package/dist/adapters/nuxt/index.js.map +1 -1
- package/dist/adapters/nuxt/module.d.ts +1 -1
- package/dist/adapters/react/index.d.ts +2 -2
- package/dist/adapters/react/index.js +1 -1
- package/dist/adapters/react/index.js.map +1 -1
- package/dist/adapters/sveltekit/index.d.ts +5 -5
- package/dist/adapters/sveltekit/index.js +1 -1
- package/dist/adapters/sveltekit/index.js.map +1 -1
- package/dist/adapters/vue/index.d.ts +2 -2
- package/dist/adapters/vue/index.js +1 -1
- package/dist/adapters/vue/index.js.map +1 -1
- package/dist/client.cjs +1 -1
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +15 -3
- package/dist/client.d.ts +15 -3
- package/dist/client.js +1 -1
- package/dist/client.js.map +1 -1
- package/dist/codegen-astro.js +1 -1
- package/dist/codegen-astro.js.map +1 -1
- package/dist/codegen-cli.js +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.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 +139 -4
- package/dist/core.d.ts +139 -4
- package/dist/core.js +1 -1
- package/dist/core.js.map +1 -1
- package/dist/doctor-cli.d.ts +1 -1
- package/dist/doctor-cli.js +1 -1
- package/dist/doctor-cli.js.map +1 -1
- package/dist/doctor.d.ts +2 -2
- package/dist/doctor.js +1 -1
- package/dist/doctor.js.map +1 -1
- package/dist/{document-session-Bkd181PV.d.ts → document-session-BuYuO3ae.d.ts} +27 -2
- package/dist/{fragment-endpoint-CLRpNQ3n.d.ts → fragment-endpoint-CQE_7MKi.d.ts} +1 -1
- package/dist/fragment.cjs +1 -1
- package/dist/fragment.cjs.map +1 -1
- package/dist/fragment.d.cts +6 -2
- package/dist/fragment.d.ts +6 -2
- 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 +73 -12
- package/dist/index.d.ts +73 -12
- 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.d.cts +16 -4
- package/dist/lexical.d.ts +16 -4
- package/dist/lexical.js +1 -1
- package/dist/lexical.js.map +1 -1
- package/dist/{locals-zz2gi9dj.d.ts → locals-CyzDKfKU.d.ts} +7 -1
- package/dist/{middleware-CI-UTf6R.d.ts → middleware-Dnt6u4nF.d.ts} +2 -2
- package/dist/{options-CnYybG51.d.ts → options-DTzhFVJ4.d.ts} +13 -1
- package/dist/plugin.cjs +1 -1
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.d.cts +15 -9
- package/dist/plugin.d.ts +15 -9
- package/dist/plugin.js +1 -1
- package/dist/plugin.js.map +1 -1
- package/dist/plugins.cjs +1 -1
- package/dist/plugins.cjs.map +1 -1
- package/dist/plugins.d.cts +8 -2
- package/dist/plugins.d.ts +8 -2
- package/dist/plugins.js +1 -1
- package/dist/plugins.js.map +1 -1
- package/dist/{probe-D-IHg9dF.d.ts → probe-afwdYebU.d.ts} +6 -2
- package/dist/server.cjs +1 -1
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +19 -1
- package/dist/server.d.ts +19 -1
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/dist/structural.d.cts +2 -0
- package/dist/structural.d.ts +2 -0
- package/package.json +2 -1
package/dist/doctor.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.js","names":["DIAGNOSTIC_CODES","Object","freeze","NoTrustedOrigin","ReferrerOnlyTrust","PluginIncompatible","ProfileFeatureOmitted","OrphanField","UnattributableUpdate","UnboundField","VisibilityGateDeferred","UnsafeAttributeWrite","TextTargetHasChildren","MissingArrayTemplate","StructuralItemUnkeyed","StructuralDuplicateKey","StructuralUnstableKeys","UnsupportedStrategy","UnknownValueFormat","SanitizerDroppedAttribute","UnrenderedBlockKept","UnfaithfulPatch","ServerFormatReplaced","UnrenderedBlockLost","MessageRejected","TokenRejected","ProtocolShapeUnknown","HandlerThrew","TransformThrew","RendererThrew","StartupFailed","ReadyFailed","HydrationWaitTimedOut","AuditRuntimeMissing","AuditNoFrameAncestors","AuditFrameOptionsBlocks","AuditBindingsExposed","AuditGateThresholdExceeded","AuditUnownedBindings","AuditNoBindings","AuditNotAPage","FragmentRequestFailed","FragmentResponseInvalid","FragmentUnauthorized","FragmentSuperseded","RouteRefreshLoop","FragmentStrategyUnavailable","UnboundChangeRefresh","EscalationUnavailable","IslandStartUnavailable","RouteRefreshUnconfirmed","V2ReadinessGap","RuntimeOnPublicPage","DEFAULT_PORT","http","https","ws","wss","SCHEME_SOURCE","HOST_SOURCE","schemeOf","url","protocol","slice","toLowerCase","portOf","port","schemeMatches","expression","actual","isSelf","admin","page","hostname","adminScheme","pageScheme","sourceMatches","source","scheme","exec","host","expressionScheme","expressionHost","expressionPort","pattern","wanted","replace","startsWith","endsWith","length","hostMatches","INLINE_CONFIG_KEYS","V2_RUNTIME_DEFAULTS","disableReferrerDetection","eventSourcePolicy","skipUnchanged","sanitizerPolicy","V1_RUNTIME_DEFAULTS","CONFIG_NAME","CONFIG_MARKER","COMMENT_OR_SCRIPT","CONFIG_ID","JSON_TYPE","ROWS","key","title","effect","condition","ORIGIN_DETAIL","profile","explicit","unmarked","UNREADABLE","code","level","detail","remedy","UNMARKED","readServedConfig","body","includes","scripts","attributes","text","matchAll","push","servedScripts","inline","find","tuple","start","indexOf","from","depth","inString","previous","json","index","char","test","value","JSON","parse","Array","isArray","readInlineConfig","named","readConfigElement","hasOwn","literal","stringify","remedyFor","origin","row","gap","unknownGeneration","marker","analyzeV2Readiness","probe","config","previewResponse","generation","fallback","findings","flatMap","slot","RUNTIME_MARKER","START_TAG","TAG_ATTRIBUTE","LEVEL_ORDER","error","warning","info","NON_MARKUP","visibleMarkup","markup","stripped","markupAttributes","found","tag","name","assigned","double","single","bare","at","filter","attribute","tryUrl","URL","analyzeProbe","context","publicResponse","pub","preview","notAPage","status","location","headers","String","trim","contentType","describeNonPage","errors","warnings","runtimeInPreview","credentials","adminOrigin","sameOrigin","frameAncestors","csp","directive","split","trimmed","frameAncestorsOf","admitted","sources","map","frameAncestorsAdmits","frameOptions","judged","framingFindings","previewMarkup","publicMarkup","runtime","gate","previewBindings","publicBindings","owners","boundaries","boundary","threshold","configured","unowned","firstOwner","binding","unownedBindingsBeforeFirstOwner","bindingFindings","servedGate","v2","sort","a","b","finding","LEVEL_LABEL","formatReport","report","lines","join","DEFAULT_INTENT_PARAMS","splitUrl","hashAt","hash","beforeHash","queryAt","base","parts","decodePart","part","decode","decodeURIComponent","TOKEN_PARAM","redactToken","tokenParam","shown","some","carriesToken","lowercaseHeaders","out","forEach","async","readBody","response","limit","reader","getReader","decoder","TextDecoder","received","done","read","byteLength","cancel","Error","stream","previewReferer","href","runDoctor","options","fetchImpl","timeoutMs","maxBodyBytes","init","fetchFn","globalThis","fetch","redirect","signal","AbortSignal","timeout","cause","createDefaultFetch","params","previewQueryParams","tokenQueryParam","dropped","Set","kept","has","visitorUrl","Accept","probeHeaders","Referer","sent","own","taken","keys","fromEntries","entries","callerHeaders","previewHeaders","isIntentValue","query","joiner","intent","encodeURIComponent","previewUrl"],"sources":["../src/core/diagnostic-codes.ts","../src/doctor/csp.ts","../src/types/inline-config.ts","../src/types/defaults-profile.ts","../src/doctor/readiness.ts","../src/doctor/analyze.ts","../src/doctor/format.ts","../src/doctor/probe.ts"],"sourcesContent":["/**\n * Every diagnostic code the runtime and tools emit, so a consumer can branch\n * on `e.code` instead of matching message text. A code is never reused for a\n * different meaning; a retired one stays reserved.\n *\n * Browser-side reporting sites write the literal (`code: 'LP0603'`) rather than\n * reading it from this record: the frozen table would then ship in the inline\n * runtime for the sake of six characters — it did, 1 287 B in every page, for\n * the one read Z6 left in the strategy runner, and `diagnostic-codes.test.ts`\n * now holds the artifact free of it. Server-side tools may import it.\n */\n\n/**\n * Every diagnostic code the runtime can report, by name.\n *\n * ```ts\n * client.events.on('error', (e) => {\n * if (e.code === DIAGNOSTIC_CODES.TransformThrew) reportToSentry(e.error);\n * });\n * ```\n */\nexport const DIAGNOSTIC_CODES = Object.freeze({\n /** No trusted origin configured in production; nothing will be accepted. */\n NoTrustedOrigin: 'LP0101',\n /** Origin trust rests on `document.referrer`, so any framing site is trusted. */\n ReferrerOnlyTrust: 'LP0102',\n /** A plugin declares a runtime range this runtime does not satisfy; it was refused. */\n PluginIncompatible: 'LP0103',\n /** The page carries the lean runtime and needs a feature that profile leaves out. */\n ProfileFeatureOmitted: 'LP0104',\n\n /** An update named an empty field with no binding anchor on the page; an edit to it has nowhere to land. */\n OrphanField: 'LP0201',\n /** Owner scoping is on and the update names no document it could belong to. */\n UnattributableUpdate: 'LP0202',\n /** An update carried a value for a field the page binds nowhere; editing it changes nothing on this page. */\n UnboundField: 'LP0203',\n\n /** The visibility gate held offscreen writes back until they scroll into view. */\n VisibilityGateDeferred: 'LP0301',\n\n /** A value was refused because the attribute or the value itself is unsafe. */\n UnsafeAttributeWrite: 'LP0401',\n /** A text element has structured children, so replacing its text was skipped. */\n TextTargetHasChildren: 'LP0402',\n /** A structural container has no array template, so the update was skipped. */\n MissingArrayTemplate: 'LP0403',\n /** A structural item has no `id`; it pairs positionally, so an insert re-renders every row after it. */\n StructuralItemUnkeyed: 'LP0404',\n /** Two structural items share a key; later ones pair positionally. */\n StructuralDuplicateKey: 'LP0405',\n /** Every structural key changed while the length did not; the source generates keys per message. */\n StructuralUnstableKeys: 'LP0406',\n /** A binding asks for a delivery strategy this release does not have; it is left unchanged. */\n UnsupportedStrategy: 'LP0407',\n /** `data-payload-format` names a format the vocabulary does not contain; the default formatting was used instead. */\n UnknownValueFormat: 'LP0408',\n /** The strict sanitizer removed an attribute the 1.x `'compat'` policy kept. */\n SanitizerDroppedAttribute: 'LP0409',\n /** A Lexical block has no renderer; the markup the server rendered for it is kept instead. */\n UnrenderedBlockKept: 'LP0410',\n /** A patch cannot reach what the server would have drawn; `onUnfaithfulPatch` decided what happens next. */\n UnfaithfulPatch: 'LP0411',\n /** The first write to a date, number or checkbox binding replaced a different reading of the same value. */\n ServerFormatReplaced: 'LP0412',\n /** A Lexical block has no renderer, and the write could not keep the markup the server rendered for it; it is gone. */\n UnrenderedBlockLost: 'LP0413',\n\n /** A message was rejected before it reached the update pipeline. */\n MessageRejected: 'LP0501',\n /** A preview token was rejected. */\n TokenRejected: 'LP0502',\n /** A trusted origin sent a message this protocol version does not recognise. */\n ProtocolShapeUnknown: 'LP0503',\n\n /** A consumer event handler threw. */\n HandlerThrew: 'LP0601',\n /** A consumer transform threw; the original value was kept. */\n TransformThrew: 'LP0602',\n /** A renderer threw while writing a value. */\n RendererThrew: 'LP0603',\n // LP0604 is reserved and unassigned; codes are never reused or renumbered.\n /** Runtime startup failed. */\n StartupFailed: 'LP0605',\n /** Sending the ready handshake failed. */\n ReadyFailed: 'LP0606',\n /** The page declared hydration, but no React commit, Vue mount or SvelteKit mount came within the cap; the runtime started without waiting. */\n HydrationWaitTimedOut: 'LP0607',\n\n /** The audit found no runtime in a response that carried preview intent. */\n AuditRuntimeMissing: 'LP0701',\n /** The preview response declares no `frame-ancestors`. */\n AuditNoFrameAncestors: 'LP0702',\n /** `X-Frame-Options` forbids framing on a response that declares no `frame-ancestors`; `SAMEORIGIN` forbids it from another origin only. */\n AuditFrameOptionsBlocks: 'LP0703',\n /** Binding attributes are served to anonymous visitors. */\n AuditBindingsExposed: 'LP0704',\n /** More bindings than the visibility gate will write eagerly. */\n AuditGateThresholdExceeded: 'LP0705',\n /** Owner markers exist, but some bindings are outside all of them. */\n AuditUnownedBindings: 'LP0706',\n /** The preview response carries no bindings and no fragment boundary. */\n AuditNoBindings: 'LP0707',\n /** The URL did not return an HTML page, so nothing else can be judged. */\n AuditNotAPage: 'LP0708',\n /** A fragment request failed (network, timeout, server error); the boundary was patched instead. */\n FragmentRequestFailed: 'LP0801',\n /** The fragment response had the wrong content type or shape; the boundary was patched instead. */\n FragmentResponseInvalid: 'LP0802',\n /** The fragment endpoint refused the preview (not authorized); the boundary was patched instead. */\n FragmentUnauthorized: 'LP0803',\n /** A fragment response arrived for a revision that was already superseded and was discarded. */\n FragmentSuperseded: 'LP0804',\n /** A route refresh was refused: the loop guard, or the strategy's minimum interval. */\n RouteRefreshLoop: 'LP0805',\n /** A boundary asks for the fragment strategy but no fragment handler is configured; it is patched. */\n FragmentStrategyUnavailable: 'LP0806',\n /** A revision changed a field with no binding; `onUnfaithfulPatch` refreshed the route. */\n UnboundChangeRefresh: 'LP0807',\n /** `onUnfaithfulPatch: 'escalate'` found no route or fragment strategy, or no boundary, to hand an unfaithful patch to; the page keeps it. */\n EscalationUnavailable: 'LP0808',\n /** A fragment inserted an Astro island the page cannot start: it never loaded the island element or that client directive. */\n IslandStartUnavailable: 'LP0809',\n /** The host's route refresh returned no promise, so the revision was re-applied before the host committed. */\n RouteRefreshUnconfirmed: 'LP0810',\n /** A readiness row is not yet at its 2.0 value; `pll doctor --v2` reports it. */\n V2ReadinessGap: 'LP0709',\n /** The preview runtime is served to anonymous visitors, not only inside the admin frame. */\n RuntimeOnPublicPage: 'LP0710',\n} as const);\n\n/** A diagnostic code, as it appears in log output and on the `error` event. */\nexport type DiagnosticCode = (typeof DIAGNOSTIC_CODES)[keyof typeof DIAGNOSTIC_CODES];\n","/**\n * `frame-ancestors` matching by the CSP3 source-expression rules. The audit\n * judges origins, so path parts of a host-source are ignored.\n */\n\nconst DEFAULT_PORT: Readonly<Record<string, string>> = {\n http: '80',\n https: '443',\n ws: '80',\n wss: '443',\n};\nconst SCHEME_SOURCE = /^([a-z][a-z0-9+.-]*):$/u;\nconst HOST_SOURCE =\n /^(?:([a-z][a-z0-9+.-]*):\\/\\/)?(\\*|(?:\\*\\.)?[a-z0-9-]+(?:\\.[a-z0-9-]+)*\\.?)(?::(\\*|\\d+))?(?:\\/.*)?$/u;\n\n/** The `frame-ancestors` directive of a policy, verbatim, or `undefined` when it has none. */\nexport function frameAncestorsOf(csp: string | undefined): string | undefined {\n if (csp === undefined) return undefined;\n for (const directive of csp.split(';')) {\n const trimmed = directive.trim();\n if (/^frame-ancestors(?:\\s|$)/iu.test(trimmed)) return trimmed;\n }\n return undefined;\n}\n\nfunction schemeOf(url: URL): string {\n return url.protocol.slice(0, -1).toLowerCase();\n}\n\nfunction portOf(url: URL): string {\n return url.port === '' ? (DEFAULT_PORT[schemeOf(url)] ?? '') : url.port;\n}\n\nfunction schemeMatches(expression: string, actual: string): boolean {\n return (\n expression === actual ||\n (expression === 'http' && actual === 'https') ||\n (expression === 'ws' && actual === 'wss')\n );\n}\n\nfunction hostMatches(pattern: string, host: string): boolean {\n if (pattern === '*') return true;\n const wanted = pattern.replace(/\\.$/u, '');\n const actual = host.replace(/\\.$/u, '');\n if (wanted.startsWith('*.')) {\n return actual.endsWith(wanted.slice(1)) && actual.length > wanted.length - 1;\n }\n return wanted === actual;\n}\n\nfunction isSelf(admin: URL, page: URL): boolean {\n if (admin.hostname !== page.hostname) return false;\n const adminScheme = schemeOf(admin);\n const pageScheme = schemeOf(page);\n if (adminScheme !== pageScheme && !(pageScheme === 'http' && adminScheme === 'https')) {\n return false;\n }\n return portOf(admin) === portOf(page) || (admin.port === '' && page.port === '');\n}\n\nfunction sourceMatches(source: string, admin: URL, page: URL): boolean {\n const scheme = SCHEME_SOURCE.exec(source);\n if (scheme?.[1] !== undefined) return schemeMatches(scheme[1], schemeOf(admin));\n const host = HOST_SOURCE.exec(source);\n if (host === null) return false;\n const expressionScheme = host[1];\n const expressionHost = host[2] ?? '';\n const expressionPort = host[3];\n const requiredScheme = expressionScheme ?? schemeOf(page);\n if (!schemeMatches(requiredScheme, schemeOf(admin))) return false;\n if (!hostMatches(expressionHost, admin.hostname)) return false;\n if (expressionPort === undefined) return admin.port === '';\n return expressionPort === '*' || expressionPort === portOf(admin);\n}\n\n/** Whether the directive admits `admin` as an ancestor of `page`. */\nexport function frameAncestorsAdmits(directive: string, admin: URL, page: URL): boolean {\n const sources = directive\n .split(/\\s+/u)\n .slice(1)\n .filter((source) => source.length > 0)\n .map((source) => source.toLowerCase());\n if (sources.length === 0 || (sources.length === 1 && sources[0] === \"'none'\")) return false;\n for (const source of sources) {\n if (source === \"'self'\") {\n if (isSelf(admin, page)) return true;\n } else if (source === '*') {\n return true;\n } else if (!source.startsWith(\"'\") && sourceMatches(source, admin, page)) {\n return true;\n }\n }\n return false;\n}\n","/**\n * The inline script's configuration and its positional wire order: the\n * generator writes the tuple, `src/core/runtime.ts` destructures it, and\n * `INLINE_CONFIG_KEYS` is where they agree. Append only, never reorder.\n */\n\nimport type { DefaultsProfile } from '@/types/defaults-profile';\n\nexport interface InlineScriptConfig {\n /** Trusted admin origins, merged with the detected ones. */\n readonly allowedOrigins?: readonly string[];\n /** Payload origin; every update is then re-fetched through the REST API with credentials. Requires `mergeDepth`. */\n readonly serverURL?: string;\n /** REST route prefix used with `serverURL`. Default `/api`. */\n readonly apiRoute?: string;\n /** Population depth used with `serverURL`. Required with `serverURL` unless `defaults: 'v1'`. */\n readonly mergeDepth?: number;\n /** Verbose console logging. Default `false`. */\n readonly debug?: boolean;\n /** Debounce window for incoming updates in ms. Default `50`. */\n readonly debounceMs?: number;\n /** Mount the screen-reader live region. Default `true`. */\n readonly enableA11y?: boolean;\n /** Heartbeat timeout in ms; `0` disables it (default), because the admin posts only on edits. */\n readonly heartbeatMs?: number;\n /** Apply every update regardless of viewport position. Default `false`. */\n readonly disableVisibilityGate?: boolean;\n /** Cache size above which off-screen updates wait for intersection. Default `50`. */\n readonly visibilityGateThreshold?: number;\n /** `rootMargin` of the IntersectionObserver. Default `'200px'`. */\n readonly intersectionRootMargin?: string;\n /** Ignore `document.referrer` as an origin source. Default `true`; `defaults: 'v1'` sets `false`. */\n readonly disableReferrerDetection?: boolean;\n /** Disable the dev-mode localhost matcher (any port on `localhost`/`127.0.0.1`). Default `false`. */\n readonly disableLocalhostMatching?: boolean;\n /** Update only bindings under a matching `data-payload-owner`. Default `false`. */\n readonly scopeBindingsByOwner?: boolean;\n /** Skip bindings whose value did not change. Default `true`; `defaults: 'v1'` sets `false`. */\n readonly skipUnchanged?: boolean;\n /** Scroll the preview to the field being edited. Default `false`. */\n readonly revealEditedField?: boolean;\n /** Which windows may post updates. Default `'parent-or-opener'`; `defaults: 'v1'` sets `'any'`. */\n readonly eventSourcePolicy?: 'any' | 'parent-or-opener';\n /** Sanitizer policy for rich text and HTML writes. Default `'strict'`; `defaults: 'v1'` sets `'compat'`. */\n readonly sanitizerPolicy?: 'compat' | 'strict';\n /** Same-origin path of a fragment endpoint; the script then carries the fragment client ahead of the runtime (ADR 0011). */\n readonly fragmentEndpoint?: string;\n /**\n * Carry the route strategy, so a binding in `<head>` or one marked\n * `data-payload-strategy=\"route\"` refreshes the route. `fragmentEndpoint`\n * implies it — the fragment prelude already contains it. Default `false`.\n */\n readonly routeStrategy?: boolean;\n /**\n * The 2.0 name for `onUnfaithfulPatch`, kept until 3.0. `'route'` means\n * `'escalate'`, `'ignore'` means `'ignore'`.\n *\n * @deprecated Renamed to `onUnfaithfulPatch`.\n */\n readonly onUnboundChange?: 'ignore' | 'route';\n /**\n * What to do when the runtime knows a patch cannot reach what the server\n * would have drawn. `'escalate'` (the default) hands the region to the\n * fragment strategy when a boundary covers it and to the route otherwise, so\n * it needs `fragmentEndpoint` or `routeStrategy` to do anything. `'warn'`\n * reports LP0411 and keeps the patch; `'ignore'` keeps it silently.\n */\n readonly onUnfaithfulPatch?: 'ignore' | 'warn' | 'escalate';\n /**\n * Find bindings by value on the connection's first message (ADR 0014): a\n * scalar whose value is the whole content of exactly one element is bound\n * to it as if `data-payload-field` stood there. Default `'off'`.\n */\n readonly autoBind?: 'off' | 'unique';\n /**\n * The framework that hydrates this page (ADR 0015). Under `'react'` the\n * runtime holds its first write until React has committed the tree that\n * holds the bindings — otherwise React finds markup it did not render,\n * throws `Hydration failed` and regenerates the tree, and the write is gone.\n * Under `'vue'` it holds the write until Vue has mounted the app around them\n * — otherwise Vue's hydration repairs the write back to the server's value,\n * quietly. Under `'sveltekit'` it holds the write until SvelteKit's root has\n * mounted, on a page SvelteKit's client runs on — otherwise Svelte's\n * hydration sets its own text back over it, as quietly. The Next.js, Nuxt\n * and SvelteKit adapters set it on every script they emit; a page built by\n * hand with `generateInlineScript()` may. Omitted: the runtime starts on\n * `DOMContentLoaded`, as on a static page.\n */\n readonly hydration?: 'react' | 'vue' | 'sveltekit';\n /**\n * Which defaults the omitted options fall back to, resolved by the generator\n * rather than the runtime, whose own fallbacks are the 2.0 rows: `'v1'`\n * writes its four runtime rows into their slots (an explicit option still\n * wins) and relaxes the `mergeDepth` check. The resolved value always\n * travels in slot 24, so a reader of the served page knows what an\n * empty slot means instead of guessing it — `pll doctor --v2` reads it. The\n * runtime does not use that value: every row the profile decides is already\n * in its slot. Slot 24 stays fixed as later options are appended.\n */\n readonly defaults?: DefaultsProfile;\n /**\n * Document events fired after a client router committed new markup. Each\n * rebuilds the binding cache and locally reapplies the last accepted state.\n * Adapter integrations set this from their framework lifecycle. It is a\n * low-level generator fact, not a `PreviewAdapterOptions` field. Empty by\n * default.\n */\n readonly softNavigationEvents?: readonly string[];\n /**\n * How a group counts as covered (ADR 0022). `'descendant'`, the 2.x default:\n * one bound path inside it covers all of it. `'declared'`: each changed path\n * inside such a group needs its own binding or a `data-payload-covers`\n * declaration; an uncovered one is an unbound change.\n */\n readonly subfieldCoverage?: 'descendant' | 'declared';\n /**\n * A runtime artifact to embed instead of the full one — today only\n * `LEAN_RUNTIME` from `payload-live-preview/lean`, which leaves out the\n * strategies, the keyed morph, the structural arrays, the item templates, the\n * announcer and auto-binding (about 7 KB gzip less on the page) and reports\n * LP0104 when a page needs one of them.\n *\n * It is an imported value rather than a `profile: 'lean'` string on purpose:\n * a second artifact behind a string option would sit in every build that can\n * reach the generator; measured, it grew each adapter entry by the artifact's\n * whole size. This way the bytes follow the import.\n *\n * Not serialized: it decides which bytes are emitted, not how they behave.\n */\n readonly runtime?: RuntimeArtifact;\n}\n\n/**\n * A runtime build this package produces. `source` is the IIFE the page runs;\n * the two digests describe the same bytes as a servable asset, so asset\n * delivery can hash-name and SRI-verify whichever artifact was chosen without\n * hashing anything at request time.\n */\nexport interface RuntimeArtifact {\n readonly profile: 'lean';\n readonly source: string;\n /** Short content hash, for the asset's file name. */\n readonly contentHash: string;\n /** `sha384-…`, for the bootstrap's `integrity` attribute. */\n readonly integrity: string;\n}\n\n/** Keys that travel in the wire tuple, in slot order. */\nexport const INLINE_CONFIG_KEYS = [\n 'allowedOrigins',\n 'serverURL',\n 'apiRoute',\n 'mergeDepth',\n 'debug',\n 'debounceMs',\n 'enableA11y',\n 'heartbeatMs',\n 'disableVisibilityGate',\n 'visibilityGateThreshold',\n 'intersectionRootMargin',\n 'disableReferrerDetection',\n 'disableLocalhostMatching',\n 'scopeBindingsByOwner',\n 'skipUnchanged',\n 'eventSourcePolicy',\n 'sanitizerPolicy',\n 'fragmentEndpoint',\n 'revealEditedField',\n 'routeStrategy',\n 'onUnboundChange',\n 'onUnfaithfulPatch',\n 'autoBind',\n 'hydration',\n 'defaults',\n 'softNavigationEvents',\n 'subfieldCoverage',\n] as const satisfies readonly Exclude<keyof InlineScriptConfig, 'runtime'>[];\n","/**\n * The two defaults profiles, row by row. 2.0 ships `v2`; `defaults: 'v1'` opts\n * back into the 1.x table for a staged migration. Each row belongs to exactly\n * one side — the adapter's request decision or the browser runtime — and a\n * test asserts the profile assigns every readiness row that has an option, so\n * a row added later cannot be forgotten. See ADR 0007.\n *\n * It lives in the leaf `types` domain, like `merge-depth.ts`, because the\n * inline generator resolves the profile too, and the generator is server-side\n * code that may not reach the browser runtime in `core`.\n */\n\n/** `'v2'` is the 2.0 default; `'v1'` is the 1.x table. */\nexport type DefaultsProfile = 'v1' | 'v2';\n\n/** How the runtime decides which windows may post updates. */\nexport type EventSourcePolicy = 'any' | 'parent-or-opener';\n\n/** Readiness rows that are options, keyed by the option they set. */\nexport const READINESS_ROWS = Object.freeze({\n strict: 'production response changes require an authorized context',\n previewSignals: 'query-only intent signal',\n disableReferrerDetection: 'referrer trust off',\n eventSourcePolicy: 'messages must come from parent/opener',\n skipUnchanged: 'skip unchanged bindings by default',\n sanitizerPolicy: 'hardened sanitizer id/data-* policy',\n} as const);\n\n/** Options an adapter's request decision reads from the profile. */\nexport interface AdapterProfileDefaults {\n readonly strict: boolean;\n readonly previewSignals: readonly ('query' | 'fetch-dest' | 'referer')[];\n}\n\n/** Options the browser runtime reads from the profile (through the inline configuration). */\nexport interface RuntimeProfileDefaults {\n readonly disableReferrerDetection: boolean;\n readonly eventSourcePolicy: EventSourcePolicy;\n readonly skipUnchanged: boolean;\n readonly sanitizerPolicy: 'compat' | 'strict';\n}\n\nexport const V2_ADAPTER_DEFAULTS: AdapterProfileDefaults = Object.freeze({\n strict: true,\n previewSignals: Object.freeze(['query'] as const),\n});\n\nexport const V2_RUNTIME_DEFAULTS: RuntimeProfileDefaults = Object.freeze({\n disableReferrerDetection: true,\n eventSourcePolicy: 'parent-or-opener',\n skipUnchanged: true,\n sanitizerPolicy: 'strict',\n});\n\n/** The 1.x table, spelled out so the profiles compare row by row. */\nexport const V1_ADAPTER_DEFAULTS: AdapterProfileDefaults = Object.freeze({\n strict: false,\n previewSignals: Object.freeze(['query', 'fetch-dest', 'referer'] as const),\n});\n\nexport const V1_RUNTIME_DEFAULTS: RuntimeProfileDefaults = Object.freeze({\n disableReferrerDetection: false,\n eventSourcePolicy: 'any',\n skipUnchanged: false,\n sanitizerPolicy: 'compat',\n});\n\n/** Only an explicit `'v1'` opts out; everything else gets the hardened rows. */\nexport function adapterDefaultsFor(profile: DefaultsProfile | undefined): AdapterProfileDefaults {\n return profile === 'v1' ? V1_ADAPTER_DEFAULTS : V2_ADAPTER_DEFAULTS;\n}\n\nexport function runtimeDefaultsFor(profile: DefaultsProfile | undefined): RuntimeProfileDefaults {\n return profile === 'v1' ? V1_RUNTIME_DEFAULTS : V2_RUNTIME_DEFAULTS;\n}\n","/**\n * `pll doctor --v2`: read the served configuration and report each runtime row\n * still at its 1.x value (ADR 0007).\n *\n * An empty slot means whatever the generation that wrote the script meant by\n * it, and that is exactly what changed between 1.x and 2.0. So the script\n * names the defaults it was resolved against in slot 24, and this reads\n * the answer instead of guessing it. A script without the marker predates it —\n * 1.x or 2.0.0-beta.0 — and is read as 1.x, with a line that says so.\n */\nimport { INLINE_CONFIG_KEYS } from '@/types/inline-config';\nimport {\n V1_RUNTIME_DEFAULTS,\n V2_RUNTIME_DEFAULTS,\n type RuntimeProfileDefaults,\n} from '@/types/defaults-profile';\nimport type { DoctorFinding, DoctorProbe } from './types';\n\nconst CONFIG_NAME = '__LIVE_PREVIEW_CONFIG__';\nconst CONFIG_MARKER = `var ${CONFIG_NAME}=`;\n// A comment or a script, in document order, so a script inside a comment is\n// skipped as the browser skips it. A quoted attribute value may hold `>`.\nconst COMMENT_OR_SCRIPT =\n /<!--[\\s\\S]*?-->|<script\\b((?:[^>\"']|\"[^\"]*\"|'[^']*')*)>([\\s\\S]*?)<\\/script\\b[^>]*>/giu;\n// The attribute name in any case, the value exactly, as `getElementById` compares it.\nconst CONFIG_ID = /(?:^|\\s)[iI][dD]\\s*=\\s*[\"']?__LIVE_PREVIEW_CONFIG__(?=[\"'\\s]|$)/u;\nconst JSON_TYPE = /(?:^|\\s)type\\s*=\\s*[\"']?application\\/json(?=[\"'\\s;]|$)/iu;\n\ntype InlineKey = (typeof INLINE_CONFIG_KEYS)[number];\n\n/** The configuration a preview response serves, whichever way the page writes it. */\nexport interface ServedConfig {\n /** `inline`: the generator's tuple. `element`: the JSON a page writes for a client it starts itself. */\n readonly source: 'inline' | 'element';\n /** The option under its inline name; `undefined` when the page does not set it. */\n value(key: InlineKey): unknown;\n}\n\n/** A runtime row of the readiness table, as a finding words it. */\ninterface ReadinessRow {\n readonly key: keyof RuntimeProfileDefaults;\n readonly title: string;\n /** What the 1.x value does. */\n readonly effect: string;\n /** What has to hold before the row can take its 2.0 value. */\n readonly condition: string;\n}\n\nconst ROWS: readonly ReadinessRow[] = [\n {\n key: 'disableReferrerDetection',\n title: 'Referrer trust is still on',\n effect: 'the runtime accepts the admin referer as a preview signal.',\n condition: 'no preview flow relies on the admin referer alone',\n },\n {\n key: 'eventSourcePolicy',\n title: 'Messages are accepted from any window',\n effect: 'any window that passes the origin check may post updates.',\n condition: 'no window but the parent or opener posts updates',\n },\n {\n key: 'sanitizerPolicy',\n title: 'Sanitizer is in compat mode',\n effect: 'id and every data-* pass.',\n condition: 'rich text no longer relies on `id` or `data-*`',\n },\n {\n key: 'skipUnchanged',\n title: 'Unchanged bindings are re-applied every message',\n effect: 'every binding is written again on every message.',\n condition: 'no renderer relies on running again for a value that did not change',\n },\n];\n\n/** Where a row's value came from, which decides what the reader can change. */\ntype Origin = 'profile' | 'explicit' | 'unmarked';\n\nconst ORIGIN_DETAIL: Readonly<Record<Origin, string>> = {\n profile: \"The script was generated with `defaults: 'v1'`.\",\n explicit: 'The script sets it explicitly.',\n unmarked: 'The script names no defaults, so its empty slot is read as the 1.x value.',\n};\n\nconst UNREADABLE: DoctorFinding = {\n code: 'LP0709',\n level: 'info',\n title: 'Could not read the inline configuration for a v2 readiness check',\n detail:\n 'The preview response carried no readable `__LIVE_PREVIEW_CONFIG__` inline script or JSON configuration element.',\n remedy:\n 'Run this against a page with the inline runtime (not loader mode without a preview context). ' +\n 'A preview behind authorizePreview serves it only to a request with credentials: pass them with --header, or a signed token as ?previewToken=… in the URL.',\n};\n\nconst UNMARKED: DoctorFinding = {\n code: 'LP0709',\n level: 'info',\n title: 'The inline configuration does not say which defaults it was generated against',\n detail:\n 'Scripts carry a `defaults` marker from 2.0.0-rc.1 on; this one has none, so a 1.x or 2.0.0-beta.0 generator wrote it. An empty slot is read as its 1.x value, which is what a 1.x runtime runs.',\n remedy:\n 'On 1.x the warnings stand as written. On 2.0.0-beta.0 an empty slot already runs its 2.0 value, so a warning about one does not apply there; upgrade and run `pll doctor --v2` again to read the marker.',\n};\n\n/**\n * The array literal after the marker, read as JSON. The generator writes `,,`\n * for unset slots, which JSON cannot express, so elisions outside strings\n * become `null`. Never evaluated: a page controls this text.\n */\nexport function readInlineConfig(body: string): readonly unknown[] | undefined {\n const start = body.indexOf(CONFIG_MARKER);\n if (start === -1) return undefined;\n const from = start + CONFIG_MARKER.length;\n if (body[from] !== '[') return undefined;\n let depth = 0;\n let inString = false;\n let previous = '';\n let json = '';\n for (let index = from; index < body.length; index += 1) {\n const char = body[index] ?? '';\n if (inString) {\n json += char;\n if (char === '\\\\') {\n json += body[index + 1] ?? '';\n index += 1;\n } else if (char === '\"') {\n inString = false;\n // A closed string is a value: without this, the next `,` reads as an elision.\n previous = '\"';\n }\n continue;\n }\n if (char === '\"') {\n inString = true;\n json += char;\n continue;\n }\n if (/\\s/u.test(char)) continue;\n const elision =\n (char === ',' && (previous === ',' || previous === '[')) ||\n (char === ']' && previous === ',');\n if (elision) json += 'null';\n json += char;\n previous = char;\n if (char === '[') depth += 1;\n if (char === ']') {\n depth -= 1;\n if (depth === 0) break;\n }\n }\n if (depth !== 0) return undefined;\n try {\n const value: unknown = JSON.parse(json);\n return Array.isArray(value) ? (value as readonly unknown[]) : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * The inline tuple first, because it is what the runtime reads; else a\n * `<script type=\"application/json\" id=\"__LIVE_PREVIEW_CONFIG__\">` holding an\n * object that names the options as the client and the inline script spell\n * them. That is how a page that starts `LivePreviewClient` itself can show the\n * doctor its options. Never evaluated: a page controls this text.\n * @internal\n */\nexport function readServedConfig(body: string): ServedConfig | undefined {\n // Both ways spell the name, and the scan below is slow on hostile markup.\n if (!body.includes(CONFIG_NAME)) return undefined;\n const scripts = servedScripts(body);\n const inline = scripts.find(({ text }) => text.includes(CONFIG_MARKER));\n const tuple = inline === undefined ? undefined : readInlineConfig(inline.text);\n if (tuple !== undefined) {\n return {\n source: 'inline',\n value: (key) => tuple[INLINE_CONFIG_KEYS.indexOf(key)] ?? undefined,\n };\n }\n const named = readConfigElement(scripts);\n if (named === undefined) return undefined;\n return {\n source: 'element',\n value: (key) => (Object.hasOwn(named, key) ? (named[key] ?? undefined) : undefined),\n };\n}\n\ninterface ServedScript {\n readonly attributes: string;\n readonly text: string;\n}\n\n/** The scripts a browser would run or read, without those inside a comment. */\nfunction servedScripts(body: string): readonly ServedScript[] {\n const scripts: ServedScript[] = [];\n for (const [, attributes, text] of body.matchAll(COMMENT_OR_SCRIPT)) {\n // A comment captures neither group.\n if (attributes !== undefined && text !== undefined) scripts.push({ attributes, text });\n }\n return scripts;\n}\n\nfunction readConfigElement(\n scripts: readonly ServedScript[],\n): Readonly<Record<string, unknown>> | undefined {\n for (const { attributes, text } of scripts) {\n if (!CONFIG_ID.test(attributes)) continue;\n if (!JSON_TYPE.test(attributes)) return undefined;\n try {\n const value: unknown = JSON.parse(text);\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Readonly<Record<string, unknown>>)\n : undefined;\n } catch {\n return undefined;\n }\n }\n return undefined;\n}\n\n/** `'compat'` rather than `\"compat\"`: remedies are read as source code. */\nfunction literal(value: unknown): string {\n return typeof value === 'string' ? `'${value}'` : JSON.stringify(value);\n}\n\nfunction remedyFor(origin: Origin, row: ReadinessRow, value: unknown): string {\n const wanted = `\\`${row.key}: ${literal(V2_RUNTIME_DEFAULTS[row.key])}\\``;\n switch (origin) {\n case 'profile':\n return (\n `Set ${wanted} or drop \\`defaults: 'v1'\\` once ${row.condition}. 3.0 removes the ` +\n 'profile; `pll migrate` writes its rows out, so each can go on its own.'\n );\n case 'explicit':\n return `Drop the explicit \\`${row.key}: ${literal(value)}\\` once ${row.condition}; 2.0 defaults to ${wanted}.`;\n case 'unmarked':\n // 1.8.1 has no `defaults` option and three of the four rows are not\n // inline options there, so the way out is the upgrade itself.\n return `Upgrading to 2.0 sets ${wanted}; check first that ${row.condition}.`;\n }\n}\n\nfunction gap(row: ReadinessRow, value: unknown, origin: Origin): DoctorFinding {\n return {\n code: 'LP0709',\n level: 'warning',\n title: row.title,\n detail: `${row.key} is ${literal(value)}: ${row.effect} ${ORIGIN_DETAIL[origin]}`,\n remedy: remedyFor(origin, row, value),\n };\n}\n\nfunction unknownGeneration(marker: unknown): DoctorFinding {\n return {\n code: 'LP0709',\n level: 'info',\n title: 'The inline configuration names defaults this doctor does not know',\n detail: `Its \\`defaults\\` marker is ${JSON.stringify(marker)}; this version reads 'v1' and 'v2', so no row was judged.`,\n remedy: 'Run the `pll doctor` of the package version that generated the page.',\n };\n}\n\n/**\n * One `LP0709` finding per runtime row not yet at its 2.0 value. An empty slot\n * has the value of the generation the script names, and the 1.x value when it\n * names none; a generation this version does not know is not judged at all.\n * A configuration element is written for a 2.x client, whose omitted options\n * are the 2.0 rows unless it names `'v1'`.\n * @internal\n */\nexport function analyzeV2Readiness(probe: DoctorProbe): readonly DoctorFinding[] {\n const config = readServedConfig(probe.previewResponse.body);\n if (config === undefined) return [UNREADABLE];\n const marker = config.value('defaults');\n if (marker !== undefined && marker !== 'v1' && marker !== 'v2') {\n return [unknownGeneration(marker)];\n }\n const generation = marker ?? (config.source === 'element' ? 'v2' : undefined);\n const fallback = generation === 'v2' ? V2_RUNTIME_DEFAULTS : V1_RUNTIME_DEFAULTS;\n const findings = ROWS.flatMap((row) => {\n const slot = config.value(row.key);\n const value = slot ?? fallback[row.key];\n if (value === V2_RUNTIME_DEFAULTS[row.key]) return [];\n const origin: Origin =\n generation === 'v1' ? 'profile' : slot === undefined ? 'unmarked' : 'explicit';\n return [gap(row, value, origin)];\n });\n return generation === undefined ? [...findings, UNMARKED] : findings;\n}\n","/**\n * The `pll doctor` audit: judge what a deployment serves from two fetches of\n * one URL, as a visitor and as the admin's iframe. Pure — the caller fetches.\n * Markup is read with regular expressions, which is enough to count\n * attributes inside start tags and find the runtime marker; findings say what\n * was observed.\n */\nimport { DIAGNOSTIC_CODES } from '../core/diagnostic-codes';\nimport { frameAncestorsAdmits, frameAncestorsOf } from './csp';\nimport { analyzeV2Readiness, readServedConfig } from './readiness';\nimport type {\n DoctorContext,\n DoctorFinding,\n DoctorProbe,\n DoctorReport,\n DoctorResponse,\n} from './types';\n\n/** Survives minification; the banner comment does not. */\nconst RUNTIME_MARKER = '__LIVE_PREVIEW_CONFIG__';\n/**\n * A start tag, quoted values read whole so a `>` inside one does not end it.\n * Text is never a tag: `<h1 data-payload-field=…>` in a code sample, or\n * a sentence that names the attribute, is what a page shows, not what it binds.\n * A raw `<` outside quotes ends the attempt, which keeps the scan linear: with\n * it, `<a<a<a…` and no `>` took 645 ms for 20 000 repetitions, 15 times the\n * time of 5 000.\n */\nconst START_TAG = /<[a-zA-Z](?:\"[^\"]*\"|'[^']*'|[^\"'<>])*>/gu;\n/** One attribute of a start tag: its name, and its value when it has one. */\nconst TAG_ATTRIBUTE = /([^\\s\"'>/=]+)(\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s\"'=<>`]+)))?/gu;\n/** Mirrors the scheduler default; the audit must not import the runtime. */\nconst DEFAULT_VISIBILITY_GATE_THRESHOLD = 50;\nconst LEVEL_ORDER = { error: 0, warning: 1, info: 2 } as const;\n// An end tag may carry junk before its `>`; browsers still close on it, so the\n// pattern must too, or a `</script foo>` would leave the script body counted.\nconst NON_MARKUP =\n /<script\\b[^>]*>[\\s\\S]*?<\\/script\\b[^>]*>|<style\\b[^>]*>[\\s\\S]*?<\\/style\\b[^>]*>|<!--[\\s\\S]*?-->/giu;\n\n/**\n * The runtime's own source spells `data-payload-field=` in a message, so\n * scripts must not be counted. This is a counter's view of the page, not a\n * sanitiser — nothing here is rendered — but it strips to a fixed point all\n * the same, so a block that only appears once its neighbour is gone is still\n * removed rather than counted.\n */\nfunction visibleMarkup(body: string): string {\n let markup = body;\n for (;;) {\n const stripped = markup.replace(NON_MARKUP, '');\n if (stripped === markup) return markup;\n markup = stripped;\n }\n}\n\n/** An attribute as the markup carries it, at the offset of its tag. */\ninterface MarkupAttribute {\n readonly name: string;\n readonly value: string;\n readonly at: number;\n}\n\n/**\n * Every attribute with a value, in document order. Names are compared in lower\n * case, as a browser parses them; the tag name is read along and never matches\n * an attribute the audit asks about.\n */\nfunction markupAttributes(markup: string): readonly MarkupAttribute[] {\n const found: MarkupAttribute[] = [];\n for (const tag of markup.matchAll(START_TAG)) {\n for (const [, name = '', assigned, double, single, bare] of tag[0].matchAll(TAG_ATTRIBUTE)) {\n if (assigned === undefined) continue;\n found.push({\n name: name.toLowerCase(),\n value: double ?? single ?? bare ?? '',\n at: tag.index,\n });\n }\n }\n return found;\n}\n\nfunction named(attributes: readonly MarkupAttribute[], name: string): readonly MarkupAttribute[] {\n return attributes.filter((attribute) => attribute.name === name);\n}\n\n/** Bindings before the first owner marker are certainly unowned; nesting is not modelled, so this under-reports. */\nfunction unownedBindingsBeforeFirstOwner(attributes: readonly MarkupAttribute[]): number {\n const firstOwner = named(attributes, 'data-payload-owner')[0];\n if (firstOwner === undefined) return 0;\n return named(attributes, 'data-payload-field').filter((binding) => binding.at < firstOwner.at)\n .length;\n}\n\nfunction tryUrl(value: string | undefined): URL | undefined {\n if (value === undefined) return undefined;\n try {\n return new URL(value);\n } catch {\n return undefined;\n }\n}\n\n/** Why the preview response cannot be audited as a page; the public one may legitimately be gated. */\nfunction describeNonPage(preview: DoctorResponse): DoctorFinding | undefined {\n if (preview.status >= 300 && preview.status <= 399) {\n const location = preview.headers['location'];\n return {\n code: 'LP0708',\n level: 'error',\n title: `The preview request was redirected (${String(preview.status)})${location === undefined ? '' : ` to ${location}`}`,\n detail:\n 'The audit does not follow redirects: the page at the end of one is a different ' +\n 'resource, usually a login or a canonical host, and judging it would say nothing ' +\n 'about this URL.',\n remedy:\n 'The request carried the intent parameter (?preview=true, or the --param name), the ' +\n \"way the admin's iframe loads the page, so a rule that redirects on that query redirects \" +\n 'the iframe too. A redirect to a login means the route needs authentication that a ' +\n \"plain probe cannot supply: pass an editor's credentials with --header, or a signed token as ?previewToken=… in the URL. Probe the final \" +\n 'URL directly only when the redirect has nothing to do with either.',\n };\n }\n if (preview.status < 200 || preview.status > 299) {\n return {\n code: 'LP0708',\n level: 'error',\n title: `The preview request returned ${String(preview.status)}, not a page`,\n detail:\n 'Nothing below this can be judged: a status outside 2xx means the body is an ' +\n 'error page or empty.',\n remedy: 'Check the URL.',\n };\n }\n if (preview.body.trim() === '') {\n return {\n code: 'LP0708',\n level: 'error',\n title: `The preview request returned ${String(preview.status)} with an empty body`,\n detail:\n 'A 2xx status with nothing in it — 204 and 205 are defined that way — is not a page, ' +\n 'so its missing runtime and missing bindings say nothing.',\n remedy: 'Point the audit at a route that renders a document.',\n };\n }\n const contentType = preview.headers['content-type'];\n if (contentType !== undefined && !contentType.toLowerCase().includes('html')) {\n return {\n code: 'LP0708',\n level: 'error',\n title: `The preview request returned ${contentType}, not HTML`,\n detail:\n 'The audit reads a rendered page. A non-HTML response has no runtime and no ' +\n 'bindings by definition, so reporting their absence would say nothing.',\n remedy: 'Point the audit at a page route rather than an API or an asset.',\n };\n }\n return undefined;\n}\n\nfunction framingFindings(\n preview: DoctorResponse,\n runtimeInPreview: boolean,\n context: DoctorContext,\n): DoctorFinding[] {\n const findings: DoctorFinding[] = [];\n const page = tryUrl(context.url);\n const admin = tryUrl(context.adminOrigin);\n const sameOrigin = admin !== undefined && page?.origin === admin.origin;\n const frameAncestors = frameAncestorsOf(preview.headers['content-security-policy']);\n if (runtimeInPreview && frameAncestors === undefined) {\n findings.push({\n code: 'LP0702',\n level: 'warning',\n title: 'Preview response declares no frame-ancestors',\n detail:\n 'The runtime is injected but the response carries no frame-ancestors directive, ' +\n 'so nothing states which origins may embed this page.',\n remedy:\n 'Let the adapter manage CSP, or set frame-ancestors yourself to the admin origin. ' +\n 'Without it you are relying on the absence of a policy.',\n });\n } else if (frameAncestors !== undefined && context.adminOrigin !== undefined) {\n const admitted =\n admin !== undefined &&\n page !== undefined &&\n frameAncestorsAdmits(frameAncestors, admin, page);\n if (!admitted) {\n findings.push({\n code: 'LP0702',\n level: 'error',\n title: 'frame-ancestors does not admit the admin origin',\n detail:\n admin === undefined\n ? `\"${context.adminOrigin}\" is not an absolute URL, so no source expression can admit it.`\n : `Served: \"${frameAncestors}\". Expected it to allow ${admin.origin}.`,\n remedy:\n 'Add the admin origin to allowedOrigins so the adapter merges it into ' +\n 'frame-ancestors, or add it to your own policy.',\n });\n }\n }\n const frameOptions = preview.headers['x-frame-options'];\n // A response with a `frame-ancestors` directive is framed by that directive alone: the HTML\n // Standard skips the header for it (measured in Chromium, Firefox and WebKit). The header\n // decides only where the response declares none; the directive's own verdict is LP0702.\n const blocks =\n frameOptions !== undefined &&\n frameAncestors === undefined &&\n (/deny/iu.test(frameOptions) || (/sameorigin/iu.test(frameOptions) && !sameOrigin));\n if (blocks) {\n // `SAMEORIGIN` refuses a frame from *another* origin, so it is a verdict\n // about this preview only once the admin origin is known. Without --admin\n // the audit says what it saw and names the condition instead of asserting a\n // block it cannot have established: where the admin and the site share an\n // origin, the preview runs. `DENY` needs no origin to be a verdict.\n const judged = admin !== undefined || /deny/iu.test(frameOptions);\n findings.push(\n judged\n ? {\n code: 'LP0703',\n level: 'error',\n title: `X-Frame-Options: ${frameOptions} blocks the preview iframe`,\n detail:\n 'This header is older than CSP. A browser applies it to a response that has no ' +\n 'frame-ancestors directive, and this response has no frame-ancestors.',\n remedy:\n 'Remove X-Frame-Options for preview responses, or serve a frame-ancestors ' +\n 'directive that admits the admin: the header is then ignored. It is usually ' +\n 'set by a proxy or a security middleware rather than by the app.',\n }\n : {\n code: 'LP0703',\n level: 'warning',\n title: `X-Frame-Options: ${frameOptions} blocks every frame from another origin`,\n detail:\n 'This header is older than CSP. A browser applies it to a response that has no ' +\n 'frame-ancestors directive, and this response has no frame-ancestors. SAMEORIGIN ' +\n 'refuses a frame from another origin only, and no admin origin was given: whether ' +\n 'it blocks this preview depends on where the admin is served, which this probe ' +\n 'cannot see.',\n remedy:\n 'Re-run with --admin <origin> to have it judged. If the admin is on another ' +\n 'origin, remove X-Frame-Options for preview responses or serve a frame-ancestors ' +\n 'directive that admits it; a proxy or a security middleware usually sets the header.',\n },\n );\n }\n return findings;\n}\n\n/** The gate as the served script configures it; `threshold` is undefined when the gate is off. */\ninterface ServedGate {\n readonly threshold: number | undefined;\n readonly configured: boolean;\n}\n\n/**\n * Read from the served configuration, because an adapter now forwards both\n * options: judged against the default, a page that raised its threshold\n * would be told to raise it. A page whose configuration cannot be read\n * (loader mode, a client the page starts without a configuration element) is\n * judged against the default.\n */\nfunction servedGate(body: string): ServedGate {\n const config = readServedConfig(body);\n if (config?.value('disableVisibilityGate') === true) {\n return { threshold: undefined, configured: true };\n }\n const threshold = config?.value('visibilityGateThreshold');\n return typeof threshold === 'number'\n ? { threshold, configured: true }\n : { threshold: DEFAULT_VISIBILITY_GATE_THRESHOLD, configured: false };\n}\n\nfunction bindingFindings(\n previewMarkup: string,\n publicMarkup: string,\n runtime: boolean,\n gate: ServedGate,\n): DoctorFinding[] {\n const findings: DoctorFinding[] = [];\n const preview = markupAttributes(previewMarkup);\n const previewBindings = named(preview, 'data-payload-field').length;\n const publicBindings = named(markupAttributes(publicMarkup), 'data-payload-field').length;\n const owners = named(preview, 'data-payload-owner').length;\n // A boundary is rendered by the server from the form state, so an edit of a\n // field it depends on arrives without one field binding; an empty id names\n // no registry entry, and no strategy renders it.\n const boundaries = named(preview, 'data-payload-fragment').filter(\n (boundary) => boundary.value.length > 0,\n ).length;\n if (publicBindings > 0) {\n findings.push({\n code: 'LP0704',\n level: 'warning',\n title: `${String(publicBindings)} binding attribute(s) served to anonymous visitors`,\n detail:\n 'A request with no preview intent received data-payload-field attributes. These ' +\n 'disclose the CMS field taxonomy, and any CSS keyed on them changes public layout ' +\n 'the moment emission is gated.',\n remedy:\n 'Gate binding emission on an authorized preview context — see ' +\n 'createPreviewBindings(), whose suppressed form emits nothing at all.',\n });\n }\n if (gate.threshold !== undefined && previewBindings > gate.threshold) {\n findings.push({\n code: 'LP0705',\n level: 'warning',\n title: `${String(previewBindings)} bindings exceed the ${gate.configured ? 'configured' : 'default'} visibility gate`,\n detail:\n `Above visibilityGateThreshold (${String(gate.threshold)}) the ` +\n 'scheduler stops writing offscreen elements and buffers them until they scroll into ' +\n 'view. On a page nobody scrolls, that is never.',\n remedy:\n 'Raise visibilityGateThreshold, or set disableVisibilityGate, on the adapter, ' +\n 'LivePreviewClient or generateInlineScript() if the whole page must stay live, or ' +\n 'confirm that deferring below the fold is acceptable here.',\n });\n }\n if (runtime && previewBindings === 0 && boundaries === 0) {\n findings.push({\n code: 'LP0707',\n level: 'error',\n title: 'Preview response carries no bindings',\n detail:\n 'The runtime is present but there is not one data-payload-field element for it to ' +\n 'write into and not one data-payload-fragment boundary for a server to render, so ' +\n 'no edit can ever be visible on this page.',\n remedy:\n 'Add binding attributes or fragment boundaries to the markup. pll-codegen --inventory ' +\n 'lists every field the schema makes addressable.',\n });\n }\n const unowned = unownedBindingsBeforeFirstOwner(preview);\n if (owners > 0 && unowned > 0) {\n findings.push({\n code: 'LP0706',\n level: 'warning',\n title: `${String(unowned)} binding(s) outside every owner marker`,\n detail:\n `The page declares ${String(owners)} owner marker(s), so owner scoping is in use. ` +\n 'Bindings that belong to no owner receive nothing once scopeBindingsByOwner is on — ' +\n 'they fail closed.',\n remedy:\n 'Give every binding an owning ancestor, or leave scopeBindingsByOwner off until they ' +\n 'all have one. Counted before the first owner marker only, so the real number may be ' +\n 'higher.',\n });\n }\n return findings;\n}\n\n/** Audit a probed deployment. */\nexport function analyzeProbe(\n probe: DoctorProbe,\n context: DoctorContext & {\n readonly v2?: boolean;\n /** Whether the preview probe carried caller-supplied headers (`--header`). */\n readonly credentials?: boolean;\n },\n): DoctorReport {\n const { publicResponse: pub, previewResponse: preview } = probe;\n const notAPage = describeNonPage(preview);\n if (notAPage !== undefined) {\n return { url: context.url, findings: [notAPage], errors: 1, warnings: 0 };\n }\n const runtimeInPreview = preview.body.includes(RUNTIME_MARKER);\n const findings: DoctorFinding[] = [];\n if (!runtimeInPreview) {\n findings.push(\n context.credentials === true\n ? {\n code: 'LP0701',\n level: 'warning',\n title: 'No inline runtime in the preview response',\n detail:\n `The preview request returned ${String(preview.status)} without the inline ` +\n 'runtime, although it carried credentials (--header, or ?previewToken= in the URL). Three readings: ' +\n 'those credentials were not accepted — expired, issued for another page, or not ' +\n 'what authorizePreview reads — an adapter that did not recognise the request as a ' +\n 'preview, or a consumer that starts LivePreviewClient itself.',\n remedy:\n 'Check that the credentials are current and are the ones authorizePreview reads; ' +\n 'if you use an adapter, check its inject mode and previewSignals. If you start the ' +\n 'client yourself, this line is expected.',\n }\n : {\n code: 'LP0701',\n level: 'warning',\n title: 'No inline runtime in the preview response',\n detail:\n `The preview request returned ${String(preview.status)} without the inline ` +\n 'runtime. Three readings, and this audit cannot tell them apart from the response ' +\n 'alone: a preview behind authorizePreview — the strict 2.0 default — which answers ' +\n 'a request without credentials exactly like this, an adapter that did not recognise ' +\n 'the request as a preview, or a consumer that starts LivePreviewClient itself.',\n remedy:\n \"To audit a gated preview, send what an editor's browser sends: \" +\n \"--header 'Cookie: payload-token=…' for a Payload session; for a signed token, \" +\n 'the URL with ?previewToken=… (the default transport, dropped from the visitor ' +\n \"request), or --header 'x-preview-token: …' where the strategy sets transport: \" +\n \"{ kind: 'header' }. Otherwise check the adapter's \" +\n 'inject mode and previewSignals; if you start the client yourself, this line is ' +\n 'expected.',\n },\n );\n }\n findings.push(...framingFindings(preview, runtimeInPreview, context));\n findings.push(\n ...bindingFindings(\n visibleMarkup(preview.body),\n visibleMarkup(pub.body),\n runtimeInPreview,\n servedGate(preview.body),\n ),\n );\n if (pub.body.includes(RUNTIME_MARKER)) {\n // A page that starts its own client serves only the JSON element, which spells the name too.\n const inline = pub.body.includes(`var ${RUNTIME_MARKER}=`);\n findings.push({\n code: DIAGNOSTIC_CODES.RuntimeOnPublicPage,\n level: 'info',\n title: inline\n ? 'Runtime is also served to anonymous visitors'\n : 'Preview configuration is also served to anonymous visitors',\n detail: inline\n ? 'A request with no preview intent received the inline runtime. That is correct for ' +\n \"inject: 'always' and unintended for 'preview-only'.\"\n : 'A request with no preview intent received the configuration element a page writes ' +\n 'for a client it starts itself. That is correct if the page starts the client for ' +\n 'every request, and unintended if it writes the element only for a preview.',\n remedy: '',\n });\n }\n if (context.v2 === true) findings.push(...analyzeV2Readiness(probe));\n findings.sort((a, b) => LEVEL_ORDER[a.level] - LEVEL_ORDER[b.level]);\n return {\n url: context.url,\n findings,\n errors: findings.filter((finding) => finding.level === 'error').length,\n warnings: findings.filter((finding) => finding.level === 'warning').length,\n };\n}\n","/** Terminal rendering of a doctor report. */\nimport type { DoctorReport } from './types';\n\nconst LEVEL_LABEL = { error: 'ERROR', warning: 'WARN ', info: 'INFO ' } as const;\n\n/** @internal */\nexport function formatReport(report: DoctorReport): string {\n const lines: string[] = [`pll doctor — ${report.url}`, ''];\n if (report.findings.length === 0) {\n lines.push('No findings. The preview response carries the runtime, a frame-ancestors');\n lines.push('policy, and bindings; the public response carries none of them.', '');\n return lines.join('\\n');\n }\n for (const finding of report.findings) {\n lines.push(`${LEVEL_LABEL[finding.level]} ${finding.code} ${finding.title}`);\n lines.push(` ${finding.detail}`);\n if (finding.remedy !== '') lines.push(` → ${finding.remedy}`);\n lines.push('');\n }\n lines.push(`${String(report.errors)} error(s), ${String(report.warnings)} warning(s).`, '');\n return lines.join('\\n');\n}\n","/**\n * Fetching for `pll doctor`: the two probes, and the default fetch that talks\n * to a real server. Not in the barrel so it stays in the coverage report.\n */\nimport { analyzeProbe } from './analyze';\nimport type { DoctorReport, DoctorResponse } from './types';\n\n/** Injectable so tests can drive the audit without a server. */\nexport type DoctorFetch = (\n url: string,\n init: { readonly headers: Readonly<Record<string, string>> },\n) => Promise<DoctorResponse>;\n\nexport interface RunDoctorOptions {\n readonly url: string;\n /** Admin origin the preview is meant to be embedded from, when known. */\n readonly adminOrigin?: string | undefined;\n /** Defaults to a fetch built on `globalThis.fetch`. */\n readonly fetchImpl?: DoctorFetch | undefined;\n /** Also check the served page against the 2.0 readiness table. */\n readonly v2?: boolean;\n /**\n * Headers sent with the preview probe only, never with the visitor probe: the\n * credentials a preview behind `authorizePreview` needs, such as a Payload\n * session `Cookie`, or an `x-preview-token` where the token strategy reads a\n * header (a token in the URL is passed in `url` instead). Without them a gated page answers\n * the audit the way it answers any stranger. Their values never reach the report.\n */\n readonly previewHeaders?: Readonly<Record<string, string>> | undefined;\n /**\n * The query parameters the deployment reads as preview intent, when its\n * adapter sets `previewQueryParams`; the same list, since that option replaces\n * the default `preview`, `draft` and `livePreview`. The first one is what the\n * preview probe appends as `=true`; none of them reaches the visitor probe.\n */\n readonly previewQueryParams?: readonly string[] | undefined;\n /**\n * The query parameter the `signed-token` strategy reads, when its transport\n * names one other than `previewToken`. Treated like `previewToken`: dropped\n * from the visitor probe, counted as the credential, its value never shown.\n */\n readonly tokenQueryParam?: string | undefined;\n}\n\n/**\n * The query parameters an adapter reads as preview intent by default; the same\n * list as `DEFAULT_QUERY_PARAMS` in adapters/shared/preview-request.ts, which\n * `previewQueryParams` replaces rather than extends.\n */\nconst DEFAULT_INTENT_PARAMS: readonly string[] = ['preview', 'draft', 'livePreview'];\n\nfunction isIntentValue(value: string): boolean {\n return value === 'true' || value === '1';\n}\n\n/** `key=value` and `#fragment` split off a URL, with the query kept as the caller spelled it. */\nfunction splitUrl(url: string): {\n readonly base: string;\n readonly parts: string[];\n readonly hash: string;\n} {\n const hashAt = url.indexOf('#');\n const hash = hashAt === -1 ? '' : url.slice(hashAt);\n const beforeHash = hashAt === -1 ? url : url.slice(0, hashAt);\n const queryAt = beforeHash.indexOf('?');\n if (queryAt === -1) return { base: beforeHash, parts: [], hash };\n return {\n base: beforeHash.slice(0, queryAt),\n parts: beforeHash.slice(queryAt + 1).split('&'),\n hash,\n };\n}\n\nfunction decodePart(part: string): readonly [string, string] {\n const at = part.indexOf('=');\n const decode = (text: string): string => {\n try {\n return decodeURIComponent(text.replace(/\\+/gu, ' '));\n } catch {\n return text;\n }\n };\n return at === -1 ? [decode(part), ''] : [decode(part.slice(0, at)), decode(part.slice(at + 1))];\n}\n\n/**\n * The query parameter the `signed-token` strategy reads by default. A token\n * audit puts the token in the URL, and it belongs to the preview request only:\n * in the visitor request it would authorize the \"anonymous\" probe and, behind a\n * replay store, spend the token before the preview request arrives.\n */\nconst TOKEN_PARAM = 'previewToken';\n\n/**\n * The URL as the audit prints it: the token's value replaced by `…`. Header\n * values never reach the report, and a token passed in the URL is the same\n * credential, so the report, `--json` and the failure line all show this form.\n * @internal\n */\nexport function redactToken(url: string, tokenParam: string = TOKEN_PARAM): string {\n const { base, parts, hash } = splitUrl(url);\n const shown = parts.map((part) => {\n const at = part.indexOf('=');\n return at === -1 || decodePart(part)[0] !== tokenParam ? part : `${part.slice(0, at)}=…`;\n });\n return shown.some((part, index) => part !== parts[index])\n ? `${base}?${shown.join('&')}${hash}`\n : url;\n}\n\n/** Whether the URL carries a non-empty token for the preview request. */\nfunction carriesToken(url: string, tokenParam: string): boolean {\n return splitUrl(url).parts.some((part) => {\n const [key, value] = decodePart(part);\n return key === tokenParam && value !== '';\n });\n}\n\n/**\n * The page a visitor requests: the intent parameters removed, the rest of the\n * query byte for byte as given. A query is never re-serialised — `URLSearchParams`\n * would turn `%20` into `+` and add `=` to a bare key, and a page whose query is\n * signed by an edge token would answer a different request than a visitor makes.\n */\nfunction visitorUrl(url: string, params: readonly string[], tokenParam: string): string {\n const { base, parts, hash } = splitUrl(url);\n const dropped = new Set([...params, tokenParam]);\n const kept = parts.filter((part) => !dropped.has(decodePart(part)[0]));\n if (kept.length === parts.length) return url;\n return `${base}${kept.length === 0 ? '' : `?${kept.join('&')}`}${hash}`;\n}\n\n/**\n * The page the admin's iframe loads. A 2.0 adapter counts only the query as\n * intent (`previewSignals: ['query']`), and `buildLivePreviewUrl` writes\n * `preview=true`, so the first intent parameter is appended as `=true` unless the\n * URL already names one with a value that counts.\n */\nfunction previewUrl(url: string, params: readonly string[]): string {\n const { base, parts, hash } = splitUrl(url);\n const carries = parts.some((part) => {\n const [key, value] = decodePart(part);\n return params.includes(key) && isIntentValue(value);\n });\n if (carries) return url;\n const query = parts.join('&');\n const joiner = query === '' ? '?' : query.endsWith('&') ? '' : '&';\n const intent = `${encodeURIComponent(params[0] ?? 'preview')}=true`;\n return `${base}${parts.length === 0 ? '?' : `?${query}${joiner}`}${intent}${hash}`;\n}\n\nexport const DEFAULT_TIMEOUT_MS = 15_000;\nexport const MAX_BODY_BYTES = 10 * 1024 * 1024;\n\nexport interface DefaultFetchOptions {\n readonly fetchFn?: typeof fetch;\n readonly timeoutMs?: number;\n readonly maxBodyBytes?: number;\n}\n\n/** Header names lowercased; every check downstream reads a lowercase key. @internal */\nexport function lowercaseHeaders(headers: Headers): Record<string, string> {\n const out: Record<string, string> = {};\n headers.forEach((value, key) => {\n out[key.toLowerCase()] = value;\n });\n return out;\n}\n\n/** The error's message plus its cause's, which is where undici puts `self-signed certificate` and friends. */\nexport function describeFailure(error: unknown): string {\n if (!(error instanceof Error)) return String(error);\n const cause = error.cause;\n if (cause instanceof Error && cause.message !== '' && !error.message.includes(cause.message)) {\n return `${error.message} (${cause.message})`;\n }\n return error.message;\n}\n\nasync function readBody(response: Response, limit: number): Promise<string> {\n const reader = response.body?.getReader();\n if (reader === undefined) return response.text();\n const decoder = new TextDecoder();\n let received = 0;\n let text = '';\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n received += value.byteLength;\n if (received > limit) {\n await reader.cancel();\n throw new Error(\n `the response body exceeds ${String(limit)} bytes, which is not a page the audit can judge`,\n );\n }\n text += decoder.decode(value, { stream: true });\n }\n return text + decoder.decode();\n}\n\n/** Redirects are reported, not followed; a hanging origin is abandoned; a runaway body is refused. */\nexport function createDefaultFetch(options: DefaultFetchOptions = {}): DoctorFetch {\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const limit = options.maxBodyBytes ?? MAX_BODY_BYTES;\n return async (url, init) => {\n const fetchFn = options.fetchFn ?? globalThis.fetch;\n let response: Response;\n try {\n response = await fetchFn(url, {\n headers: init.headers,\n redirect: 'manual',\n signal: AbortSignal.timeout(timeoutMs),\n });\n } catch (error) {\n if (error instanceof Error && error.name === 'TimeoutError') {\n throw new Error(`no response within ${String(timeoutMs / 1000)} s`, { cause: error });\n }\n throw error;\n }\n return {\n status: response.status,\n headers: lowercaseHeaders(response.headers),\n body: await readBody(response, limit),\n };\n };\n}\n\n/** The referer the admin's iframe would send: the origin (or the given admin path) with one slash. */\nexport function previewReferer(adminOrigin: string): string {\n try {\n return new URL(adminOrigin).href;\n } catch {\n return `${adminOrigin.replace(/\\/+$/u, '')}/`;\n }\n}\n\n/**\n * The caller's headers without the ones the probe sets itself. Header names are\n * case-insensitive, and `fetch` combines two spellings of one name into one\n * comma-joined value, so a caller's `sec-fetch-dest` would otherwise turn the\n * probe's `iframe` into `document, iframe`. The probe's own headers win.\n */\nfunction callerHeaders(\n headers: Readonly<Record<string, string>> | undefined,\n own: Readonly<Record<string, string>>,\n): Record<string, string> {\n const taken = new Set(Object.keys(own).map((name) => name.toLowerCase()));\n return Object.fromEntries(\n Object.entries(headers ?? {}).filter(([name]) => !taken.has(name.toLowerCase())),\n );\n}\n\n/** Fetch the URL twice — as a visitor and as the admin's iframe — and audit the difference. */\nexport async function runDoctor(options: RunDoctorOptions): Promise<DoctorReport> {\n const fetchImpl = options.fetchImpl ?? createDefaultFetch();\n const params =\n options.previewQueryParams === undefined || options.previewQueryParams.length === 0\n ? DEFAULT_INTENT_PARAMS\n : options.previewQueryParams;\n const tokenParam = options.tokenQueryParam ?? TOKEN_PARAM;\n // No referer, no intent parameter and no credentials on the visitor probe:\n // each of them is a preview signal or a way past one.\n const publicResponse = await fetchImpl(visitorUrl(options.url, params, tokenParam), {\n headers: {\n Accept: 'text/html',\n 'Sec-Fetch-Dest': 'document',\n 'Sec-Fetch-Mode': 'navigate',\n },\n });\n const probeHeaders: Record<string, string> = {\n Accept: 'text/html',\n 'Sec-Fetch-Dest': 'iframe',\n 'Sec-Fetch-Mode': 'navigate',\n ...(options.adminOrigin === undefined ? {} : { Referer: previewReferer(options.adminOrigin) }),\n };\n const sent = callerHeaders(options.previewHeaders, probeHeaders);\n const previewResponse = await fetchImpl(previewUrl(options.url, params), {\n headers: { ...sent, ...probeHeaders },\n });\n return analyzeProbe(\n { publicResponse, previewResponse },\n {\n url: redactToken(options.url, tokenParam),\n adminOrigin: options.adminOrigin,\n ...(options.v2 === true ? { v2: true } : {}),\n ...(Object.keys(sent).length > 0 || carriesToken(options.url, tokenParam)\n ? { credentials: true }\n : {}),\n },\n );\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"doctor.js","names":["DIAGNOSTIC_CODES","Object","freeze","NoTrustedOrigin","ReferrerOnlyTrust","PluginIncompatible","ProfileFeatureOmitted","OrphanField","UnattributableUpdate","UnboundField","CoverageDefaultChanges","VisibilityGateDeferred","UnsafeAttributeWrite","TextTargetHasChildren","MissingArrayTemplate","StructuralItemUnkeyed","StructuralDuplicateKey","StructuralUnstableKeys","UnsupportedStrategy","UnknownValueFormat","SanitizerDroppedAttribute","UnrenderedBlockKept","UnfaithfulPatch","ServerFormatReplaced","UnrenderedBlockLost","MessageRejected","TokenRejected","ProtocolShapeUnknown","HandlerThrew","TransformThrew","RendererThrew","StartupFailed","ReadyFailed","HydrationWaitTimedOut","AuditRuntimeMissing","AuditNoFrameAncestors","AuditFrameOptionsBlocks","AuditBindingsExposed","AuditGateThresholdExceeded","AuditUnownedBindings","AuditNoBindings","AuditNotAPage","FragmentRequestFailed","FragmentResponseInvalid","FragmentUnauthorized","FragmentSuperseded","RouteRefreshLoop","FragmentStrategyUnavailable","UnboundChangeRefresh","EscalationUnavailable","IslandStartUnavailable","RouteRefreshUnconfirmed","V2ReadinessGap","RuntimeOnPublicPage","AuditModifierWithoutField","DEFAULT_PORT","http","https","ws","wss","SCHEME_SOURCE","HOST_SOURCE","schemeOf","url","protocol","slice","toLowerCase","portOf","port","schemeMatches","expression","actual","isSelf","admin","page","hostname","adminScheme","pageScheme","sourceMatches","source","scheme","exec","host","expressionScheme","expressionHost","expressionPort","pattern","wanted","replace","startsWith","endsWith","length","hostMatches","INLINE_CONFIG_KEYS","V2_RUNTIME_DEFAULTS","disableReferrerDetection","eventSourcePolicy","skipUnchanged","sanitizerPolicy","V1_RUNTIME_DEFAULTS","CONFIG_NAME","CONFIG_MARKER","COMMENT_OR_SCRIPT","CONFIG_ID","JSON_TYPE","ROWS","key","title","effect","condition","ORIGIN_DETAIL","profile","explicit","unmarked","UNREADABLE","code","level","detail","remedy","UNMARKED","readServedConfig","body","includes","scripts","attributes","text","matchAll","push","servedScripts","inline","find","tuple","start","indexOf","from","depth","inString","previous","json","index","char","test","value","JSON","parse","Array","isArray","readInlineConfig","named","readConfigElement","hasOwn","literal","stringify","remedyFor","origin","row","gap","unknownGeneration","marker","analyzeV2Readiness","probe","config","previewResponse","generation","fallback","findings","flatMap","slot","RUNTIME_MARKER","START_TAG","TAG_ATTRIBUTE","LEVEL_ORDER","error","warning","info","NON_MARKUP","visibleMarkup","markup","stripped","FIELD_MODIFIERS","Set","map","name","markupAttributes","found","tag","assigned","double","single","bare","at","filter","attribute","tryUrl","URL","analyzeProbe","context","publicResponse","pub","preview","notAPage","status","location","headers","String","trim","contentType","describeNonPage","errors","warnings","runtimeInPreview","credentials","adminOrigin","sameOrigin","frameAncestors","csp","directive","split","trimmed","frameAncestorsOf","admitted","sources","frameAncestorsAdmits","frameOptions","judged","framingFindings","previewMarkup","publicMarkup","runtime","gate","previewBindings","publicBindings","owners","boundaries","boundary","stranded","bound","tags","Map","has","set","values","modifiersWithoutField","threshold","configured","unowned","firstOwner","binding","unownedBindingsBeforeFirstOwner","bindingFindings","servedGate","v2","sort","a","b","finding","LEVEL_LABEL","formatReport","report","lines","join","DEFAULT_INTENT_PARAMS","splitUrl","hashAt","hash","beforeHash","queryAt","base","parts","decodePart","part","decode","decodeURIComponent","TOKEN_PARAM","redactToken","tokenParam","shown","some","carriesToken","lowercaseHeaders","out","forEach","async","readBody","response","limit","reader","getReader","decoder","TextDecoder","received","done","read","byteLength","cancel","Error","stream","previewReferer","href","runDoctor","options","fetchImpl","timeoutMs","maxBodyBytes","init","fetchFn","globalThis","fetch","redirect","signal","AbortSignal","timeout","cause","createDefaultFetch","params","previewQueryParams","tokenQueryParam","dropped","kept","visitorUrl","Accept","probeHeaders","Referer","sent","own","taken","keys","fromEntries","entries","callerHeaders","previewHeaders","isIntentValue","query","joiner","intent","encodeURIComponent","previewUrl"],"sources":["../src/core/diagnostic-codes.ts","../src/doctor/csp.ts","../src/types/inline-config.ts","../src/types/defaults-profile.ts","../src/doctor/readiness.ts","../src/doctor/analyze.ts","../src/doctor/format.ts","../src/doctor/probe.ts"],"sourcesContent":["/**\n * Every diagnostic code the runtime and tools emit, so a consumer can branch\n * on `e.code` instead of matching message text. A code is never reused for a\n * different meaning; a retired one stays reserved.\n *\n * Browser-side reporting sites write the literal (`code: 'LP0603'`) rather than\n * reading it from this record: the frozen table would then ship in the inline\n * runtime for the sake of six characters — it did, 1 287 B in every page, for\n * the one read Z6 left in the strategy runner, and `diagnostic-codes.test.ts`\n * now holds the artifact free of it. Server-side tools may import it.\n */\n\n/**\n * Every diagnostic code the runtime can report, by name.\n *\n * ```ts\n * client.events.on('error', (e) => {\n * if (e.code === DIAGNOSTIC_CODES.TransformThrew) reportToSentry(e.error);\n * });\n * ```\n */\nexport const DIAGNOSTIC_CODES = Object.freeze({\n /** No trusted origin configured in production; nothing will be accepted. */\n NoTrustedOrigin: 'LP0101',\n /** Origin trust rests on `document.referrer`, so any framing site is trusted. */\n ReferrerOnlyTrust: 'LP0102',\n /** A plugin declares a runtime range this runtime does not satisfy; it was refused. */\n PluginIncompatible: 'LP0103',\n /** The page carries the lean runtime and needs a feature that profile leaves out. */\n ProfileFeatureOmitted: 'LP0104',\n\n /** An update named an empty field with no binding anchor on the page; an edit to it has nowhere to land. */\n OrphanField: 'LP0201',\n /** Owner scoping is on and the update names no document it could belong to. */\n UnattributableUpdate: 'LP0202',\n /** An update carried a value for a field the page binds nowhere; editing it changes nothing on this page. */\n UnboundField: 'LP0203',\n /** In development: a bound path covers its group under the `descendant` default, which 3.0 changes. */\n CoverageDefaultChanges: 'LP0204',\n\n /** The visibility gate held offscreen writes back until they scroll into view. */\n VisibilityGateDeferred: 'LP0301',\n\n /** A value was refused because the attribute or the value itself is unsafe. */\n UnsafeAttributeWrite: 'LP0401',\n /** A text element has structured children, so replacing its text was skipped. */\n TextTargetHasChildren: 'LP0402',\n /** A structural container has no array template, so the update was skipped. */\n MissingArrayTemplate: 'LP0403',\n /** A structural item has no `id`; it pairs positionally, so an insert re-renders every row after it. */\n StructuralItemUnkeyed: 'LP0404',\n /** Two structural items share a key; later ones pair positionally. */\n StructuralDuplicateKey: 'LP0405',\n /** Every structural key changed while the length did not; the source generates keys per message. */\n StructuralUnstableKeys: 'LP0406',\n /** A binding asks for a delivery strategy this release does not have; it is left unchanged. */\n UnsupportedStrategy: 'LP0407',\n /** `data-payload-format` names a format the vocabulary does not contain; the default formatting was used instead. */\n UnknownValueFormat: 'LP0408',\n /** The strict sanitizer removed an attribute the 1.x `'compat'` policy kept. */\n SanitizerDroppedAttribute: 'LP0409',\n /** A Lexical block has no renderer; the markup the server rendered for it is kept instead. */\n UnrenderedBlockKept: 'LP0410',\n /** A patch cannot reach what the server would have drawn; `onUnfaithfulPatch` decided what happens next. */\n UnfaithfulPatch: 'LP0411',\n /** The first write to a date, number, checkbox, select or radio binding replaced a different reading of the same value. */\n ServerFormatReplaced: 'LP0412',\n /** A Lexical block has no renderer, and the write could not keep the markup the server rendered for it; it is gone. */\n UnrenderedBlockLost: 'LP0413',\n\n /** A message was rejected before it reached the update pipeline. */\n MessageRejected: 'LP0501',\n /** A preview token was rejected. */\n TokenRejected: 'LP0502',\n /** A trusted origin sent a message this protocol version does not recognise. */\n ProtocolShapeUnknown: 'LP0503',\n\n /** A consumer event handler threw. */\n HandlerThrew: 'LP0601',\n /** A consumer transform threw; the original value was kept. */\n TransformThrew: 'LP0602',\n /** A renderer threw while writing a value. */\n RendererThrew: 'LP0603',\n // LP0604 is reserved and unassigned; codes are never reused or renumbered.\n /** Runtime startup failed. */\n StartupFailed: 'LP0605',\n /** Sending the ready handshake failed. */\n ReadyFailed: 'LP0606',\n /** The page declared hydration, but no React commit, Vue mount, SvelteKit mount or `markHydrated()` call came within the cap; the runtime started without waiting. */\n HydrationWaitTimedOut: 'LP0607',\n\n /** The audit found no runtime in a response that carried preview intent. */\n AuditRuntimeMissing: 'LP0701',\n /** The preview response declares no `frame-ancestors`. */\n AuditNoFrameAncestors: 'LP0702',\n /** `X-Frame-Options` forbids framing on a response that declares no `frame-ancestors`; `SAMEORIGIN` forbids it from another origin only. */\n AuditFrameOptionsBlocks: 'LP0703',\n /** Binding attributes are served to anonymous visitors. */\n AuditBindingsExposed: 'LP0704',\n /** More bindings than the visibility gate will write eagerly. */\n AuditGateThresholdExceeded: 'LP0705',\n /** Owner markers exist, but some bindings are outside all of them. */\n AuditUnownedBindings: 'LP0706',\n /** The preview response carries no bindings and no fragment boundary. */\n AuditNoBindings: 'LP0707',\n /** The URL did not return an HTML page, so nothing else can be judged. */\n AuditNotAPage: 'LP0708',\n /** A fragment request failed (network, timeout, server error); the boundary was patched instead. */\n FragmentRequestFailed: 'LP0801',\n /** The fragment response had the wrong content type or shape; the boundary was patched instead. */\n FragmentResponseInvalid: 'LP0802',\n /** The fragment endpoint refused the preview (not authorized); the boundary was patched instead. */\n FragmentUnauthorized: 'LP0803',\n /** A fragment response arrived for a revision that was already superseded and was discarded. */\n FragmentSuperseded: 'LP0804',\n /** A route refresh was refused: the loop guard, or the strategy's minimum interval. */\n RouteRefreshLoop: 'LP0805',\n /** A boundary asks for the fragment strategy but no fragment handler is configured; it is patched. */\n FragmentStrategyUnavailable: 'LP0806',\n /** A revision changed a field with no binding; `onUnfaithfulPatch` refreshed the route. */\n UnboundChangeRefresh: 'LP0807',\n /** `onUnfaithfulPatch: 'escalate'` found no route or fragment strategy, or no boundary, to hand an unfaithful patch to; the page keeps it. */\n EscalationUnavailable: 'LP0808',\n /** A fragment inserted an Astro island the page cannot start: it never loaded the island element or that client directive. */\n IslandStartUnavailable: 'LP0809',\n /** The host's route refresh returned no promise, so the revision was re-applied before the host committed. */\n RouteRefreshUnconfirmed: 'LP0810',\n /** A readiness row is not yet at its 2.0 value; `pll doctor --v2` reports it. */\n V2ReadinessGap: 'LP0709',\n /** The preview runtime is served to anonymous visitors, not only inside the admin frame. */\n RuntimeOnPublicPage: 'LP0710',\n /** An element carries attributes that only mean something beside `data-payload-field`, and has none. */\n AuditModifierWithoutField: 'LP0711',\n} as const);\n\n/** A diagnostic code, as it appears in log output and on the `error` event. */\nexport type DiagnosticCode = (typeof DIAGNOSTIC_CODES)[keyof typeof DIAGNOSTIC_CODES];\n","/**\n * `frame-ancestors` matching by the CSP3 source-expression rules. The audit\n * judges origins, so path parts of a host-source are ignored.\n */\n\nconst DEFAULT_PORT: Readonly<Record<string, string>> = {\n http: '80',\n https: '443',\n ws: '80',\n wss: '443',\n};\nconst SCHEME_SOURCE = /^([a-z][a-z0-9+.-]*):$/u;\nconst HOST_SOURCE =\n /^(?:([a-z][a-z0-9+.-]*):\\/\\/)?(\\*|(?:\\*\\.)?[a-z0-9-]+(?:\\.[a-z0-9-]+)*\\.?)(?::(\\*|\\d+))?(?:\\/.*)?$/u;\n\n/** The `frame-ancestors` directive of a policy, verbatim, or `undefined` when it has none. */\nexport function frameAncestorsOf(csp: string | undefined): string | undefined {\n if (csp === undefined) return undefined;\n for (const directive of csp.split(';')) {\n const trimmed = directive.trim();\n if (/^frame-ancestors(?:\\s|$)/iu.test(trimmed)) return trimmed;\n }\n return undefined;\n}\n\nfunction schemeOf(url: URL): string {\n return url.protocol.slice(0, -1).toLowerCase();\n}\n\nfunction portOf(url: URL): string {\n return url.port === '' ? (DEFAULT_PORT[schemeOf(url)] ?? '') : url.port;\n}\n\nfunction schemeMatches(expression: string, actual: string): boolean {\n return (\n expression === actual ||\n (expression === 'http' && actual === 'https') ||\n (expression === 'ws' && actual === 'wss')\n );\n}\n\nfunction hostMatches(pattern: string, host: string): boolean {\n if (pattern === '*') return true;\n const wanted = pattern.replace(/\\.$/u, '');\n const actual = host.replace(/\\.$/u, '');\n if (wanted.startsWith('*.')) {\n return actual.endsWith(wanted.slice(1)) && actual.length > wanted.length - 1;\n }\n return wanted === actual;\n}\n\nfunction isSelf(admin: URL, page: URL): boolean {\n if (admin.hostname !== page.hostname) return false;\n const adminScheme = schemeOf(admin);\n const pageScheme = schemeOf(page);\n if (adminScheme !== pageScheme && !(pageScheme === 'http' && adminScheme === 'https')) {\n return false;\n }\n return portOf(admin) === portOf(page) || (admin.port === '' && page.port === '');\n}\n\nfunction sourceMatches(source: string, admin: URL, page: URL): boolean {\n const scheme = SCHEME_SOURCE.exec(source);\n if (scheme?.[1] !== undefined) return schemeMatches(scheme[1], schemeOf(admin));\n const host = HOST_SOURCE.exec(source);\n if (host === null) return false;\n const expressionScheme = host[1];\n const expressionHost = host[2] ?? '';\n const expressionPort = host[3];\n const requiredScheme = expressionScheme ?? schemeOf(page);\n if (!schemeMatches(requiredScheme, schemeOf(admin))) return false;\n if (!hostMatches(expressionHost, admin.hostname)) return false;\n if (expressionPort === undefined) return admin.port === '';\n return expressionPort === '*' || expressionPort === portOf(admin);\n}\n\n/** Whether the directive admits `admin` as an ancestor of `page`. */\nexport function frameAncestorsAdmits(directive: string, admin: URL, page: URL): boolean {\n const sources = directive\n .split(/\\s+/u)\n .slice(1)\n .filter((source) => source.length > 0)\n .map((source) => source.toLowerCase());\n if (sources.length === 0 || (sources.length === 1 && sources[0] === \"'none'\")) return false;\n for (const source of sources) {\n if (source === \"'self'\") {\n if (isSelf(admin, page)) return true;\n } else if (source === '*') {\n return true;\n } else if (!source.startsWith(\"'\") && sourceMatches(source, admin, page)) {\n return true;\n }\n }\n return false;\n}\n","/**\n * The inline script's configuration and its positional wire order: the\n * generator writes the tuple, `src/core/runtime.ts` destructures it, and\n * `INLINE_CONFIG_KEYS` is where they agree. Append only, never reorder.\n */\n\nimport type { DefaultsProfile } from '@/types/defaults-profile';\n\nexport interface InlineScriptConfig {\n /** Trusted admin origins, merged with the detected ones. */\n readonly allowedOrigins?: readonly string[];\n /** Payload origin; every update is then re-fetched through the REST API with credentials. Requires `mergeDepth`. */\n readonly serverURL?: string;\n /** REST route prefix used with `serverURL`. Default `/api`. */\n readonly apiRoute?: string;\n /** Population depth used with `serverURL`. Required with `serverURL` unless `defaults: 'v1'`. */\n readonly mergeDepth?: number;\n /** Verbose console logging. Default `false`. */\n readonly debug?: boolean;\n /** Debounce window for incoming updates in ms. Default `50`. */\n readonly debounceMs?: number;\n /** Mount the screen-reader live region. Default `true`. */\n readonly enableA11y?: boolean;\n /** Heartbeat timeout in ms; `0` disables it (default), because the admin posts only on edits. */\n readonly heartbeatMs?: number;\n /** Apply every update regardless of viewport position. Default `false`. */\n readonly disableVisibilityGate?: boolean;\n /** Cache size above which off-screen updates wait for intersection. Default `50`. */\n readonly visibilityGateThreshold?: number;\n /** `rootMargin` of the IntersectionObserver. Default `'200px'`. */\n readonly intersectionRootMargin?: string;\n /** Ignore `document.referrer` as an origin source. Default `true`; `defaults: 'v1'` sets `false`. */\n readonly disableReferrerDetection?: boolean;\n /** Disable the dev-mode localhost matcher (any port on `localhost`/`127.0.0.1`). Default `false`. */\n readonly disableLocalhostMatching?: boolean;\n /** Update only bindings under a matching `data-payload-owner`. Default `false`. */\n readonly scopeBindingsByOwner?: boolean;\n /** Skip bindings whose value did not change. Default `true`; `defaults: 'v1'` sets `false`. */\n readonly skipUnchanged?: boolean;\n /** Scroll the preview to the field being edited. Default `false`. */\n readonly revealEditedField?: boolean;\n /** Which windows may post updates. Default `'parent-or-opener'`; `defaults: 'v1'` sets `'any'`. */\n readonly eventSourcePolicy?: 'any' | 'parent-or-opener';\n /** Sanitizer policy for rich text and HTML writes. Default `'strict'`; `defaults: 'v1'` sets `'compat'`. */\n readonly sanitizerPolicy?: 'compat' | 'strict';\n /** Same-origin path of a fragment endpoint; the script then carries the fragment client ahead of the runtime (ADR 0011). */\n readonly fragmentEndpoint?: string;\n /**\n * Carry the route strategy, so a binding in `<head>` or one marked\n * `data-payload-strategy=\"route\"` refreshes the route. `fragmentEndpoint`\n * implies it — the fragment prelude already contains it. Default `false`.\n * `'after-save'` renders the route only once Payload reports a save: before it, a\n * GET shows nothing the editor typed and costs a server render.\n */\n readonly routeStrategy?: boolean | 'after-save';\n /**\n * The 2.0 name for `onUnfaithfulPatch`, kept until 3.0. `'route'` means\n * `'escalate'`, `'ignore'` means `'ignore'`.\n *\n * @deprecated Renamed to `onUnfaithfulPatch`.\n */\n readonly onUnboundChange?: 'ignore' | 'route';\n /**\n * What to do when the runtime knows a patch cannot reach what the server\n * would have drawn. `'escalate'` (the default) hands the region to the\n * fragment strategy when a boundary covers it and to the route otherwise, so\n * it needs `fragmentEndpoint` or `routeStrategy` to do anything. `'warn'`\n * reports LP0411 and keeps the patch; `'ignore'` keeps it silently.\n */\n readonly onUnfaithfulPatch?: 'ignore' | 'warn' | 'escalate';\n /**\n * Find bindings by value on the connection's first message (ADR 0014): a\n * scalar whose value is the whole content of exactly one element is bound\n * to it as if `data-payload-field` stood there. Default `'off'`.\n */\n readonly autoBind?: 'off' | 'unique';\n /**\n * The framework that hydrates this page (ADR 0015). Under `'react'` the\n * runtime holds its first write until React has committed the tree that\n * holds the bindings — otherwise React finds markup it did not render,\n * throws `Hydration failed` and regenerates the tree, and the write is gone.\n * Under `'vue'` it holds the write until Vue has mounted the app around them\n * — otherwise Vue's hydration repairs the write back to the server's value,\n * quietly. Under `'sveltekit'` it holds the write until SvelteKit's root has\n * mounted, on a page SvelteKit's client runs on — otherwise Svelte's\n * hydration sets its own text back over it, as quietly. The Next.js, Nuxt\n * and SvelteKit adapters set it on every script they emit; a page built by\n * hand with `generateInlineScript()` may. Under `'manual'` it holds the write\n * until the page calls `markHydrated()`, for a framework this package cannot\n * observe. Omitted: the runtime starts on `DOMContentLoaded`, as on a static page.\n */\n readonly hydration?: 'react' | 'vue' | 'sveltekit' | 'manual';\n /**\n * Which defaults the omitted options fall back to, resolved by the generator\n * rather than the runtime, whose own fallbacks are the 2.0 rows: `'v1'`\n * writes its four runtime rows into their slots (an explicit option still\n * wins) and relaxes the `mergeDepth` check. The resolved value always\n * travels in slot 24, so a reader of the served page knows what an\n * empty slot means instead of guessing it — `pll doctor --v2` reads it. The\n * runtime does not use that value: every row the profile decides is already\n * in its slot. Slot 24 stays fixed as later options are appended.\n */\n readonly defaults?: DefaultsProfile;\n /**\n * Document events fired after a client router committed new markup. Each\n * rebuilds the binding cache and locally reapplies the last accepted state.\n * Adapter integrations set this from their framework lifecycle. It is a\n * low-level generator fact, not a `PreviewAdapterOptions` field. Empty by\n * default.\n */\n readonly softNavigationEvents?: readonly string[];\n /**\n * How a group counts as covered (ADR 0022). `'descendant'`, the 2.x default:\n * one bound path inside it covers all of it. `'declared'`: each changed path\n * inside such a group needs its own binding or a `data-payload-covers`\n * declaration; an uncovered one is an unbound change.\n */\n readonly subfieldCoverage?: 'descendant' | 'declared';\n /** Milliseconds the `serverURL` merge may wait before the update shows its raw values. Off by default. */\n readonly mergeTimeoutMs?: number;\n /** The route of a Lexical internal link with no `url` of its own, as `/{locale}/{slug}/`. */\n readonly internalLinkPattern?: string;\n /**\n * A runtime artifact to embed instead of the full one — today only\n * `LEAN_RUNTIME` from `payload-live-preview/lean`, which leaves out the\n * strategies, the keyed morph, the structural arrays, the item templates, the\n * announcer and auto-binding (about 7 KB gzip less on the page) and reports\n * LP0104 when a page needs one of them.\n *\n * It is an imported value rather than a `profile: 'lean'` string on purpose:\n * a second artifact behind a string option would sit in every build that can\n * reach the generator; measured, it grew each adapter entry by the artifact's\n * whole size. This way the bytes follow the import.\n *\n * Not serialized: it decides which bytes are emitted, not how they behave.\n */\n readonly runtime?: RuntimeArtifact;\n}\n\n/**\n * A runtime build this package produces. `source` is the IIFE the page runs;\n * the two digests describe the same bytes as a servable asset, so asset\n * delivery can hash-name and SRI-verify whichever artifact was chosen without\n * hashing anything at request time.\n */\nexport interface RuntimeArtifact {\n readonly profile: 'lean';\n readonly source: string;\n /** Short content hash, for the asset's file name. */\n readonly contentHash: string;\n /** `sha384-…`, for the bootstrap's `integrity` attribute. */\n readonly integrity: string;\n}\n\n/** Keys that travel in the wire tuple, in slot order. */\nexport const INLINE_CONFIG_KEYS = [\n 'allowedOrigins',\n 'serverURL',\n 'apiRoute',\n 'mergeDepth',\n 'debug',\n 'debounceMs',\n 'enableA11y',\n 'heartbeatMs',\n 'disableVisibilityGate',\n 'visibilityGateThreshold',\n 'intersectionRootMargin',\n 'disableReferrerDetection',\n 'disableLocalhostMatching',\n 'scopeBindingsByOwner',\n 'skipUnchanged',\n 'eventSourcePolicy',\n 'sanitizerPolicy',\n 'fragmentEndpoint',\n 'revealEditedField',\n 'routeStrategy',\n 'onUnboundChange',\n 'onUnfaithfulPatch',\n 'autoBind',\n 'hydration',\n 'defaults',\n 'softNavigationEvents',\n 'subfieldCoverage',\n 'mergeTimeoutMs',\n 'internalLinkPattern',\n] as const satisfies readonly Exclude<keyof InlineScriptConfig, 'runtime'>[];\n","/**\n * The two defaults profiles, row by row. 2.0 ships `v2`; `defaults: 'v1'` opts\n * back into the 1.x table for a staged migration. Each row belongs to exactly\n * one side — the adapter's request decision or the browser runtime — and a\n * test asserts the profile assigns every readiness row that has an option, so\n * a row added later cannot be forgotten. See ADR 0007.\n *\n * It lives in the leaf `types` domain, like `merge-depth.ts`, because the\n * inline generator resolves the profile too, and the generator is server-side\n * code that may not reach the browser runtime in `core`.\n */\n\n/** `'v2'` is the 2.0 default; `'v1'` is the 1.x table. */\nexport type DefaultsProfile = 'v1' | 'v2';\n\n/** How the runtime decides which windows may post updates. */\nexport type EventSourcePolicy = 'any' | 'parent-or-opener';\n\n/** Readiness rows that are options, keyed by the option they set. */\nexport const READINESS_ROWS = Object.freeze({\n strict: 'production response changes require an authorized context',\n previewSignals: 'query-only intent signal',\n disableReferrerDetection: 'referrer trust off',\n eventSourcePolicy: 'messages must come from parent/opener',\n skipUnchanged: 'skip unchanged bindings by default',\n sanitizerPolicy: 'hardened sanitizer id/data-* policy',\n} as const);\n\n/** Options an adapter's request decision reads from the profile. */\nexport interface AdapterProfileDefaults {\n readonly strict: boolean;\n readonly previewSignals: readonly ('query' | 'fetch-dest' | 'referer')[];\n}\n\n/** Options the browser runtime reads from the profile (through the inline configuration). */\nexport interface RuntimeProfileDefaults {\n readonly disableReferrerDetection: boolean;\n readonly eventSourcePolicy: EventSourcePolicy;\n readonly skipUnchanged: boolean;\n readonly sanitizerPolicy: 'compat' | 'strict';\n}\n\nexport const V2_ADAPTER_DEFAULTS: AdapterProfileDefaults = Object.freeze({\n strict: true,\n previewSignals: Object.freeze(['query'] as const),\n});\n\nexport const V2_RUNTIME_DEFAULTS: RuntimeProfileDefaults = Object.freeze({\n disableReferrerDetection: true,\n eventSourcePolicy: 'parent-or-opener',\n skipUnchanged: true,\n sanitizerPolicy: 'strict',\n});\n\n/** The 1.x table, spelled out so the profiles compare row by row. */\nexport const V1_ADAPTER_DEFAULTS: AdapterProfileDefaults = Object.freeze({\n strict: false,\n previewSignals: Object.freeze(['query', 'fetch-dest', 'referer'] as const),\n});\n\nexport const V1_RUNTIME_DEFAULTS: RuntimeProfileDefaults = Object.freeze({\n disableReferrerDetection: false,\n eventSourcePolicy: 'any',\n skipUnchanged: false,\n sanitizerPolicy: 'compat',\n});\n\n/** Only an explicit `'v1'` opts out; everything else gets the hardened rows. */\nexport function adapterDefaultsFor(profile: DefaultsProfile | undefined): AdapterProfileDefaults {\n return profile === 'v1' ? V1_ADAPTER_DEFAULTS : V2_ADAPTER_DEFAULTS;\n}\n\nexport function runtimeDefaultsFor(profile: DefaultsProfile | undefined): RuntimeProfileDefaults {\n return profile === 'v1' ? V1_RUNTIME_DEFAULTS : V2_RUNTIME_DEFAULTS;\n}\n","/**\n * `pll doctor --v2`: read the served configuration and report each runtime row\n * still at its 1.x value (ADR 0007).\n *\n * An empty slot means whatever the generation that wrote the script meant by\n * it, and that is exactly what changed between 1.x and 2.0. So the script\n * names the defaults it was resolved against in slot 24, and this reads\n * the answer instead of guessing it. A script without the marker predates it —\n * 1.x or 2.0.0-beta.0 — and is read as 1.x, with a line that says so.\n */\nimport { INLINE_CONFIG_KEYS } from '@/types/inline-config';\nimport {\n V1_RUNTIME_DEFAULTS,\n V2_RUNTIME_DEFAULTS,\n type RuntimeProfileDefaults,\n} from '@/types/defaults-profile';\nimport type { DoctorFinding, DoctorProbe } from './types';\n\nconst CONFIG_NAME = '__LIVE_PREVIEW_CONFIG__';\nconst CONFIG_MARKER = `var ${CONFIG_NAME}=`;\n// A comment or a script, in document order, so a script inside a comment is\n// skipped as the browser skips it. A quoted attribute value may hold `>`.\nconst COMMENT_OR_SCRIPT =\n /<!--[\\s\\S]*?-->|<script\\b((?:[^>\"']|\"[^\"]*\"|'[^']*')*)>([\\s\\S]*?)<\\/script\\b[^>]*>/giu;\n// The attribute name in any case, the value exactly, as `getElementById` compares it.\nconst CONFIG_ID = /(?:^|\\s)[iI][dD]\\s*=\\s*[\"']?__LIVE_PREVIEW_CONFIG__(?=[\"'\\s]|$)/u;\nconst JSON_TYPE = /(?:^|\\s)type\\s*=\\s*[\"']?application\\/json(?=[\"'\\s;]|$)/iu;\n\ntype InlineKey = (typeof INLINE_CONFIG_KEYS)[number];\n\n/** The configuration a preview response serves, whichever way the page writes it. */\nexport interface ServedConfig {\n /** `inline`: the generator's tuple. `element`: the JSON a page writes for a client it starts itself. */\n readonly source: 'inline' | 'element';\n /** The option under its inline name; `undefined` when the page does not set it. */\n value(key: InlineKey): unknown;\n}\n\n/** A runtime row of the readiness table, as a finding words it. */\ninterface ReadinessRow {\n readonly key: keyof RuntimeProfileDefaults;\n readonly title: string;\n /** What the 1.x value does. */\n readonly effect: string;\n /** What has to hold before the row can take its 2.0 value. */\n readonly condition: string;\n}\n\nconst ROWS: readonly ReadinessRow[] = [\n {\n key: 'disableReferrerDetection',\n title: 'Referrer trust is still on',\n effect: 'the runtime accepts the admin referer as a preview signal.',\n condition: 'no preview flow relies on the admin referer alone',\n },\n {\n key: 'eventSourcePolicy',\n title: 'Messages are accepted from any window',\n effect: 'any window that passes the origin check may post updates.',\n condition: 'no window but the parent or opener posts updates',\n },\n {\n key: 'sanitizerPolicy',\n title: 'Sanitizer is in compat mode',\n effect: 'id and every data-* pass.',\n condition: 'rich text no longer relies on `id` or `data-*`',\n },\n {\n key: 'skipUnchanged',\n title: 'Unchanged bindings are re-applied every message',\n effect: 'every binding is written again on every message.',\n condition: 'no renderer relies on running again for a value that did not change',\n },\n];\n\n/** Where a row's value came from, which decides what the reader can change. */\ntype Origin = 'profile' | 'explicit' | 'unmarked';\n\nconst ORIGIN_DETAIL: Readonly<Record<Origin, string>> = {\n profile: \"The script was generated with `defaults: 'v1'`.\",\n explicit: 'The script sets it explicitly.',\n unmarked: 'The script names no defaults, so its empty slot is read as the 1.x value.',\n};\n\nconst UNREADABLE: DoctorFinding = {\n code: 'LP0709',\n level: 'info',\n title: 'Could not read the inline configuration for a v2 readiness check',\n detail:\n 'The preview response carried no readable `__LIVE_PREVIEW_CONFIG__` inline script or JSON configuration element.',\n remedy:\n 'Run this against a page with the inline runtime (not loader mode without a preview context). ' +\n 'A preview behind authorizePreview serves it only to a request with credentials: pass them with --header, or a signed token as ?previewToken=… in the URL.',\n};\n\nconst UNMARKED: DoctorFinding = {\n code: 'LP0709',\n level: 'info',\n title: 'The inline configuration does not say which defaults it was generated against',\n detail:\n 'Scripts carry a `defaults` marker from 2.0.0-rc.1 on; this one has none, so a 1.x or 2.0.0-beta.0 generator wrote it. An empty slot is read as its 1.x value, which is what a 1.x runtime runs.',\n remedy:\n 'On 1.x the warnings stand as written. On 2.0.0-beta.0 an empty slot already runs its 2.0 value, so a warning about one does not apply there; upgrade and run `pll doctor --v2` again to read the marker.',\n};\n\n/**\n * The array literal after the marker, read as JSON. The generator writes `,,`\n * for unset slots, which JSON cannot express, so elisions outside strings\n * become `null`. Never evaluated: a page controls this text.\n */\nexport function readInlineConfig(body: string): readonly unknown[] | undefined {\n const start = body.indexOf(CONFIG_MARKER);\n if (start === -1) return undefined;\n const from = start + CONFIG_MARKER.length;\n if (body[from] !== '[') return undefined;\n let depth = 0;\n let inString = false;\n let previous = '';\n let json = '';\n for (let index = from; index < body.length; index += 1) {\n const char = body[index] ?? '';\n if (inString) {\n json += char;\n if (char === '\\\\') {\n json += body[index + 1] ?? '';\n index += 1;\n } else if (char === '\"') {\n inString = false;\n // A closed string is a value: without this, the next `,` reads as an elision.\n previous = '\"';\n }\n continue;\n }\n if (char === '\"') {\n inString = true;\n json += char;\n continue;\n }\n if (/\\s/u.test(char)) continue;\n const elision =\n (char === ',' && (previous === ',' || previous === '[')) ||\n (char === ']' && previous === ',');\n if (elision) json += 'null';\n json += char;\n previous = char;\n if (char === '[') depth += 1;\n if (char === ']') {\n depth -= 1;\n if (depth === 0) break;\n }\n }\n if (depth !== 0) return undefined;\n try {\n const value: unknown = JSON.parse(json);\n return Array.isArray(value) ? (value as readonly unknown[]) : undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * The inline tuple first, because it is what the runtime reads; else a\n * `<script type=\"application/json\" id=\"__LIVE_PREVIEW_CONFIG__\">` holding an\n * object that names the options as the client and the inline script spell\n * them. That is how a page that starts `LivePreviewClient` itself can show the\n * doctor its options. Never evaluated: a page controls this text.\n * @internal\n */\nexport function readServedConfig(body: string): ServedConfig | undefined {\n // Both ways spell the name, and the scan below is slow on hostile markup.\n if (!body.includes(CONFIG_NAME)) return undefined;\n const scripts = servedScripts(body);\n const inline = scripts.find(({ text }) => text.includes(CONFIG_MARKER));\n const tuple = inline === undefined ? undefined : readInlineConfig(inline.text);\n if (tuple !== undefined) {\n return {\n source: 'inline',\n value: (key) => tuple[INLINE_CONFIG_KEYS.indexOf(key)] ?? undefined,\n };\n }\n const named = readConfigElement(scripts);\n if (named === undefined) return undefined;\n return {\n source: 'element',\n value: (key) => (Object.hasOwn(named, key) ? (named[key] ?? undefined) : undefined),\n };\n}\n\ninterface ServedScript {\n readonly attributes: string;\n readonly text: string;\n}\n\n/** The scripts a browser would run or read, without those inside a comment. */\nfunction servedScripts(body: string): readonly ServedScript[] {\n const scripts: ServedScript[] = [];\n for (const [, attributes, text] of body.matchAll(COMMENT_OR_SCRIPT)) {\n // A comment captures neither group.\n if (attributes !== undefined && text !== undefined) scripts.push({ attributes, text });\n }\n return scripts;\n}\n\nfunction readConfigElement(\n scripts: readonly ServedScript[],\n): Readonly<Record<string, unknown>> | undefined {\n for (const { attributes, text } of scripts) {\n if (!CONFIG_ID.test(attributes)) continue;\n if (!JSON_TYPE.test(attributes)) return undefined;\n try {\n const value: unknown = JSON.parse(text);\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n ? (value as Readonly<Record<string, unknown>>)\n : undefined;\n } catch {\n return undefined;\n }\n }\n return undefined;\n}\n\n/** `'compat'` rather than `\"compat\"`: remedies are read as source code. */\nfunction literal(value: unknown): string {\n return typeof value === 'string' ? `'${value}'` : JSON.stringify(value);\n}\n\nfunction remedyFor(origin: Origin, row: ReadinessRow, value: unknown): string {\n const wanted = `\\`${row.key}: ${literal(V2_RUNTIME_DEFAULTS[row.key])}\\``;\n switch (origin) {\n case 'profile':\n return (\n `Set ${wanted} or drop \\`defaults: 'v1'\\` once ${row.condition}. 3.0 removes the ` +\n 'profile; `pll migrate` writes its rows out, so each can go on its own.'\n );\n case 'explicit':\n return `Drop the explicit \\`${row.key}: ${literal(value)}\\` once ${row.condition}; 2.0 defaults to ${wanted}.`;\n case 'unmarked':\n // 1.8.1 has no `defaults` option and three of the four rows are not\n // inline options there, so the way out is the upgrade itself.\n return `Upgrading to 2.0 sets ${wanted}; check first that ${row.condition}.`;\n }\n}\n\nfunction gap(row: ReadinessRow, value: unknown, origin: Origin): DoctorFinding {\n return {\n code: 'LP0709',\n level: 'warning',\n title: row.title,\n detail: `${row.key} is ${literal(value)}: ${row.effect} ${ORIGIN_DETAIL[origin]}`,\n remedy: remedyFor(origin, row, value),\n };\n}\n\nfunction unknownGeneration(marker: unknown): DoctorFinding {\n return {\n code: 'LP0709',\n level: 'info',\n title: 'The inline configuration names defaults this doctor does not know',\n detail: `Its \\`defaults\\` marker is ${JSON.stringify(marker)}; this version reads 'v1' and 'v2', so no row was judged.`,\n remedy: 'Run the `pll doctor` of the package version that generated the page.',\n };\n}\n\n/**\n * One `LP0709` finding per runtime row not yet at its 2.0 value. An empty slot\n * has the value of the generation the script names, and the 1.x value when it\n * names none; a generation this version does not know is not judged at all.\n * A configuration element is written for a 2.x client, whose omitted options\n * are the 2.0 rows unless it names `'v1'`.\n * @internal\n */\nexport function analyzeV2Readiness(probe: DoctorProbe): readonly DoctorFinding[] {\n const config = readServedConfig(probe.previewResponse.body);\n if (config === undefined) return [UNREADABLE];\n const marker = config.value('defaults');\n if (marker !== undefined && marker !== 'v1' && marker !== 'v2') {\n return [unknownGeneration(marker)];\n }\n const generation = marker ?? (config.source === 'element' ? 'v2' : undefined);\n const fallback = generation === 'v2' ? V2_RUNTIME_DEFAULTS : V1_RUNTIME_DEFAULTS;\n const findings = ROWS.flatMap((row) => {\n const slot = config.value(row.key);\n const value = slot ?? fallback[row.key];\n if (value === V2_RUNTIME_DEFAULTS[row.key]) return [];\n const origin: Origin =\n generation === 'v1' ? 'profile' : slot === undefined ? 'unmarked' : 'explicit';\n return [gap(row, value, origin)];\n });\n return generation === undefined ? [...findings, UNMARKED] : findings;\n}\n","/**\n * The `pll doctor` audit: judge what a deployment serves from two fetches of\n * one URL, as a visitor and as the admin's iframe. Pure — the caller fetches.\n * Markup is read with regular expressions, which is enough to count\n * attributes inside start tags and find the runtime marker; findings say what\n * was observed.\n */\nimport { DIAGNOSTIC_CODES } from '../core/diagnostic-codes';\nimport { frameAncestorsAdmits, frameAncestorsOf } from './csp';\nimport { analyzeV2Readiness, readServedConfig } from './readiness';\nimport type {\n DoctorContext,\n DoctorFinding,\n DoctorProbe,\n DoctorReport,\n DoctorResponse,\n} from './types';\n\n/** Survives minification; the banner comment does not. */\nconst RUNTIME_MARKER = '__LIVE_PREVIEW_CONFIG__';\n/**\n * A start tag, quoted values read whole so a `>` inside one does not end it.\n * Text is never a tag: `<h1 data-payload-field=…>` in a code sample, or\n * a sentence that names the attribute, is what a page shows, not what it binds.\n * A raw `<` outside quotes ends the attempt, which keeps the scan linear: with\n * it, `<a<a<a…` and no `>` took 645 ms for 20 000 repetitions, 15 times the\n * time of 5 000.\n */\nconst START_TAG = /<[a-zA-Z](?:\"[^\"]*\"|'[^']*'|[^\"'<>])*>/gu;\n/** One attribute of a start tag: its name, and its value when it has one. */\nconst TAG_ATTRIBUTE = /([^\\s\"'>/=]+)(\\s*=\\s*(?:\"([^\"]*)\"|'([^']*)'|([^\\s\"'=<>`]+)))?/gu;\n/** Mirrors the scheduler default; the audit must not import the runtime. */\nconst DEFAULT_VISIBILITY_GATE_THRESHOLD = 50;\nconst LEVEL_ORDER = { error: 0, warning: 1, info: 2 } as const;\n// An end tag may carry junk before its `>`; browsers still close on it, so the\n// pattern must too, or a `</script foo>` would leave the script body counted.\nconst NON_MARKUP =\n /<script\\b[^>]*>[\\s\\S]*?<\\/script\\b[^>]*>|<style\\b[^>]*>[\\s\\S]*?<\\/style\\b[^>]*>|<!--[\\s\\S]*?-->/giu;\n\n/**\n * The runtime's own source spells `data-payload-field=` in a message, so\n * scripts must not be counted. This is a counter's view of the page, not a\n * sanitiser — nothing here is rendered — but it strips to a fixed point all\n * the same, so a block that only appears once its neighbour is gone is still\n * removed rather than counted.\n */\nfunction visibleMarkup(body: string): string {\n let markup = body;\n for (;;) {\n const stripped = markup.replace(NON_MARKUP, '');\n if (stripped === markup) return markup;\n markup = stripped;\n }\n}\n\n/** Read only beside `data-payload-field`; an owner, a strategy or a dependency also means something alone. */\nconst FIELD_MODIFIERS: ReadonlySet<string> = new Set(\n [\n 'type',\n 'attribute',\n 'href',\n 'src',\n 'alt',\n 'richtext',\n 'html',\n 'text',\n 'array',\n 'array-template',\n 'array-separator',\n 'structural',\n 'locale',\n 'format',\n 'boundary',\n ].map((name) => `data-payload-${name}`),\n);\n\n/** An attribute as the markup carries it, at the offset of its tag. */\ninterface MarkupAttribute {\n readonly name: string;\n readonly value: string;\n readonly at: number;\n}\n\n/**\n * Every attribute with a value, in document order. Names are compared in lower\n * case, as a browser parses them; the tag name is read along and never matches\n * an attribute the audit asks about.\n */\nfunction markupAttributes(markup: string): readonly MarkupAttribute[] {\n const found: MarkupAttribute[] = [];\n for (const tag of markup.matchAll(START_TAG)) {\n for (const [, name = '', assigned, double, single, bare] of tag[0].matchAll(TAG_ATTRIBUTE)) {\n if (assigned === undefined) continue;\n found.push({\n name: name.toLowerCase(),\n value: double ?? single ?? bare ?? '',\n at: tag.index,\n });\n }\n }\n return found;\n}\n\n/** The modifiers on start tags that bind no field, one entry per tag. */\nfunction modifiersWithoutField(attributes: readonly MarkupAttribute[]): readonly string[] {\n const bound = new Set(named(attributes, 'data-payload-field').map((attribute) => attribute.at));\n const tags = new Map<number, string>();\n for (const { name, at } of attributes) {\n if (FIELD_MODIFIERS.has(name) && !bound.has(at) && !tags.has(at)) tags.set(at, name);\n }\n return [...tags.values()];\n}\n\nfunction named(attributes: readonly MarkupAttribute[], name: string): readonly MarkupAttribute[] {\n return attributes.filter((attribute) => attribute.name === name);\n}\n\n/** Bindings before the first owner marker are certainly unowned; nesting is not modelled, so this under-reports. */\nfunction unownedBindingsBeforeFirstOwner(attributes: readonly MarkupAttribute[]): number {\n const firstOwner = named(attributes, 'data-payload-owner')[0];\n if (firstOwner === undefined) return 0;\n return named(attributes, 'data-payload-field').filter((binding) => binding.at < firstOwner.at)\n .length;\n}\n\nfunction tryUrl(value: string | undefined): URL | undefined {\n if (value === undefined) return undefined;\n try {\n return new URL(value);\n } catch {\n return undefined;\n }\n}\n\n/** Why the preview response cannot be audited as a page; the public one may legitimately be gated. */\nfunction describeNonPage(preview: DoctorResponse): DoctorFinding | undefined {\n if (preview.status >= 300 && preview.status <= 399) {\n const location = preview.headers['location'];\n return {\n code: 'LP0708',\n level: 'error',\n title: `The preview request was redirected (${String(preview.status)})${location === undefined ? '' : ` to ${location}`}`,\n detail:\n 'The audit does not follow redirects: the page at the end of one is a different ' +\n 'resource, usually a login or a canonical host, and judging it would say nothing ' +\n 'about this URL.',\n remedy:\n 'The request carried the intent parameter (?preview=true, or the --param name), the ' +\n \"way the admin's iframe loads the page, so a rule that redirects on that query redirects \" +\n 'the iframe too. A redirect to a login means the route needs authentication that a ' +\n \"plain probe cannot supply: pass an editor's credentials with --header, or a signed token as ?previewToken=… in the URL. Probe the final \" +\n 'URL directly only when the redirect has nothing to do with either.',\n };\n }\n if (preview.status < 200 || preview.status > 299) {\n return {\n code: 'LP0708',\n level: 'error',\n title: `The preview request returned ${String(preview.status)}, not a page`,\n detail:\n 'Nothing below this can be judged: a status outside 2xx means the body is an ' +\n 'error page or empty.',\n remedy: 'Check the URL.',\n };\n }\n if (preview.body.trim() === '') {\n return {\n code: 'LP0708',\n level: 'error',\n title: `The preview request returned ${String(preview.status)} with an empty body`,\n detail:\n 'A 2xx status with nothing in it — 204 and 205 are defined that way — is not a page, ' +\n 'so its missing runtime and missing bindings say nothing.',\n remedy: 'Point the audit at a route that renders a document.',\n };\n }\n const contentType = preview.headers['content-type'];\n if (contentType !== undefined && !contentType.toLowerCase().includes('html')) {\n return {\n code: 'LP0708',\n level: 'error',\n title: `The preview request returned ${contentType}, not HTML`,\n detail:\n 'The audit reads a rendered page. A non-HTML response has no runtime and no ' +\n 'bindings by definition, so reporting their absence would say nothing.',\n remedy: 'Point the audit at a page route rather than an API or an asset.',\n };\n }\n return undefined;\n}\n\nfunction framingFindings(\n preview: DoctorResponse,\n runtimeInPreview: boolean,\n context: DoctorContext,\n): DoctorFinding[] {\n const findings: DoctorFinding[] = [];\n const page = tryUrl(context.url);\n const admin = tryUrl(context.adminOrigin);\n const sameOrigin = admin !== undefined && page?.origin === admin.origin;\n const frameAncestors = frameAncestorsOf(preview.headers['content-security-policy']);\n if (runtimeInPreview && frameAncestors === undefined) {\n findings.push({\n code: 'LP0702',\n level: 'warning',\n title: 'Preview response declares no frame-ancestors',\n detail:\n 'The runtime is injected but the response carries no frame-ancestors directive, ' +\n 'so nothing states which origins may embed this page.',\n remedy:\n 'Let the adapter manage CSP, or set frame-ancestors yourself to the admin origin. ' +\n 'Without it you are relying on the absence of a policy.',\n });\n } else if (frameAncestors !== undefined && context.adminOrigin !== undefined) {\n const admitted =\n admin !== undefined &&\n page !== undefined &&\n frameAncestorsAdmits(frameAncestors, admin, page);\n if (!admitted) {\n findings.push({\n code: 'LP0702',\n level: 'error',\n title: 'frame-ancestors does not admit the admin origin',\n detail:\n admin === undefined\n ? `\"${context.adminOrigin}\" is not an absolute URL, so no source expression can admit it.`\n : `Served: \"${frameAncestors}\". Expected it to allow ${admin.origin}.`,\n remedy:\n 'Add the admin origin to allowedOrigins so the adapter merges it into ' +\n 'frame-ancestors, or add it to your own policy.',\n });\n }\n }\n const frameOptions = preview.headers['x-frame-options'];\n // A response with a `frame-ancestors` directive is framed by that directive alone: the HTML\n // Standard skips the header for it (measured in Chromium, Firefox and WebKit). The header\n // decides only where the response declares none; the directive's own verdict is LP0702.\n const blocks =\n frameOptions !== undefined &&\n frameAncestors === undefined &&\n (/deny/iu.test(frameOptions) || (/sameorigin/iu.test(frameOptions) && !sameOrigin));\n if (blocks) {\n // `SAMEORIGIN` refuses a frame from *another* origin, so it is a verdict\n // about this preview only once the admin origin is known. Without --admin\n // the audit says what it saw and names the condition instead of asserting a\n // block it cannot have established: where the admin and the site share an\n // origin, the preview runs. `DENY` needs no origin to be a verdict.\n const judged = admin !== undefined || /deny/iu.test(frameOptions);\n findings.push(\n judged\n ? {\n code: 'LP0703',\n level: 'error',\n title: `X-Frame-Options: ${frameOptions} blocks the preview iframe`,\n detail:\n 'This header is older than CSP. A browser applies it to a response that has no ' +\n 'frame-ancestors directive, and this response has no frame-ancestors.',\n remedy:\n 'Remove X-Frame-Options for preview responses, or serve a frame-ancestors ' +\n 'directive that admits the admin: the header is then ignored. It is usually ' +\n 'set by a proxy or a security middleware rather than by the app.',\n }\n : {\n code: 'LP0703',\n level: 'warning',\n title: `X-Frame-Options: ${frameOptions} blocks every frame from another origin`,\n detail:\n 'This header is older than CSP. A browser applies it to a response that has no ' +\n 'frame-ancestors directive, and this response has no frame-ancestors. SAMEORIGIN ' +\n 'refuses a frame from another origin only, and no admin origin was given: whether ' +\n 'it blocks this preview depends on where the admin is served, which this probe ' +\n 'cannot see.',\n remedy:\n 'Re-run with --admin <origin> to have it judged. If the admin is on another ' +\n 'origin, remove X-Frame-Options for preview responses or serve a frame-ancestors ' +\n 'directive that admits it; a proxy or a security middleware usually sets the header.',\n },\n );\n }\n return findings;\n}\n\n/** The gate as the served script configures it; `threshold` is undefined when the gate is off. */\ninterface ServedGate {\n readonly threshold: number | undefined;\n readonly configured: boolean;\n}\n\n/**\n * Read from the served configuration, because an adapter now forwards both\n * options: judged against the default, a page that raised its threshold\n * would be told to raise it. A page whose configuration cannot be read\n * (loader mode, a client the page starts without a configuration element) is\n * judged against the default.\n */\nfunction servedGate(body: string): ServedGate {\n const config = readServedConfig(body);\n if (config?.value('disableVisibilityGate') === true) {\n return { threshold: undefined, configured: true };\n }\n const threshold = config?.value('visibilityGateThreshold');\n return typeof threshold === 'number'\n ? { threshold, configured: true }\n : { threshold: DEFAULT_VISIBILITY_GATE_THRESHOLD, configured: false };\n}\n\nfunction bindingFindings(\n previewMarkup: string,\n publicMarkup: string,\n runtime: boolean,\n gate: ServedGate,\n): DoctorFinding[] {\n const findings: DoctorFinding[] = [];\n const preview = markupAttributes(previewMarkup);\n const previewBindings = named(preview, 'data-payload-field').length;\n const publicBindings = named(markupAttributes(publicMarkup), 'data-payload-field').length;\n const owners = named(preview, 'data-payload-owner').length;\n // A boundary is rendered by the server from the form state, so an edit of a\n // field it depends on arrives without one field binding; an empty id names\n // no registry entry, and no strategy renders it.\n const boundaries = named(preview, 'data-payload-fragment').filter(\n (boundary) => boundary.value.length > 0,\n ).length;\n if (publicBindings > 0) {\n findings.push({\n code: 'LP0704',\n level: 'warning',\n title: `${String(publicBindings)} binding attribute(s) served to anonymous visitors`,\n detail:\n 'A request with no preview intent received data-payload-field attributes. These ' +\n 'disclose the CMS field taxonomy, and any CSS keyed on them changes public layout ' +\n 'the moment emission is gated.',\n remedy:\n 'Gate binding emission on an authorized preview context — see ' +\n 'createPreviewBindings(), whose suppressed form emits nothing at all.',\n });\n }\n const stranded = modifiersWithoutField(preview);\n if (stranded.length > 0) {\n findings.push({\n code: 'LP0711',\n level: 'warning',\n title: `${String(stranded.length)} element(s) carry ${stranded[0] ?? ''} or the like without a field`,\n detail:\n 'The runtime reads these attributes only on a bound element, so they do nothing here, ' +\n 'and it says nothing about them either.',\n remedy: 'Add data-payload-field to the element, or remove them.',\n });\n }\n if (gate.threshold !== undefined && previewBindings > gate.threshold) {\n findings.push({\n code: 'LP0705',\n level: 'warning',\n title: `${String(previewBindings)} bindings exceed the ${gate.configured ? 'configured' : 'default'} visibility gate`,\n detail:\n `Above visibilityGateThreshold (${String(gate.threshold)}) the ` +\n 'scheduler stops writing offscreen elements and buffers them until they scroll into ' +\n 'view. On a page nobody scrolls, that is never.',\n remedy:\n 'Raise visibilityGateThreshold, or set disableVisibilityGate, on the adapter, ' +\n 'LivePreviewClient or generateInlineScript() if the whole page must stay live, or ' +\n 'confirm that deferring below the fold is acceptable here.',\n });\n }\n if (runtime && previewBindings === 0 && boundaries === 0) {\n findings.push({\n code: 'LP0707',\n level: 'error',\n title: 'Preview response carries no bindings',\n detail:\n 'The runtime is present but there is not one data-payload-field element for it to ' +\n 'write into and not one data-payload-fragment boundary for a server to render, so ' +\n 'no edit can ever be visible on this page.',\n remedy:\n 'Add binding attributes or fragment boundaries to the markup. pll-codegen --inventory ' +\n 'lists every field the schema makes addressable.',\n });\n }\n const unowned = unownedBindingsBeforeFirstOwner(preview);\n if (owners > 0 && unowned > 0) {\n findings.push({\n code: 'LP0706',\n level: 'warning',\n title: `${String(unowned)} binding(s) outside every owner marker`,\n detail:\n `The page declares ${String(owners)} owner marker(s), so owner scoping is in use. ` +\n 'Bindings that belong to no owner receive nothing once scopeBindingsByOwner is on — ' +\n 'they fail closed.',\n remedy:\n 'Give every binding an owning ancestor, or leave scopeBindingsByOwner off until they ' +\n 'all have one. Counted before the first owner marker only, so the real number may be ' +\n 'higher.',\n });\n }\n return findings;\n}\n\n/** Audit a probed deployment. */\nexport function analyzeProbe(\n probe: DoctorProbe,\n context: DoctorContext & {\n readonly v2?: boolean;\n /** Whether the preview probe carried caller-supplied headers (`--header`). */\n readonly credentials?: boolean;\n },\n): DoctorReport {\n const { publicResponse: pub, previewResponse: preview } = probe;\n const notAPage = describeNonPage(preview);\n if (notAPage !== undefined) {\n return { url: context.url, findings: [notAPage], errors: 1, warnings: 0 };\n }\n const runtimeInPreview = preview.body.includes(RUNTIME_MARKER);\n const findings: DoctorFinding[] = [];\n if (!runtimeInPreview) {\n findings.push(\n context.credentials === true\n ? {\n code: 'LP0701',\n level: 'warning',\n title: 'No inline runtime in the preview response',\n detail:\n `The preview request returned ${String(preview.status)} without the inline ` +\n 'runtime, although it carried credentials (--header, or ?previewToken= in the URL). Three readings: ' +\n 'those credentials were not accepted — expired, issued for another page, or not ' +\n 'what authorizePreview reads — an adapter that did not recognise the request as a ' +\n 'preview, or a consumer that starts LivePreviewClient itself.',\n remedy:\n 'Check that the credentials are current and are the ones authorizePreview reads; ' +\n 'if you use an adapter, check its inject mode and previewSignals. If you start the ' +\n 'client yourself, this line is expected.',\n }\n : {\n code: 'LP0701',\n level: 'warning',\n title: 'No inline runtime in the preview response',\n detail:\n `The preview request returned ${String(preview.status)} without the inline ` +\n 'runtime. Three readings, and this audit cannot tell them apart from the response ' +\n 'alone: a preview behind authorizePreview — the strict 2.0 default — which answers ' +\n 'a request without credentials exactly like this, an adapter that did not recognise ' +\n 'the request as a preview, or a consumer that starts LivePreviewClient itself.',\n remedy:\n \"To audit a gated preview, send what an editor's browser sends: \" +\n \"--header 'Cookie: payload-token=…' for a Payload session; for a signed token, \" +\n 'the URL with ?previewToken=… (the default transport, dropped from the visitor ' +\n \"request), or --header 'x-preview-token: …' where the strategy sets transport: \" +\n \"{ kind: 'header' }. Otherwise check the adapter's \" +\n 'inject mode and previewSignals; if you start the client yourself, this line is ' +\n 'expected.',\n },\n );\n }\n findings.push(...framingFindings(preview, runtimeInPreview, context));\n findings.push(\n ...bindingFindings(\n visibleMarkup(preview.body),\n visibleMarkup(pub.body),\n runtimeInPreview,\n servedGate(preview.body),\n ),\n );\n if (pub.body.includes(RUNTIME_MARKER)) {\n // A page that starts its own client serves only the JSON element, which spells the name too.\n const inline = pub.body.includes(`var ${RUNTIME_MARKER}=`);\n findings.push({\n code: DIAGNOSTIC_CODES.RuntimeOnPublicPage,\n level: 'info',\n title: inline\n ? 'Runtime is also served to anonymous visitors'\n : 'Preview configuration is also served to anonymous visitors',\n detail: inline\n ? 'A request with no preview intent received the inline runtime. That is correct for ' +\n \"inject: 'always' and unintended for 'preview-only'.\"\n : 'A request with no preview intent received the configuration element a page writes ' +\n 'for a client it starts itself. That is correct if the page starts the client for ' +\n 'every request, and unintended if it writes the element only for a preview.',\n remedy: '',\n });\n }\n if (context.v2 === true) findings.push(...analyzeV2Readiness(probe));\n findings.sort((a, b) => LEVEL_ORDER[a.level] - LEVEL_ORDER[b.level]);\n return {\n url: context.url,\n findings,\n errors: findings.filter((finding) => finding.level === 'error').length,\n warnings: findings.filter((finding) => finding.level === 'warning').length,\n };\n}\n","/** Terminal rendering of a doctor report. */\nimport type { DoctorReport } from './types';\n\nconst LEVEL_LABEL = { error: 'ERROR', warning: 'WARN ', info: 'INFO ' } as const;\n\n/** @internal */\nexport function formatReport(report: DoctorReport): string {\n const lines: string[] = [`pll doctor — ${report.url}`, ''];\n if (report.findings.length === 0) {\n lines.push('No findings. The preview response carries the runtime, a frame-ancestors');\n lines.push('policy, and bindings; the public response carries none of them.', '');\n return lines.join('\\n');\n }\n for (const finding of report.findings) {\n lines.push(`${LEVEL_LABEL[finding.level]} ${finding.code} ${finding.title}`);\n lines.push(` ${finding.detail}`);\n if (finding.remedy !== '') lines.push(` → ${finding.remedy}`);\n lines.push('');\n }\n lines.push(`${String(report.errors)} error(s), ${String(report.warnings)} warning(s).`, '');\n return lines.join('\\n');\n}\n","/**\n * Fetching for `pll doctor`: the two probes, and the default fetch that talks\n * to a real server. Not in the barrel so it stays in the coverage report.\n */\nimport { analyzeProbe } from './analyze';\nimport type { DoctorReport, DoctorResponse } from './types';\n\n/** Injectable so tests can drive the audit without a server. */\nexport type DoctorFetch = (\n url: string,\n init: { readonly headers: Readonly<Record<string, string>> },\n) => Promise<DoctorResponse>;\n\nexport interface RunDoctorOptions {\n readonly url: string;\n /** Admin origin the preview is meant to be embedded from, when known. */\n readonly adminOrigin?: string | undefined;\n /** Defaults to a fetch built on `globalThis.fetch`. */\n readonly fetchImpl?: DoctorFetch | undefined;\n /** Also check the served page against the 2.0 readiness table. */\n readonly v2?: boolean;\n /**\n * Headers sent with the preview probe only, never with the visitor probe: the\n * credentials a preview behind `authorizePreview` needs, such as a Payload\n * session `Cookie`, or an `x-preview-token` where the token strategy reads a\n * header (a token in the URL is passed in `url` instead). Without them a gated page answers\n * the audit the way it answers any stranger. Their values never reach the report.\n */\n readonly previewHeaders?: Readonly<Record<string, string>> | undefined;\n /**\n * The query parameters the deployment reads as preview intent, when its\n * adapter sets `previewQueryParams`; the same list, since that option replaces\n * the default `preview`, `draft` and `livePreview`. The first one is what the\n * preview probe appends as `=true`; none of them reaches the visitor probe.\n */\n readonly previewQueryParams?: readonly string[] | undefined;\n /**\n * The query parameter the `signed-token` strategy reads, when its transport\n * names one other than `previewToken`. Treated like `previewToken`: dropped\n * from the visitor probe, counted as the credential, its value never shown.\n */\n readonly tokenQueryParam?: string | undefined;\n}\n\n/**\n * The query parameters an adapter reads as preview intent by default; the same\n * list as `DEFAULT_QUERY_PARAMS` in adapters/shared/preview-request.ts, which\n * `previewQueryParams` replaces rather than extends.\n */\nconst DEFAULT_INTENT_PARAMS: readonly string[] = ['preview', 'draft', 'livePreview'];\n\nfunction isIntentValue(value: string): boolean {\n return value === 'true' || value === '1';\n}\n\n/** `key=value` and `#fragment` split off a URL, with the query kept as the caller spelled it. */\nfunction splitUrl(url: string): {\n readonly base: string;\n readonly parts: string[];\n readonly hash: string;\n} {\n const hashAt = url.indexOf('#');\n const hash = hashAt === -1 ? '' : url.slice(hashAt);\n const beforeHash = hashAt === -1 ? url : url.slice(0, hashAt);\n const queryAt = beforeHash.indexOf('?');\n if (queryAt === -1) return { base: beforeHash, parts: [], hash };\n return {\n base: beforeHash.slice(0, queryAt),\n parts: beforeHash.slice(queryAt + 1).split('&'),\n hash,\n };\n}\n\nfunction decodePart(part: string): readonly [string, string] {\n const at = part.indexOf('=');\n const decode = (text: string): string => {\n try {\n return decodeURIComponent(text.replace(/\\+/gu, ' '));\n } catch {\n return text;\n }\n };\n return at === -1 ? [decode(part), ''] : [decode(part.slice(0, at)), decode(part.slice(at + 1))];\n}\n\n/**\n * The query parameter the `signed-token` strategy reads by default. A token\n * audit puts the token in the URL, and it belongs to the preview request only:\n * in the visitor request it would authorize the \"anonymous\" probe and, behind a\n * replay store, spend the token before the preview request arrives.\n */\nconst TOKEN_PARAM = 'previewToken';\n\n/**\n * The URL as the audit prints it: the token's value replaced by `…`. Header\n * values never reach the report, and a token passed in the URL is the same\n * credential, so the report, `--json` and the failure line all show this form.\n * @internal\n */\nexport function redactToken(url: string, tokenParam: string = TOKEN_PARAM): string {\n const { base, parts, hash } = splitUrl(url);\n const shown = parts.map((part) => {\n const at = part.indexOf('=');\n return at === -1 || decodePart(part)[0] !== tokenParam ? part : `${part.slice(0, at)}=…`;\n });\n return shown.some((part, index) => part !== parts[index])\n ? `${base}?${shown.join('&')}${hash}`\n : url;\n}\n\n/** Whether the URL carries a non-empty token for the preview request. */\nfunction carriesToken(url: string, tokenParam: string): boolean {\n return splitUrl(url).parts.some((part) => {\n const [key, value] = decodePart(part);\n return key === tokenParam && value !== '';\n });\n}\n\n/**\n * The page a visitor requests: the intent parameters removed, the rest of the\n * query byte for byte as given. A query is never re-serialised — `URLSearchParams`\n * would turn `%20` into `+` and add `=` to a bare key, and a page whose query is\n * signed by an edge token would answer a different request than a visitor makes.\n */\nfunction visitorUrl(url: string, params: readonly string[], tokenParam: string): string {\n const { base, parts, hash } = splitUrl(url);\n const dropped = new Set([...params, tokenParam]);\n const kept = parts.filter((part) => !dropped.has(decodePart(part)[0]));\n if (kept.length === parts.length) return url;\n return `${base}${kept.length === 0 ? '' : `?${kept.join('&')}`}${hash}`;\n}\n\n/**\n * The page the admin's iframe loads. A 2.0 adapter counts only the query as\n * intent (`previewSignals: ['query']`), and `buildLivePreviewUrl` writes\n * `preview=true`, so the first intent parameter is appended as `=true` unless the\n * URL already names one with a value that counts.\n */\nfunction previewUrl(url: string, params: readonly string[]): string {\n const { base, parts, hash } = splitUrl(url);\n const carries = parts.some((part) => {\n const [key, value] = decodePart(part);\n return params.includes(key) && isIntentValue(value);\n });\n if (carries) return url;\n const query = parts.join('&');\n const joiner = query === '' ? '?' : query.endsWith('&') ? '' : '&';\n const intent = `${encodeURIComponent(params[0] ?? 'preview')}=true`;\n return `${base}${parts.length === 0 ? '?' : `?${query}${joiner}`}${intent}${hash}`;\n}\n\nexport const DEFAULT_TIMEOUT_MS = 15_000;\nexport const MAX_BODY_BYTES = 10 * 1024 * 1024;\n\nexport interface DefaultFetchOptions {\n readonly fetchFn?: typeof fetch;\n readonly timeoutMs?: number;\n readonly maxBodyBytes?: number;\n}\n\n/** Header names lowercased; every check downstream reads a lowercase key. @internal */\nexport function lowercaseHeaders(headers: Headers): Record<string, string> {\n const out: Record<string, string> = {};\n headers.forEach((value, key) => {\n out[key.toLowerCase()] = value;\n });\n return out;\n}\n\n/** The error's message plus its cause's, which is where undici puts `self-signed certificate` and friends. */\nexport function describeFailure(error: unknown): string {\n if (!(error instanceof Error)) return String(error);\n const cause = error.cause;\n if (cause instanceof Error && cause.message !== '' && !error.message.includes(cause.message)) {\n return `${error.message} (${cause.message})`;\n }\n return error.message;\n}\n\nasync function readBody(response: Response, limit: number): Promise<string> {\n const reader = response.body?.getReader();\n if (reader === undefined) return response.text();\n const decoder = new TextDecoder();\n let received = 0;\n let text = '';\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n received += value.byteLength;\n if (received > limit) {\n await reader.cancel();\n throw new Error(\n `the response body exceeds ${String(limit)} bytes, which is not a page the audit can judge`,\n );\n }\n text += decoder.decode(value, { stream: true });\n }\n return text + decoder.decode();\n}\n\n/** Redirects are reported, not followed; a hanging origin is abandoned; a runaway body is refused. */\nexport function createDefaultFetch(options: DefaultFetchOptions = {}): DoctorFetch {\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const limit = options.maxBodyBytes ?? MAX_BODY_BYTES;\n return async (url, init) => {\n const fetchFn = options.fetchFn ?? globalThis.fetch;\n let response: Response;\n try {\n response = await fetchFn(url, {\n headers: init.headers,\n redirect: 'manual',\n signal: AbortSignal.timeout(timeoutMs),\n });\n } catch (error) {\n if (error instanceof Error && error.name === 'TimeoutError') {\n throw new Error(`no response within ${String(timeoutMs / 1000)} s`, { cause: error });\n }\n throw error;\n }\n return {\n status: response.status,\n headers: lowercaseHeaders(response.headers),\n body: await readBody(response, limit),\n };\n };\n}\n\n/** The referer the admin's iframe would send: the origin (or the given admin path) with one slash. */\nexport function previewReferer(adminOrigin: string): string {\n try {\n return new URL(adminOrigin).href;\n } catch {\n return `${adminOrigin.replace(/\\/+$/u, '')}/`;\n }\n}\n\n/**\n * The caller's headers without the ones the probe sets itself. Header names are\n * case-insensitive, and `fetch` combines two spellings of one name into one\n * comma-joined value, so a caller's `sec-fetch-dest` would otherwise turn the\n * probe's `iframe` into `document, iframe`. The probe's own headers win.\n */\nfunction callerHeaders(\n headers: Readonly<Record<string, string>> | undefined,\n own: Readonly<Record<string, string>>,\n): Record<string, string> {\n const taken = new Set(Object.keys(own).map((name) => name.toLowerCase()));\n return Object.fromEntries(\n Object.entries(headers ?? {}).filter(([name]) => !taken.has(name.toLowerCase())),\n );\n}\n\n/** Fetch the URL twice — as a visitor and as the admin's iframe — and audit the difference. */\nexport async function runDoctor(options: RunDoctorOptions): Promise<DoctorReport> {\n const fetchImpl = options.fetchImpl ?? createDefaultFetch();\n const params =\n options.previewQueryParams === undefined || options.previewQueryParams.length === 0\n ? DEFAULT_INTENT_PARAMS\n : options.previewQueryParams;\n const tokenParam = options.tokenQueryParam ?? TOKEN_PARAM;\n // No referer, no intent parameter and no credentials on the visitor probe:\n // each of them is a preview signal or a way past one.\n const publicResponse = await fetchImpl(visitorUrl(options.url, params, tokenParam), {\n headers: {\n Accept: 'text/html',\n 'Sec-Fetch-Dest': 'document',\n 'Sec-Fetch-Mode': 'navigate',\n },\n });\n const probeHeaders: Record<string, string> = {\n Accept: 'text/html',\n 'Sec-Fetch-Dest': 'iframe',\n 'Sec-Fetch-Mode': 'navigate',\n ...(options.adminOrigin === undefined ? {} : { Referer: previewReferer(options.adminOrigin) }),\n };\n const sent = callerHeaders(options.previewHeaders, probeHeaders);\n const previewResponse = await fetchImpl(previewUrl(options.url, params), {\n headers: { ...sent, ...probeHeaders },\n });\n return analyzeProbe(\n { publicResponse, previewResponse },\n {\n url: redactToken(options.url, tokenParam),\n adminOrigin: options.adminOrigin,\n ...(options.v2 === true ? { v2: true } : {}),\n ...(Object.keys(sent).length > 0 || carriesToken(options.url, tokenParam)\n ? { credentials: true }\n : {}),\n },\n );\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
*
|
|
10
10
|
* The session is per instance (ADR 0002): two hooks on one page have two
|
|
11
11
|
* mergers, two revisions and two snapshots, so a request or an error in one
|
|
12
|
-
* never reaches the other.
|
|
13
|
-
*
|
|
12
|
+
* never reaches the other. A session given a `document` merges only the
|
|
13
|
+
* messages for that document; without one it follows the document the admin
|
|
14
|
+
* sends, whichever document it started from.
|
|
14
15
|
*/
|
|
15
16
|
/**
|
|
16
17
|
* `idle` — connected, nothing received yet. `live` — the newest update was
|
|
@@ -34,6 +35,18 @@ interface DocumentSnapshot<T> {
|
|
|
34
35
|
*/
|
|
35
36
|
readonly revision: number;
|
|
36
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* The document a session previews: a collection document by its id, or a global by its slug.
|
|
40
|
+
* `locale` narrows it to the updates the admin sends for that locale.
|
|
41
|
+
*/
|
|
42
|
+
type PreviewDocument = {
|
|
43
|
+
readonly collection: string;
|
|
44
|
+
readonly id: string | number;
|
|
45
|
+
readonly locale?: string;
|
|
46
|
+
} | {
|
|
47
|
+
readonly global: string;
|
|
48
|
+
readonly locale?: string;
|
|
49
|
+
};
|
|
37
50
|
interface DocumentSessionOptions {
|
|
38
51
|
/** Payload server origin the update is re-fetched from. */
|
|
39
52
|
readonly serverURL: string;
|
|
@@ -41,6 +54,12 @@ interface DocumentSessionOptions {
|
|
|
41
54
|
readonly apiRoute?: string;
|
|
42
55
|
/** Population depth for the merge. Default `1`, as Payload's own hook. */
|
|
43
56
|
readonly depth?: number;
|
|
57
|
+
/**
|
|
58
|
+
* Milliseconds a merge may wait for the server; after that the update is `unavailable`
|
|
59
|
+
* and `data` stays the last good document. Off by default: a merge waits as long as the
|
|
60
|
+
* server takes.
|
|
61
|
+
*/
|
|
62
|
+
readonly timeoutMs?: number;
|
|
44
63
|
/**
|
|
45
64
|
* Admin origins allowed to post updates. Without one the session falls back
|
|
46
65
|
* to the localhost matcher in development and trusts nothing in production.
|
|
@@ -52,6 +71,12 @@ interface DocumentSessionOptions {
|
|
|
52
71
|
readonly enableReferrerDetection?: boolean;
|
|
53
72
|
/** Match `localhost` origins in development. Default `true`. */
|
|
54
73
|
readonly enableLocalhostMatching?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* The document this session previews. An update for another document is ignored, and so is
|
|
76
|
+
* one that does not say which document it is for: Payload 2 sends no collection or global
|
|
77
|
+
* slug, and a create screen has no id yet. Without it the session merges every update.
|
|
78
|
+
*/
|
|
79
|
+
readonly document?: PreviewDocument;
|
|
55
80
|
/** Injected for tests. */
|
|
56
81
|
readonly fetchFn?: typeof fetch;
|
|
57
82
|
/** Window to listen on. Default the global one. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { e as PreviewAuthorizationRequest, f as AuthorizedPreviewScope, A as AuthorizedPreviewContext, P as PreviewAdapterOptions } from './options-
|
|
1
|
+
import { e as PreviewAuthorizationRequest, f as AuthorizedPreviewScope, A as AuthorizedPreviewContext, P as PreviewAdapterOptions } from './options-DTzhFVJ4.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* The `payload-session` strategy: forward exactly one named cookie to
|