clone

a deno framework for local-first web apps that open instantly, work offline and sync when users choose

feat/prf-provision-custody
lofi / docs / recipes / scope-extension.md
scope-extension.md 3.5K rendered
1# Cross-origin app-window scope23Use this recipe only when one product owns both an installed PWA and a separate HTTPS origin that4should remain inside the installed app window, such as product-owned help or a regional surface.5[`scope_extensions`](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Manifest/Reference/scope_extensions)6is experimental. It changes window presentation after both origins opt in; it does not join their7security, storage, or service-worker boundaries.89The generated starter stays single-origin and omits this capability.1011## Declare the extending origin1213Add an exact origin to the primary app's `manifest.webmanifest`:1415```json16{17 "id": "https://app.example.com/notes",18 "scope_extensions": [19 { "type": "origin", "origin": "https://help.example.com" }20 ]21}22```2324The origin must be HTTPS and contain no credentials, path, query, or fragment. Lofi's manifest25validation rejects malformed, duplicate, and invented entries.2627## Publish reciprocal ownership2829On `https://help.example.com`, publish this JSON at the exact path30`/.well-known/web-app-origin-association`:3132```json33{34 "https://app.example.com/notes": {35 "scope": "/notes/"36 }37}38```3940The key is the exact absolute manifest ID, not merely the app origin. The value limits which paths41on the extending origin receive app-window presentation. Generate and verify it at deploy time:4243```ts44import {45 createScopeExtension,46 createWebAppOriginAssociation,47 verifyWebAppOriginAssociation,48} from "@nzip/lofi/recipes/scope-extension";4950const declaration = createScopeExtension("https://help.example.com");51const expected = {52 manifestId: "https://app.example.com/notes",53 scope: "/notes/",54};55const association = createWebAppOriginAssociation(expected);5657// After fetching the deployed JSON in trusted deployment tooling:58if (!verifyWebAppOriginAssociation(deployedJson, expected)) {59 throw new Error("reciprocal scope association is missing");60}61```6263Do not fetch the association from application runtime code. A true verification result confirms64deployment configuration only; it is not authentication or authorization.6566## Deploy, test, and revoke6768Deploy in this order:69701. Publish the association on the extending origin with JSON content type, HTTPS, and no redirect.712. Fetch that exact well-known URL without credentials and verify the exact manifest ID and scope.723. Add `scope_extensions` to the primary manifest, then allow browser manifest caches to refresh.734. Test a fresh install and an existing install on supported and unsupported browsers.7475During a partial rollout, unsupported browsers, a missing association, or a failed ownership check,76links continue as ordinary out-of-scope navigation and may open browser UI. Build the feature so77that external navigation is useful. For revocation, first remove the primary manifest declaration78and let installed clients refresh; then remove the remote association.7980The primary service worker cannot control or cache the extending origin. Offline navigation there81requires that origin's own offline design. The origins retain separate cookies, storage, passkey82relying-party behavior, Jazz configuration, CSP, CORS, authentication, and authorization. Never use83scope extension as a way to share any of them or to bypass an origin boundary.8485Test exact and mismatched manifest IDs, invalid and duplicate origins, path-limited navigation,86ordinary external fallback, partial rollout, revocation, offline navigation, manifest refresh, and87passkey/storage/Jazz isolation before shipping.