@start.dev/container 0.0.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.
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Resolvers for the worker + Service-Worker assets that @start.dev/container SHIPS in its
3
+ * built `dist/`. These let an external consumer (start.dev) wire
4
+ * `WebContainer.boot({ runtime })` WITHOUT the monorepo, a runtime bundler, or
5
+ * hand-copied worker files: the URLs point at the assets that live inside the
6
+ * installed package.
7
+ *
8
+ * Mechanism: each resolver returns `new URL('./workers/<file>', import.meta.url)`.
9
+ * After the build this module lives at `dist/runtime-assets.js`, so `import.meta.url`
10
+ * is the installed package location and the workers resolve next to it. Every modern
11
+ * bundler (Vite, webpack 5, Rollup, esbuild) understands the
12
+ * `new URL('…', import.meta.url)` pattern and will EMIT these as first-class assets,
13
+ * so the consumer's build pipeline serves them automatically.
14
+ *
15
+ * NOTE — this file is only meaningful in the BUILT package. In the monorepo source
16
+ * there is no `dist/workers/*`, so the in-repo acceptance harness keeps building +
17
+ * serving the worker URLs itself (it does not import this module). External
18
+ * consumers import it from `@start.dev/container/runtime-assets`.
19
+ */
20
+ /** URL of the shipped, pre-bundled real Vite-serve worker (ESM module worker). */
21
+ export declare function viteWorkerUrl(): string;
22
+ /** URL of the shipped, pre-bundled real Node streaming-process worker (ESM module worker). */
23
+ export declare function processWorkerUrl(): string;
24
+ /** URL of the shipped, pre-bundled real shell worker (@wc/shell / just-bash; ESM module worker). */
25
+ export declare function shellWorkerUrl(): string;
26
+ /**
27
+ * URL of the shipped substrate Service Worker script. The consumer must serve this
28
+ * UNDER its preview base scope with the runtime COOP/COEP headers (it cannot be a
29
+ * cross-origin URL — a Service Worker can only control same-origin scopes), so most
30
+ * hosts copy it into their served `previewBase` directory rather than importing this
31
+ * URL directly. Returned here for completeness / programmatic copying.
32
+ */
33
+ export declare function serviceWorkerUrl(): string;
34
+ /**
35
+ * Convenience: the worker-URL subset of `RuntimeWiring` filled from the shipped
36
+ * assets. The consumer still supplies the host-specific fields it owns
37
+ * (`serviceWorkerUrl` served under its scope, `previewBase`, the WASM URLs, and the
38
+ * project `dependencyClosure`). Spread this into `boot({ runtime: { ... } })`.
39
+ */
40
+ export declare function shippedWorkerUrls(): {
41
+ viteWorkerUrl: string;
42
+ processWorkerUrl: string;
43
+ shellWorkerUrl: string;
44
+ };
@@ -0,0 +1,27 @@
1
+ // src/runtime-assets.ts
2
+ function viteWorkerUrl() {
3
+ return new URL("./workers/vite-serve.worker.js", import.meta.url).href;
4
+ }
5
+ function processWorkerUrl() {
6
+ return new URL("./workers/streaming-process.worker.js", import.meta.url).href;
7
+ }
8
+ function shellWorkerUrl() {
9
+ return new URL("./workers/shell-host.worker.js", import.meta.url).href;
10
+ }
11
+ function serviceWorkerUrl() {
12
+ return new URL("./workers/runtime-sw.js", import.meta.url).href;
13
+ }
14
+ function shippedWorkerUrls() {
15
+ return {
16
+ viteWorkerUrl: viteWorkerUrl(),
17
+ processWorkerUrl: processWorkerUrl(),
18
+ shellWorkerUrl: shellWorkerUrl()
19
+ };
20
+ }
21
+ export {
22
+ viteWorkerUrl,
23
+ shippedWorkerUrls,
24
+ shellWorkerUrl,
25
+ serviceWorkerUrl,
26
+ processWorkerUrl
27
+ };
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Public TypeScript surface for `@start.dev/container`, mirroring StackBlitz's official
3
+ * `@webcontainer/api` so the start.dev adapter (and any consumer) can build against
4
+ * the canonical shape and drop us in. Names + signatures match `@webcontainer/api`
5
+ * 1.x; members our runtime does not yet cover are documented as honest stubs in
6
+ * `WebContainer` (they throw "not yet implemented"), never faked.
7
+ */
8
+ import type { FsWatcher, WatchListener, WatchOptions } from './_wc-vendored-types';
9
+ export type { FsWatcher, WatchListener, WatchOptions } from './_wc-vendored-types';
10
+ /** A regular file node: `{ file: { contents } }`. */
11
+ export interface FileNode {
12
+ file: {
13
+ /** UTF-8 string or raw bytes. Honors the runtime's 1 MiB/utf8 limits. */
14
+ contents: string | Uint8Array;
15
+ };
16
+ }
17
+ /**
18
+ * A symlink node: `{ file: { symlink } }`. Accepted by the type for drop-in
19
+ * compatibility; the current MemoryFS mount path materializes regular files +
20
+ * directories only, so a symlink node throws an honest "not yet implemented".
21
+ */
22
+ export interface SymlinkNode {
23
+ file: {
24
+ symlink: string;
25
+ };
26
+ }
27
+ /** A directory node: `{ directory: { ...nested tree } }`. */
28
+ export interface DirectoryNode {
29
+ directory: FileSystemTree;
30
+ }
31
+ /** A nested filesystem tree: directory names → file/dir nodes. */
32
+ export interface FileSystemTree {
33
+ [name: string]: FileNode | SymlinkNode | DirectoryNode;
34
+ }
35
+ /**
36
+ * Boot options. `@webcontainer/api` exposes `coep`, `workdirName`, and
37
+ * `forwardPreviewErrors`; we mirror those and add the browser-runtime wiring our
38
+ * in-page orchestrator needs (the bundled worker + WASM URLs + the served
39
+ * dependency closure), which `@webcontainer/api` hides behind its hosted runtime.
40
+ */
41
+ export interface BootOptions {
42
+ /**
43
+ * COEP policy hint, mirroring @webcontainer/api. Informational here — the
44
+ * cross-origin isolation headers are set by whoever serves the host page; the
45
+ * facade asserts `crossOriginIsolated` at boot.
46
+ */
47
+ coep?: 'require-corp' | 'credentialless' | 'none';
48
+ /** Name of the working directory (default 'proj'); `workdir` becomes `/<name>`. */
49
+ workdirName?: string;
50
+ /** Reserved for parity with @webcontainer/api; currently informational. */
51
+ forwardPreviewErrors?: boolean | 'exceptions-only';
52
+ /**
53
+ * Runtime wiring (browser-specific; not in @webcontainer/api, whose runtime is
54
+ * hosted). The host page supplies the bundled Vite-serve worker URL, the WASM
55
+ * override URLs, and the pre-mounted dependency closure so the facade stays
56
+ * browser-pure (no Node `fs`). The start.dev adapter fills these from its
57
+ * served artifact.
58
+ */
59
+ runtime?: RuntimeWiring;
60
+ }
61
+ /**
62
+ * The browser-runtime wiring the facade needs to boot the real Vite worker. The
63
+ * host page (or the start.dev adapter) provides these; the facade does not read
64
+ * the host filesystem itself.
65
+ */
66
+ export interface RuntimeWiring {
67
+ /** URL of the bundled `vite-serve.worker.ts` (served same-origin, ESM). */
68
+ viteWorkerUrl: string;
69
+ /**
70
+ * URL of the bundled `streaming-process-host.worker.ts` (same-origin, ESM). This
71
+ * is the REAL Node process worker the command resolver uses for `node <file>`,
72
+ * `npm run <script>`, `npx <bin>`, and direct local bins — it runs the unmodified
73
+ * entry/bin on the node-compat module loader over the mounted MemoryFS and streams
74
+ * its real stdout/stderr/exit. Optional: when absent, those commands throw an
75
+ * honest "process worker not wired" error rather than faking output. (The
76
+ * dev-server path — `npm run dev` / `vite` — uses `viteWorkerUrl` instead.)
77
+ */
78
+ processWorkerUrl?: string;
79
+ /**
80
+ * URL of the bundled shell worker (same-origin, ESM) that runs the REAL @wc/shell
81
+ * (vercel-labs/just-bash) over the mounted MemoryFS. Used to back `spawn('jsh')`
82
+ * and `spawn('bash')` (jsh is StackBlitz's shell name; we substitute just-bash —
83
+ * a real AST-based bash interpreter). Optional: when absent, `jsh`/`bash` throw an
84
+ * honest "shell worker not wired" error rather than faking a prompt.
85
+ */
86
+ shellWorkerUrl?: string;
87
+ /** URL of the substrate Service Worker script (served under the preview base). */
88
+ serviceWorkerUrl: string;
89
+ /** Preview base path the SW is scoped to + Vite serves under (e.g. '/preview/'). */
90
+ previewBase: string;
91
+ /** URL of the served `esbuild.wasm` (esbuild-wasm needs `initialize({wasmURL})`). */
92
+ esbuildWasmUrl: string;
93
+ /** URL of the served `@rollup/wasm-node` `bindings_wasm_bg.wasm`. */
94
+ rollupWasmUrl: string;
95
+ /** Source of `@rollup/wasm-node/.../bindings_wasm.js` (eval'd in the worker). */
96
+ rollupBindingsSource: string;
97
+ /** Source of `@rollup/wasm-node/.../shared/parseAst.js` (eval'd in the worker). */
98
+ rollupParseAstSharedSource: string;
99
+ /**
100
+ * The pre-mounted dependency closure (vite + rollup + react + react-dom +
101
+ * @vitejs/plugin-react), keyed by VFS path. Mounted into the worker's MemoryFS
102
+ * alongside the user's project files. The host page builds this with
103
+ * `mountPackageClosure` (a Node-side op) and serves it as JSON.
104
+ */
105
+ dependencyClosure: Record<string, string>;
106
+ }
107
+ /** Options for `mount`. */
108
+ export interface MountOptions {
109
+ /** Mount the tree under this absolute VFS dir (default: `workdir`). */
110
+ mountPoint?: string;
111
+ }
112
+ export type BufferEncoding = 'utf8' | 'utf-8';
113
+ export interface FileSystemAPI {
114
+ /**
115
+ * Read a file. With no encoding, returns a `Uint8Array`; with 'utf8'/'utf-8',
116
+ * returns a string. Mirrors @webcontainer/api's `readFile`.
117
+ */
118
+ readFile(path: string): Promise<Uint8Array>;
119
+ readFile(path: string, encoding: BufferEncoding): Promise<string>;
120
+ /** Write a file (creates parent dirs). Honors the 1 MiB/utf8 limit (throws on overflow). */
121
+ writeFile(path: string, data: string | Uint8Array, options?: {
122
+ encoding?: BufferEncoding | null;
123
+ } | BufferEncoding | null): Promise<void>;
124
+ /** List a directory. `{ withFileTypes: true }` returns Dirent-like entries. */
125
+ readdir(path: string): Promise<string[]>;
126
+ readdir(path: string, options: {
127
+ withFileTypes: true;
128
+ }): Promise<DirEnt[]>;
129
+ readdir(path: string, options: {
130
+ encoding?: BufferEncoding;
131
+ withFileTypes?: false;
132
+ }): Promise<string[]>;
133
+ /** Create a directory. `{ recursive: true }` creates parents. */
134
+ mkdir(path: string, options?: {
135
+ recursive?: boolean;
136
+ }): Promise<void>;
137
+ /** Remove a file or directory. `{ recursive: true }` removes a non-empty dir. */
138
+ rm(path: string, options?: {
139
+ force?: boolean;
140
+ recursive?: boolean;
141
+ }): Promise<void>;
142
+ /** Rename / move a path. */
143
+ rename(oldPath: string, newPath: string): Promise<void>;
144
+ /**
145
+ * Watch a path for changes (mirrors @webcontainer/api). Backed by the REAL
146
+ * MemoryFS watch primitive: delivers genuine 'rename'/'change' events for the
147
+ * watched VFS subtree and returns a `{ close() }` watcher handle. The watcher is
148
+ * tracked by the container and closed on `teardown()`.
149
+ */
150
+ watch(path: string, listener: WatchListener): FsWatcher;
151
+ watch(path: string, options: WatchOptions, listener: WatchListener): FsWatcher;
152
+ watch(path: string, options?: WatchOptions): FsWatcher;
153
+ }
154
+ /** A Dirent-like entry returned by `readdir(path, { withFileTypes: true })`. */
155
+ export interface DirEnt {
156
+ name: string;
157
+ isFile(): boolean;
158
+ isDirectory(): boolean;
159
+ }
160
+ export interface SpawnOptions {
161
+ /** Working directory for the command (default: `workdir`). */
162
+ cwd?: string;
163
+ /** Environment variables merged over the defaults. */
164
+ env?: Record<string, string | number | boolean>;
165
+ /** Initial terminal dimensions (accepted for parity; informational). */
166
+ terminal?: {
167
+ cols: number;
168
+ rows: number;
169
+ };
170
+ /** Output stream as raw bytes instead of decoded strings (parity; default false). */
171
+ output?: boolean;
172
+ }
173
+ /**
174
+ * A spawned process handle. Mirrors @webcontainer/api's `WebContainerProcess`:
175
+ * a string output stream, a string input stream, an exit promise, and `kill()`.
176
+ */
177
+ export interface WebContainerProcess {
178
+ /** Combined stdout/stderr as decoded strings. */
179
+ readonly output: ReadableStream<string>;
180
+ /** stdin as decoded strings. */
181
+ readonly input: WritableStream<string>;
182
+ /** Resolves with the process exit code. */
183
+ readonly exit: Promise<number>;
184
+ /** Resize the (virtual) terminal (parity; informational for the dev server). */
185
+ resize?(dimensions: {
186
+ cols: number;
187
+ rows: number;
188
+ }): void;
189
+ /** Terminate the process. */
190
+ kill(): void;
191
+ }
192
+ export type Port = number;
193
+ /** `on('server-ready')` — fired when a dev server starts listening. */
194
+ export type ServerReadyListener = (port: Port, url: string) => void;
195
+ /** Port lifecycle type for `on('port')`. */
196
+ export type PortListenerType = 'open' | 'close';
197
+ /** `on('port')` — fired when a port opens/closes; mirrors @webcontainer/api. */
198
+ export type PortListener = (port: Port, type: PortListenerType, url: string) => void;
199
+ /** `on('error')` — fired on an internal error. */
200
+ export type ErrorListener = (error: {
201
+ message: string;
202
+ }) => void;
203
+ /**
204
+ * `on('preview-message')` — preview-origin postMessage relay. NOT yet
205
+ * implemented; the listener type exists for parity but the facade does not yet
206
+ * forward preview-origin messages (documented stub).
207
+ */
208
+ export type PreviewMessageListener = (message: unknown) => void;
209
+ /**
210
+ * `on('xdg-open')` — a guest "open this url/file" request, mirroring
211
+ * @webcontainer/api. Accepted for parity (registering a listener never throws);
212
+ * the facade does not yet EMIT it (no guest process surfaces xdg-open yet).
213
+ */
214
+ export type XdgOpenListener = (url: string) => void;
215
+ /**
216
+ * `on('code')` — a guest "open in editor" request, mirroring @webcontainer/api's
217
+ * IDE integration hook. Accepted for parity (registering a listener never
218
+ * throws); the facade does not yet EMIT it.
219
+ */
220
+ export type CodeListener = (path: string) => void;
221
+ export interface WebContainerEventMap {
222
+ 'server-ready': ServerReadyListener;
223
+ port: PortListener;
224
+ error: ErrorListener;
225
+ 'preview-message': PreviewMessageListener;
226
+ /** Parity hook (never emitted yet); registering a listener never throws. */
227
+ 'xdg-open': XdgOpenListener;
228
+ /** Parity hook (never emitted yet); registering a listener never throws. */
229
+ code: CodeListener;
230
+ }
@@ -0,0 +1,269 @@
1
+ self.addEventListener('install', (event) => {
2
+ event.waitUntil(self.skipWaiting());
3
+ });
4
+
5
+ self.addEventListener('activate', (event) => {
6
+ event.waitUntil(self.clients.claim());
7
+ });
8
+
9
+ let runtimeRequestId = 0;
10
+
11
+ // ── Navigation-proxy opt-in (Next App Router document-level hydration) ──
12
+ // The Vite preview injects a module entry into its own bootstrap document and
13
+ // RELIES on navigations being bypassed (return fetch). The Next App Router calls
14
+ // hydrateRoot(document, …) — it hydrates the WHOLE document — so the iframe
15
+ // document must BE Next's real SSR HTML, which means the navigation itself has to
16
+ // be served by the runtime (not the static bootstrap shell). This is enabled ONLY
17
+ // when the SW script was registered with the explicit ?navProxy=1 marker, so the
18
+ // Vite registration (plain runtime-sw.js) keeps the byte-identical navigate-bypass.
19
+ const NAV_PROXY_ENABLED = (() => {
20
+ try {
21
+ return new URL(self.location.href).searchParams.get('navProxy') === '1';
22
+ } catch (_) {
23
+ return false;
24
+ }
25
+ })();
26
+
27
+ // The window client (the parent page that owns the runtime worker + dispatch glue)
28
+ // that relays runtime HTTP requests for NAVIGATIONS. A navigation has no controlling
29
+ // client yet (the document is mid-creation), so the SW cannot post to event.clientId.
30
+ // The parent page registers itself here via a wc:nav-relay-register message; the SW
31
+ // then routes navigation requests to it, exactly like a subresource but flagged
32
+ // navigate:true so the parent can frame the response as a full document.
33
+ let navRelayClientId = null;
34
+
35
+ self.addEventListener('message', (event) => {
36
+ const data = event.data;
37
+ if (data && data.type === 'wc:nav-relay-register' && event.source && event.source.id) {
38
+ navRelayClientId = event.source.id;
39
+ if (event.ports && event.ports[0]) {
40
+ try { event.ports[0].postMessage({ type: 'wc:nav-relay-registered' }); } catch (_) {}
41
+ }
42
+ }
43
+ });
44
+
45
+ // Response headers that describe the ON-THE-WIRE transfer framing of the routed
46
+ // response. The runtime worker buffers the whole body and hands us the decoded
47
+ // bytes, so re-advertising chunked/gzip/length from the upstream Next response would
48
+ // make the browser's native DOCUMENT loader mis-frame the navigation (observed as
49
+ // net::ERR_FAILED / net::ERR_CONTENT_LENGTH_MISMATCH). Strip them for navigations so
50
+ // the browser frames the already-decoded body itself.
51
+ function stripTransferFramingHeaders(headers) {
52
+ if (!Array.isArray(headers)) return headers;
53
+ return headers.filter((header) => {
54
+ if (!Array.isArray(header) || typeof header[0] !== 'string') return true;
55
+ const name = header[0].toLowerCase();
56
+ return name !== 'transfer-encoding' && name !== 'content-encoding' && name !== 'content-length';
57
+ });
58
+ }
59
+
60
+ function responseFromMessage(message, expectedId, stripFraming) {
61
+ if (!message || !(message.type === 'wc:runtime-http-response') || message.id !== expectedId) {
62
+ throw new Error('Unexpected WebContainers runtime HTTP response for ' + expectedId);
63
+ }
64
+ if (!(message.status === 101 || (message.status >= 200 && message.status <= 599))) {
65
+ throw new Error('ERR_WC_SERVICE_WORKER_RESPONSE_STATUS_UNSUPPORTED: Runtime Service Worker routing received unsupported response status ' + message.status + ' for ' + expectedId + '. Refusing to construct a browser Response outside the currently proven 101/200-599 status subset; do not claim arbitrary Fetch Response construction, browser status normalization, or broad Service Worker response serialization parity until automated browser smoke proves those semantics.');
66
+ }
67
+ if (typeof ReadableStream !== 'undefined' && message.body instanceof ReadableStream) {
68
+ throw new Error('ERR_WC_SERVICE_WORKER_RESPONSE_STREAM_UNSUPPORTED: Runtime Service Worker routing streaming response bodies and backpressure are not implemented. Send string, ArrayBuffer, Blob, FormData, URLSearchParams, or null response bodies only; do not claim response streaming parity, browser UX parity, production Service Worker deployment readiness, or broad Service Worker routing parity until automated browser smoke proves streaming and cleanup semantics.');
69
+ }
70
+ const body = message.body;
71
+ const supportedBody = body === undefined || body === null || typeof body === 'string' || body instanceof ArrayBuffer || ArrayBuffer.isView(body) || (typeof Blob !== 'undefined' && body instanceof Blob) || (typeof FormData !== 'undefined' && body instanceof FormData) || (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams);
72
+ if (!supportedBody) {
73
+ throw new Error('ERR_WC_SERVICE_WORKER_RESPONSE_BODY_UNSUPPORTED: Runtime Service Worker routing received an object response body for ' + expectedId + '. Send string, ArrayBuffer, Blob, FormData, URLSearchParams, or null response bodies only; do not claim arbitrary response body serialization, implicit body coercion, Fetch BodyInit parity, or broad Service Worker response parity until automated browser smoke proves those semantics.');
74
+ }
75
+ if (Array.isArray(message.headers) && message.headers.some((header) => !Array.isArray(header) || typeof header[0] !== 'string' || typeof header[1] !== 'string')) {
76
+ throw new Error('ERR_WC_SERVICE_WORKER_RESPONSE_HEADERS_UNSUPPORTED: Runtime Service Worker routing received a non-string routed response header for ' + expectedId + '. Refusing to coerce header names or values through browser Headers; do not claim arbitrary response header serialization, implicit header coercion, Fetch header normalization, or broad Service Worker response parity until automated browser smoke proves those semantics.');
77
+ }
78
+ if (Array.isArray(message.headers) && message.headers.some((header) => header[0].toLowerCase() === 'set-cookie')) {
79
+ throw new Error('ERR_WC_SERVICE_WORKER_RESPONSE_HEADERS_UNSUPPORTED: Set-Cookie response headers are not represented by the current Service Worker routing protocol. Refusing to construct a Response that could collapse, expose, or misrepresent forbidden response headers; do not claim multi-value Set-Cookie, forbidden response-header filtering, browser cookie, Fetch, or broad Service Worker header parity until automated browser smoke proves those semantics.');
80
+ }
81
+ return new Response(message.body ?? null, {
82
+ status: message.status,
83
+ statusText: message.statusText,
84
+ headers: stripFraming ? stripTransferFramingHeaders(message.headers) : message.headers,
85
+ });
86
+ }
87
+
88
+ function requestBodySerializationError(request, cause) {
89
+ const causeText = cause && cause.name ? cause.name + ': ' + cause.message : String(cause);
90
+ return new Error('ERR_WC_SERVICE_WORKER_REQUEST_BODY_UNSUPPORTED: Runtime Service Worker routing could not clone or serialize the request body for ' + request.method + ' ' + request.url + '. The request body has already been consumed, is locked, or cannot be represented by the current string/ArrayBuffer routing subset; do not claim arbitrary request cloning/body serialization parity, streamed upload parity, browser UX parity, production Service Worker deployment readiness, or broad Service Worker routing parity until automated browser smoke proves those semantics. Cause: ' + causeText);
91
+ }
92
+
93
+ function requestRedirectModeUnsupportedError(request) {
94
+ return new Error('ERR_WC_SERVICE_WORKER_REQUEST_REDIRECT_UNSUPPORTED: Runtime Service Worker routing only serializes requests with redirect mode follow. Refusing ' + request.method + ' ' + request.url + ' because redirect mode ' + request.redirect + ' is not represented by the current request/response protocol; do not claim redirect handling parity, browser UX parity, production Service Worker deployment readiness, or broad Service Worker routing parity until automated browser smoke proves manual/error redirect semantics.');
95
+ }
96
+
97
+ function requestCacheModeUnsupportedError(request) {
98
+ return new Error('ERR_WC_SERVICE_WORKER_REQUEST_CACHE_UNSUPPORTED: Runtime Service Worker routing only serializes requests with cache mode default. Refusing ' + request.method + ' ' + request.url + ' because cache mode ' + request.cache + ' is not represented by the current request/response protocol; do not claim browser cache routing parity, Cache API parity, offline readiness, browser UX parity, production Service Worker deployment readiness, or broad Service Worker routing parity until automated browser smoke proves cache-mode semantics.');
99
+ }
100
+
101
+ function requestModeUnsupportedError(request) {
102
+ return new Error('ERR_WC_SERVICE_WORKER_REQUEST_MODE_UNSUPPORTED: Runtime Service Worker routing does not serialize no-cors opaque request semantics. Refusing ' + request.method + ' ' + request.url + ' because request mode ' + request.mode + ' is not represented by the current request/response protocol; do not claim opaque request, browser fetch mode, cross-origin preview routing, or broad Service Worker routing parity until automated browser smoke proves these semantics.');
103
+ }
104
+
105
+ function requestCredentialsUnsupportedError(request) {
106
+ return new Error('ERR_WC_SERVICE_WORKER_REQUEST_CREDENTIALS_UNSUPPORTED: Runtime Service Worker routing does not serialize credentials mode omit. Refusing ' + request.method + ' ' + request.url + ' because credentials mode ' + request.credentials + ' is not represented by the current request/response protocol; do not claim browser cookie, credential forwarding, Fetch credentials, cross-origin preview routing, or broad Service Worker request serialization parity until automated browser smoke proves these semantics.');
107
+ }
108
+
109
+ function serviceWorkerPostMessageUnsupportedError(request, cause) {
110
+ const causeText = cause && cause.name ? cause.name + ': ' + cause.message : String(cause);
111
+ return new Error('ERR_WC_SERVICE_WORKER_POSTMESSAGE_UNSUPPORTED: Runtime Service Worker routing failed to post the serialized request for ' + request.method + ' ' + request.url + ' to the controlling client. Service Worker routing transfer lists must contain the response MessagePort and any ArrayBuffer body as valid transferables; do not claim Service Worker routing transfer-list readiness, arbitrary request/response serialization parity, browser UX parity, production Service Worker deployment readiness, or broad Service Worker routing parity until automated browser smoke proves this boundary. Cause: ' + causeText);
112
+ }
113
+
114
+ function assertSupportedRequestRedirectMode(request) {
115
+ if (request.redirect !== 'follow') throw requestRedirectModeUnsupportedError(request);
116
+ }
117
+
118
+ function assertSupportedRequestCacheMode(request) {
119
+ if (request.cache !== 'default') throw requestCacheModeUnsupportedError(request);
120
+ }
121
+
122
+ function isSameOriginRequest(request) {
123
+ try {
124
+ return new URL(request.url).origin === self.location.origin;
125
+ } catch (_) {
126
+ return false;
127
+ }
128
+ }
129
+
130
+ function assertSupportedRequestMode(request) {
131
+ // no-cors is only problematic CROSS-origin, where the response would be opaque
132
+ // and unreadable. For SAME-origin requests (every preview subresource — the
133
+ // browser issues classic <script src> chunk loads with mode 'no-cors') the
134
+ // response is a normal, fully-readable response, so routing it is correct and
135
+ // not an opaque/cross-origin claim. Refuse only cross-origin no-cors.
136
+ if (request.mode === 'no-cors' && !isSameOriginRequest(request)) throw requestModeUnsupportedError(request);
137
+ }
138
+
139
+ function assertSupportedRequestCredentials(request) {
140
+ if (request.credentials === 'omit') throw requestCredentialsUnsupportedError(request);
141
+ }
142
+
143
+ async function serializeRequestBody(request) {
144
+ const hasRequestBody = request.method !== 'GET' && request.method !== 'HEAD';
145
+ if (!hasRequestBody) return {};
146
+ const contentType = request.headers.get('content-type') || '';
147
+ const useTextBody = contentType === '' || /^(text\/)|json|javascript|xml|x-www-form-urlencoded/.test(contentType);
148
+ try {
149
+ if (request.bodyUsed) throw new Error('request body has already been consumed before Service Worker routing serialization');
150
+ const body = useTextBody ? await request.clone().text() : await request.clone().arrayBuffer();
151
+ return useTextBody ? { body } : { body, bodyEncoding: 'arrayBuffer' };
152
+ } catch (error) {
153
+ throw requestBodySerializationError(request, error);
154
+ }
155
+ }
156
+
157
+ // Route a fetch event through a window client over the MessagePort protocol. The
158
+ // same path serves subresources (client = the iframe itself) and — when navigation
159
+ // proxying is opted in — the iframe NAVIGATION (client = the registered nav relay,
160
+ // isNavigate=true so we strip on-the-wire framing headers and tell the relay this is
161
+ // a document request).
162
+ async function routeThroughClient(event, client, isNavigate) {
163
+ const id = String(++runtimeRequestId);
164
+ const channel = new MessageChannel();
165
+ let serializedRequestBody;
166
+ try {
167
+ // A navigation request intercepted by a Service Worker carries redirect mode
168
+ // 'manual' (the browser handles document redirects itself), request mode
169
+ // 'navigate', and — for a reload navigation — cache mode 'reload'/'no-cache'.
170
+ // All three are correct for a document load and must not be refused by the
171
+ // subresource-oriented guards (refusing a reload navigation rejects respondWith →
172
+ // net::ERR_FAILED → the iframe lands on a browser error page). We still serialize
173
+ // the body normally (a navigation is GET, so there is none). Subresources keep the
174
+ // full guard set unchanged.
175
+ if (!isNavigate) {
176
+ assertSupportedRequestRedirectMode(event.request);
177
+ assertSupportedRequestMode(event.request);
178
+ assertSupportedRequestCacheMode(event.request);
179
+ }
180
+ assertSupportedRequestCredentials(event.request);
181
+ serializedRequestBody = await serializeRequestBody(event.request);
182
+ } catch (error) {
183
+ channel.port1.close();
184
+ channel.port2.close();
185
+ throw error;
186
+ }
187
+ const body = serializedRequestBody.body;
188
+ const bodyEncoding = serializedRequestBody.bodyEncoding;
189
+ const response = new Promise((resolve, reject) => {
190
+ channel.port1.onmessage = (portEvent) => {
191
+ try {
192
+ resolve(responseFromMessage(portEvent.data, id, isNavigate));
193
+ } catch (error) {
194
+ reject(error);
195
+ } finally {
196
+ channel.port1.close();
197
+ }
198
+ };
199
+ channel.port1.start();
200
+ });
201
+
202
+ const transfer = bodyEncoding === 'arrayBuffer' ? [channel.port2, body] : [channel.port2];
203
+ try {
204
+ client.postMessage({
205
+ type: 'wc:runtime-http-request',
206
+ id,
207
+ url: event.request.url,
208
+ method: event.request.method,
209
+ headers: [...event.request.headers.entries()],
210
+ body,
211
+ bodyEncoding,
212
+ navigate: isNavigate === true,
213
+ }, transfer);
214
+ } catch (error) {
215
+ channel.port1.close();
216
+ channel.port2.close();
217
+ throw serviceWorkerPostMessageUnsupportedError(event.request, error);
218
+ }
219
+
220
+ return response;
221
+ }
222
+
223
+ self.addEventListener('fetch', (event) => {
224
+ const clientId = event.clientId || event.resultingClientId;
225
+ event.respondWith((async () => {
226
+ const isNavigate = event.request.mode === 'navigate';
227
+
228
+ if (isNavigate && !NAV_PROXY_ENABLED) {
229
+ // Vite (and every non-opted-in registration) keeps the byte-identical bypass:
230
+ // its bootstrap shell is served statically and it injects its own module entry.
231
+ return fetch(event.request);
232
+ }
233
+
234
+ if (NAV_PROXY_ENABLED) {
235
+ // In nav-proxy mode the iframe document IS the runtime's real HTML (so React can
236
+ // hydrate the whole document), which means it carries no relay script. Route BOTH
237
+ // the navigation AND every subresource it then requests through the registered
238
+ // nav relay (the parent page that owns the runtime worker). Navigations strip the
239
+ // on-the-wire framing headers so the native document loader frames the body.
240
+ let inScope = false;
241
+ try {
242
+ const reqUrl = new URL(event.request.url);
243
+ const scopePath = self.registration.scope.indexOf(self.location.origin) === 0
244
+ ? self.registration.scope.slice(self.location.origin.length)
245
+ : self.registration.scope;
246
+ inScope = reqUrl.origin === self.location.origin && reqUrl.pathname.indexOf(scopePath) === 0;
247
+ } catch (_) {
248
+ inScope = false;
249
+ }
250
+ if (!inScope) return fetch(event.request);
251
+ const relay = navRelayClientId ? await self.clients.get(navRelayClientId) : undefined;
252
+ if (!relay) {
253
+ // No relay registered yet: navigations must not silently fall through to the
254
+ // static shell (that would defeat document-level hydration), but a subresource
255
+ // can still try the controlling client.
256
+ if (isNavigate) return fetch(event.request);
257
+ const fallback = clientId ? await self.clients.get(clientId) : undefined;
258
+ if (!fallback) return fetch(event.request);
259
+ return routeThroughClient(event, fallback, false);
260
+ }
261
+ return routeThroughClient(event, relay, isNavigate);
262
+ }
263
+
264
+ const client = clientId ? await self.clients.get(clientId) : undefined;
265
+ if (!client) return fetch(event.request);
266
+
267
+ return routeThroughClient(event, client, false);
268
+ })());
269
+ });