@volter/tabnode 0.2.14-volter.88
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/LICENSE +22 -0
- package/README.md +78 -0
- package/THIRD-PARTY-NOTICES.md +12 -0
- package/dist/__sw__.js +526 -0
- package/dist/assets/runtime-worker-4MKaHFVb.js +75221 -0
- package/dist/assets/runtime-worker-4MKaHFVb.js.map +1 -0
- package/dist/code-transforms.d.ts +21 -0
- package/dist/code-transforms.d.ts.map +1 -0
- package/dist/config/cdn.d.ts +12 -0
- package/dist/config/cdn.d.ts.map +1 -0
- package/dist/create-runtime.d.ts +22 -0
- package/dist/create-runtime.d.ts.map +1 -0
- package/dist/host-globals.d.ts +62 -0
- package/dist/host-globals.d.ts.map +1 -0
- package/dist/index.cjs +36021 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +127 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +36020 -0
- package/dist/index.mjs.map +1 -0
- package/dist/node-internals.d.ts +68 -0
- package/dist/node-internals.d.ts.map +1 -0
- package/dist/node-lib/binding/blob.d.ts +12 -0
- package/dist/node-lib/binding/blob.d.ts.map +1 -0
- package/dist/node-lib/binding/buffer.d.ts +144 -0
- package/dist/node-lib/binding/buffer.d.ts.map +1 -0
- package/dist/node-lib/binding/cares_wrap.d.ts +14 -0
- package/dist/node-lib/binding/cares_wrap.d.ts.map +1 -0
- package/dist/node-lib/binding/fds.d.ts +30 -0
- package/dist/node-lib/binding/fds.d.ts.map +1 -0
- package/dist/node-lib/binding/fs.d.ts +157 -0
- package/dist/node-lib/binding/fs.d.ts.map +1 -0
- package/dist/node-lib/binding/handles.d.ts +81 -0
- package/dist/node-lib/binding/handles.d.ts.map +1 -0
- package/dist/node-lib/binding/http_parser.d.ts +122 -0
- package/dist/node-lib/binding/http_parser.d.ts.map +1 -0
- package/dist/node-lib/binding/index.d.ts +3 -0
- package/dist/node-lib/binding/index.d.ts.map +1 -0
- package/dist/node-lib/binding/llhttp-wasm.d.ts +22 -0
- package/dist/node-lib/binding/llhttp-wasm.d.ts.map +1 -0
- package/dist/node-lib/binding/misc.d.ts +123 -0
- package/dist/node-lib/binding/misc.d.ts.map +1 -0
- package/dist/node-lib/binding/pipe_wrap.d.ts +84 -0
- package/dist/node-lib/binding/pipe_wrap.d.ts.map +1 -0
- package/dist/node-lib/binding/process_wrap.d.ts +173 -0
- package/dist/node-lib/binding/process_wrap.d.ts.map +1 -0
- package/dist/node-lib/binding/spawn_sync.d.ts +30 -0
- package/dist/node-lib/binding/spawn_sync.d.ts.map +1 -0
- package/dist/node-lib/binding/stream_wrap.d.ts +191 -0
- package/dist/node-lib/binding/stream_wrap.d.ts.map +1 -0
- package/dist/node-lib/binding/stubs.d.ts +23 -0
- package/dist/node-lib/binding/stubs.d.ts.map +1 -0
- package/dist/node-lib/binding/tcp_wrap.d.ts +98 -0
- package/dist/node-lib/binding/tcp_wrap.d.ts.map +1 -0
- package/dist/node-lib/binding/tty_wrap.d.ts +36 -0
- package/dist/node-lib/binding/tty_wrap.d.ts.map +1 -0
- package/dist/node-lib/binding/util.d.ts +126 -0
- package/dist/node-lib/binding/util.d.ts.map +1 -0
- package/dist/node-lib/binding/uv.d.ts +32 -0
- package/dist/node-lib/binding/uv.d.ts.map +1 -0
- package/dist/node-lib/binding/zlib-pako.d.ts +22 -0
- package/dist/node-lib/binding/zlib-pako.d.ts.map +1 -0
- package/dist/node-lib/binding/zlib.d.ts +38 -0
- package/dist/node-lib/binding/zlib.d.ts.map +1 -0
- package/dist/node-lib/buffer-module.d.ts +51 -0
- package/dist/node-lib/buffer-module.d.ts.map +1 -0
- package/dist/node-lib/child-process-module.d.ts +26 -0
- package/dist/node-lib/child-process-module.d.ts.map +1 -0
- package/dist/node-lib/events-module.d.ts +58 -0
- package/dist/node-lib/events-module.d.ts.map +1 -0
- package/dist/node-lib/fs-module.d.ts +5 -0
- package/dist/node-lib/fs-module.d.ts.map +1 -0
- package/dist/node-lib/http-bridge.d.ts +43 -0
- package/dist/node-lib/http-bridge.d.ts.map +1 -0
- package/dist/node-lib/http-module.d.ts +35 -0
- package/dist/node-lib/http-module.d.ts.map +1 -0
- package/dist/node-lib/internals/addresses.d.ts +40 -0
- package/dist/node-lib/internals/addresses.d.ts.map +1 -0
- package/dist/node-lib/internals/buffer-and-streams.d.ts +96 -0
- package/dist/node-lib/internals/buffer-and-streams.d.ts.map +1 -0
- package/dist/node-lib/internals/child-process.d.ts +27 -0
- package/dist/node-lib/internals/child-process.d.ts.map +1 -0
- package/dist/node-lib/internals/events-util.d.ts +83 -0
- package/dist/node-lib/internals/events-util.d.ts.map +1 -0
- package/dist/node-lib/internals/index.d.ts +2 -0
- package/dist/node-lib/internals/index.d.ts.map +1 -0
- package/dist/node-lib/internals/modules.d.ts +67 -0
- package/dist/node-lib/internals/modules.d.ts.map +1 -0
- package/dist/node-lib/internals/readline-access.d.ts +2 -0
- package/dist/node-lib/internals/readline-access.d.ts.map +1 -0
- package/dist/node-lib/internals/runtime.d.ts +150 -0
- package/dist/node-lib/internals/runtime.d.ts.map +1 -0
- package/dist/node-lib/internals/util.d.ts +33 -0
- package/dist/node-lib/internals/util.d.ts.map +1 -0
- package/dist/node-lib/lazy.d.ts +31 -0
- package/dist/node-lib/lazy.d.ts.map +1 -0
- package/dist/node-lib/load.d.ts +27 -0
- package/dist/node-lib/load.d.ts.map +1 -0
- package/dist/node-lib/module-hooks.d.ts +89 -0
- package/dist/node-lib/module-hooks.d.ts.map +1 -0
- package/dist/node-lib/net-module.d.ts +181 -0
- package/dist/node-lib/net-module.d.ts.map +1 -0
- package/dist/node-lib/node-versions.d.ts +27 -0
- package/dist/node-lib/node-versions.d.ts.map +1 -0
- package/dist/node-lib/public-modules.d.ts +2 -0
- package/dist/node-lib/public-modules.d.ts.map +1 -0
- package/dist/node-lib/require-hook.d.ts +15 -0
- package/dist/node-lib/require-hook.d.ts.map +1 -0
- package/dist/node-lib/small-modules.d.ts +16 -0
- package/dist/node-lib/small-modules.d.ts.map +1 -0
- package/dist/node-lib/sources.d.ts +4 -0
- package/dist/node-lib/sources.d.ts.map +1 -0
- package/dist/node-lib/stream-module.d.ts +77 -0
- package/dist/node-lib/stream-module.d.ts.map +1 -0
- package/dist/node-lib/timers.d.ts +19 -0
- package/dist/node-lib/timers.d.ts.map +1 -0
- package/dist/node-lib/util-module.d.ts +76 -0
- package/dist/node-lib/util-module.d.ts.map +1 -0
- package/dist/node-resolution.d.ts +50 -0
- package/dist/node-resolution.d.ts.map +1 -0
- package/dist/node-resolver.d.ts +7 -0
- package/dist/node-resolver.d.ts.map +1 -0
- package/dist/npm/index.d.ts +73 -0
- package/dist/npm/index.d.ts.map +1 -0
- package/dist/npm/registry.d.ts +111 -0
- package/dist/npm/registry.d.ts.map +1 -0
- package/dist/npm/resolver.d.ts +79 -0
- package/dist/npm/resolver.d.ts.map +1 -0
- package/dist/npm/tarball.d.ts +28 -0
- package/dist/npm/tarball.d.ts.map +1 -0
- package/dist/process-tokens.d.ts +77 -0
- package/dist/process-tokens.d.ts.map +1 -0
- package/dist/punycode-source.d.ts +3 -0
- package/dist/punycode-source.d.ts.map +1 -0
- package/dist/runtime-interface.d.ts +74 -0
- package/dist/runtime-interface.d.ts.map +1 -0
- package/dist/runtime.d.ts +144 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/server-bridge.d.ts +147 -0
- package/dist/server-bridge.d.ts.map +1 -0
- package/dist/shims/async_hooks.d.ts +50 -0
- package/dist/shims/async_hooks.d.ts.map +1 -0
- package/dist/shims/child_process.d.ts +104 -0
- package/dist/shims/child_process.d.ts.map +1 -0
- package/dist/shims/cluster.d.ts +52 -0
- package/dist/shims/cluster.d.ts.map +1 -0
- package/dist/shims/command-line.d.ts +43 -0
- package/dist/shims/command-line.d.ts.map +1 -0
- package/dist/shims/constants.d.ts +222 -0
- package/dist/shims/constants.d.ts.map +1 -0
- package/dist/shims/crypto-constants.d.ts +12 -0
- package/dist/shims/crypto-constants.d.ts.map +1 -0
- package/dist/shims/crypto.d.ts +144 -0
- package/dist/shims/crypto.d.ts.map +1 -0
- package/dist/shims/dgram.d.ts +34 -0
- package/dist/shims/dgram.d.ts.map +1 -0
- package/dist/shims/dns.d.ts +91 -0
- package/dist/shims/dns.d.ts.map +1 -0
- package/dist/shims/domain.d.ts +25 -0
- package/dist/shims/domain.d.ts.map +1 -0
- package/dist/shims/esbuild.d.ts +205 -0
- package/dist/shims/esbuild.d.ts.map +1 -0
- package/dist/shims/fs.d.ts +25 -0
- package/dist/shims/fs.d.ts.map +1 -0
- package/dist/shims/http2.d.ts +81 -0
- package/dist/shims/http2.d.ts.map +1 -0
- package/dist/shims/inspector.d.ts +25 -0
- package/dist/shims/inspector.d.ts.map +1 -0
- package/dist/shims/module.d.ts +22 -0
- package/dist/shims/module.d.ts.map +1 -0
- package/dist/shims/os.d.ts +159 -0
- package/dist/shims/os.d.ts.map +1 -0
- package/dist/shims/path.d.ts +52 -0
- package/dist/shims/path.d.ts.map +1 -0
- package/dist/shims/perf_hooks.d.ts +50 -0
- package/dist/shims/perf_hooks.d.ts.map +1 -0
- package/dist/shims/process.d.ts +226 -0
- package/dist/shims/process.d.ts.map +1 -0
- package/dist/shims/rollup.d.ts +71 -0
- package/dist/shims/rollup.d.ts.map +1 -0
- package/dist/shims/string_decoder.d.ts +41 -0
- package/dist/shims/string_decoder.d.ts.map +1 -0
- package/dist/shims/sync-child.d.ts +64 -0
- package/dist/shims/sync-child.d.ts.map +1 -0
- package/dist/shims/tls.d.ts +53 -0
- package/dist/shims/tls.d.ts.map +1 -0
- package/dist/shims/url.d.ts +84 -0
- package/dist/shims/url.d.ts.map +1 -0
- package/dist/shims/v8.d.ts +73 -0
- package/dist/shims/v8.d.ts.map +1 -0
- package/dist/shims/vfs-adapter.d.ts +126 -0
- package/dist/shims/vfs-adapter.d.ts.map +1 -0
- package/dist/shims/vm.d.ts +52 -0
- package/dist/shims/vm.d.ts.map +1 -0
- package/dist/shims/wasi.d.ts +78 -0
- package/dist/shims/wasi.d.ts.map +1 -0
- package/dist/shims/worker_threads.d.ts +66 -0
- package/dist/shims/worker_threads.d.ts.map +1 -0
- package/dist/shims/zlib-constants.d.ts +66 -0
- package/dist/shims/zlib-constants.d.ts.map +1 -0
- package/dist/shims/zlib.d.ts +102 -0
- package/dist/shims/zlib.d.ts.map +1 -0
- package/dist/transform.d.ts +24 -0
- package/dist/transform.d.ts.map +1 -0
- package/dist/types/package-json.d.ts +18 -0
- package/dist/types/package-json.d.ts.map +1 -0
- package/dist/utils/binary-encoding.d.ts +13 -0
- package/dist/utils/binary-encoding.d.ts.map +1 -0
- package/dist/utils/glob.d.ts +19 -0
- package/dist/utils/glob.d.ts.map +1 -0
- package/dist/utils/hash.d.ts +6 -0
- package/dist/utils/hash.d.ts.map +1 -0
- package/dist/virtual-fs.d.ts +259 -0
- package/dist/virtual-fs.d.ts.map +1 -0
- package/dist/vite-plugin.cjs +50 -0
- package/dist/vite-plugin.cjs.map +1 -0
- package/dist/vite-plugin.d.ts +36 -0
- package/dist/vite-plugin.d.ts.map +1 -0
- package/dist/vite-plugin.mjs +31 -0
- package/dist/vite-plugin.mjs.map +1 -0
- package/dist/worker/runtime-worker.d.ts +8 -0
- package/dist/worker/runtime-worker.d.ts.map +1 -0
- package/dist/worker-runtime.d.ts +50 -0
- package/dist/worker-runtime.d.ts.map +1 -0
- package/package.json +88 -0
- package/src/code-transforms.ts +560 -0
- package/src/config/cdn.ts +13 -0
- package/src/create-runtime.ts +89 -0
- package/src/host-globals.ts +121 -0
- package/src/index.ts +235 -0
- package/src/node-internals.ts +283 -0
- package/src/node-lib/LICENSE +47 -0
- package/src/node-lib/_http_agent.js +547 -0
- package/src/node-lib/_http_client.js +996 -0
- package/src/node-lib/_http_common.js +269 -0
- package/src/node-lib/_http_incoming.js +453 -0
- package/src/node-lib/_http_outgoing.js +1262 -0
- package/src/node-lib/_http_server.js +1230 -0
- package/src/node-lib/assert/strict.js +3 -0
- package/src/node-lib/assert.js +844 -0
- package/src/node-lib/binding/blob.ts +35 -0
- package/src/node-lib/binding/buffer.ts +465 -0
- package/src/node-lib/binding/cares_wrap.ts +57 -0
- package/src/node-lib/binding/fds.ts +80 -0
- package/src/node-lib/binding/fs.ts +857 -0
- package/src/node-lib/binding/handles.ts +187 -0
- package/src/node-lib/binding/http_parser.ts +536 -0
- package/src/node-lib/binding/index.ts +77 -0
- package/src/node-lib/binding/llhttp-wasm.ts +670 -0
- package/src/node-lib/binding/misc.ts +252 -0
- package/src/node-lib/binding/pipe_wrap.ts +166 -0
- package/src/node-lib/binding/process_wrap.ts +371 -0
- package/src/node-lib/binding/spawn_sync.ts +141 -0
- package/src/node-lib/binding/stream_wrap.ts +413 -0
- package/src/node-lib/binding/stubs.ts +19 -0
- package/src/node-lib/binding/tcp_wrap.ts +254 -0
- package/src/node-lib/binding/tty_wrap.ts +56 -0
- package/src/node-lib/binding/util.ts +274 -0
- package/src/node-lib/binding/uv.ts +102 -0
- package/src/node-lib/binding/zlib-pako.ts +50 -0
- package/src/node-lib/binding/zlib.ts +259 -0
- package/src/node-lib/buffer-module.ts +70 -0
- package/src/node-lib/buffer.js +1373 -0
- package/src/node-lib/child-process-module.ts +58 -0
- package/src/node-lib/child_process.js +1046 -0
- package/src/node-lib/constants.js +41 -0
- package/src/node-lib/diagnostics_channel.js +438 -0
- package/src/node-lib/events-module.ts +80 -0
- package/src/node-lib/events.js +1223 -0
- package/src/node-lib/fs-module.ts +43 -0
- package/src/node-lib/fs.js +3377 -0
- package/src/node-lib/http-bridge.ts +299 -0
- package/src/node-lib/http-module.ts +53 -0
- package/src/node-lib/http.js +198 -0
- package/src/node-lib/https.js +429 -0
- package/src/node-lib/internal/assert/assertion_error.js +417 -0
- package/src/node-lib/internal/assert/calltracker.js +153 -0
- package/src/node-lib/internal/assert/myers_diff.js +177 -0
- package/src/node-lib/internal/assert/utils.js +278 -0
- package/src/node-lib/internal/buffer.js +1120 -0
- package/src/node-lib/internal/child_process/serialization.js +169 -0
- package/src/node-lib/internal/child_process.js +1135 -0
- package/src/node-lib/internal/constants.js +56 -0
- package/src/node-lib/internal/deps/minimatch/index.js +1628 -0
- package/src/node-lib/internal/errors.js +1912 -0
- package/src/node-lib/internal/events/abort_listener.js +54 -0
- package/src/node-lib/internal/events/symbols.js +11 -0
- package/src/node-lib/internal/fixed_queue.js +119 -0
- package/src/node-lib/internal/freelist.js +30 -0
- package/src/node-lib/internal/fs/cp/cp-sync.js +242 -0
- package/src/node-lib/internal/fs/cp/cp.js +382 -0
- package/src/node-lib/internal/fs/dir.js +369 -0
- package/src/node-lib/internal/fs/glob.js +763 -0
- package/src/node-lib/internal/fs/promises.js +1312 -0
- package/src/node-lib/internal/fs/read/context.js +129 -0
- package/src/node-lib/internal/fs/recursive_watch.js +275 -0
- package/src/node-lib/internal/fs/rimraf.js +311 -0
- package/src/node-lib/internal/fs/streams.js +571 -0
- package/src/node-lib/internal/fs/sync_write_stream.js +49 -0
- package/src/node-lib/internal/fs/utils.js +987 -0
- package/src/node-lib/internal/fs/watchers.js +418 -0
- package/src/node-lib/internal/http.js +62 -0
- package/src/node-lib/internal/mime.js +389 -0
- package/src/node-lib/internal/modules/customization_hooks.js +406 -0
- package/src/node-lib/internal/net.js +95 -0
- package/src/node-lib/internal/querystring.js +119 -0
- package/src/node-lib/internal/readline/callbacks.js +132 -0
- package/src/node-lib/internal/readline/emitKeypressEvents.js +99 -0
- package/src/node-lib/internal/readline/interface.js +1418 -0
- package/src/node-lib/internal/readline/promises.js +144 -0
- package/src/node-lib/internal/readline/utils.js +405 -0
- package/src/node-lib/internal/socket_list.js +108 -0
- package/src/node-lib/internal/stream_base_commons.js +280 -0
- package/src/node-lib/internal/streams/add-abort-signal.js +60 -0
- package/src/node-lib/internal/streams/compose.js +257 -0
- package/src/node-lib/internal/streams/destroy.js +361 -0
- package/src/node-lib/internal/streams/duplex.js +203 -0
- package/src/node-lib/internal/streams/duplexify.js +379 -0
- package/src/node-lib/internal/streams/duplexpair.js +62 -0
- package/src/node-lib/internal/streams/end-of-stream.js +331 -0
- package/src/node-lib/internal/streams/from.js +208 -0
- package/src/node-lib/internal/streams/lazy_transform.js +57 -0
- package/src/node-lib/internal/streams/legacy.js +125 -0
- package/src/node-lib/internal/streams/operators.js +436 -0
- package/src/node-lib/internal/streams/passthrough.js +47 -0
- package/src/node-lib/internal/streams/pipeline.js +465 -0
- package/src/node-lib/internal/streams/readable.js +1762 -0
- package/src/node-lib/internal/streams/state.js +51 -0
- package/src/node-lib/internal/streams/transform.js +204 -0
- package/src/node-lib/internal/streams/utils.js +363 -0
- package/src/node-lib/internal/streams/writable.js +1163 -0
- package/src/node-lib/internal/tty.js +260 -0
- package/src/node-lib/internal/util/colors.js +40 -0
- package/src/node-lib/internal/util/comparisons.js +1028 -0
- package/src/node-lib/internal/util/debuglog.js +524 -0
- package/src/node-lib/internal/util/inspect.js +2535 -0
- package/src/node-lib/internal/util/parse_args/parse_args.js +412 -0
- package/src/node-lib/internal/util/parse_args/utils.js +197 -0
- package/src/node-lib/internal/util.js +1003 -0
- package/src/node-lib/internal/validators.js +654 -0
- package/src/node-lib/internals/addresses.ts +159 -0
- package/src/node-lib/internals/buffer-and-streams.ts +224 -0
- package/src/node-lib/internals/child-process.ts +64 -0
- package/src/node-lib/internals/events-util.ts +105 -0
- package/src/node-lib/internals/index.ts +66 -0
- package/src/node-lib/internals/modules.ts +117 -0
- package/src/node-lib/internals/readline-access.ts +16 -0
- package/src/node-lib/internals/runtime.ts +277 -0
- package/src/node-lib/internals/util.ts +98 -0
- package/src/node-lib/lazy.ts +158 -0
- package/src/node-lib/load.ts +390 -0
- package/src/node-lib/module-hooks.ts +281 -0
- package/src/node-lib/net-module.ts +248 -0
- package/src/node-lib/net.js +2540 -0
- package/src/node-lib/node-versions.ts +34 -0
- package/src/node-lib/os.js +356 -0
- package/src/node-lib/path.js +1694 -0
- package/src/node-lib/primordials.js +743 -0
- package/src/node-lib/public-modules.ts +109 -0
- package/src/node-lib/punycode.js +455 -0
- package/src/node-lib/querystring.js +484 -0
- package/src/node-lib/readline/promises.js +73 -0
- package/src/node-lib/readline.js +523 -0
- package/src/node-lib/require-hook.ts +27 -0
- package/src/node-lib/small-modules.ts +35 -0
- package/src/node-lib/sources.ts +200 -0
- package/src/node-lib/stream/promises.js +44 -0
- package/src/node-lib/stream-module.ts +101 -0
- package/src/node-lib/stream.js +152 -0
- package/src/node-lib/timers.ts +33 -0
- package/src/node-lib/tty.js +168 -0
- package/src/node-lib/util-module.ts +104 -0
- package/src/node-lib/util.js +689 -0
- package/src/node-lib/wasi.js +179 -0
- package/src/node-lib/zlib.js +1036 -0
- package/src/node-resolution.ts +241 -0
- package/src/node-resolver.ts +33 -0
- package/src/npm/index.ts +425 -0
- package/src/npm/registry.ts +235 -0
- package/src/npm/resolver.ts +589 -0
- package/src/npm/tarball.ts +246 -0
- package/src/process-tokens.ts +156 -0
- package/src/punycode-source.ts +6 -0
- package/src/runtime-interface.ts +85 -0
- package/src/runtime.ts +3073 -0
- package/src/server-bridge.ts +971 -0
- package/src/shims/async_hooks.ts +181 -0
- package/src/shims/child_process.ts +1591 -0
- package/src/shims/cluster.ts +79 -0
- package/src/shims/command-line.ts +79 -0
- package/src/shims/constants.ts +68 -0
- package/src/shims/crypto-constants.ts +11 -0
- package/src/shims/crypto.ts +1061 -0
- package/src/shims/dgram.ts +47 -0
- package/src/shims/dns.ts +185 -0
- package/src/shims/domain.ts +58 -0
- package/src/shims/esbuild.ts +1638 -0
- package/src/shims/fs.ts +82 -0
- package/src/shims/http2.ts +96 -0
- package/src/shims/inspector.ts +30 -0
- package/src/shims/module.ts +86 -0
- package/src/shims/os.ts +195 -0
- package/src/shims/path.ts +246 -0
- package/src/shims/perf_hooks.ts +92 -0
- package/src/shims/process.ts +804 -0
- package/src/shims/rollup.ts +149 -0
- package/src/shims/string_decoder.ts +396 -0
- package/src/shims/sync-child.ts +469 -0
- package/src/shims/tls.ts +94 -0
- package/src/shims/url.ts +217 -0
- package/src/shims/v8.ts +113 -0
- package/src/shims/vfs-adapter.ts +408 -0
- package/src/shims/vm.ts +132 -0
- package/src/shims/wasi.ts +1440 -0
- package/src/shims/worker_threads.ts +114 -0
- package/src/shims/zlib-constants.ts +65 -0
- package/src/shims/zlib.ts +228 -0
- package/src/transform.ts +324 -0
- package/src/types/external.d.ts +55 -0
- package/src/types/package-json.ts +17 -0
- package/src/types/pako-zlib.d.ts +30 -0
- package/src/types/sha.js.d.ts +14 -0
- package/src/utils/binary-encoding.ts +43 -0
- package/src/utils/glob.ts +54 -0
- package/src/utils/hash.ts +12 -0
- package/src/virtual-fs.ts +1048 -0
- package/src/vite-plugin.ts +76 -0
- package/src/worker/runtime-worker.ts +128 -0
- package/src/worker-runtime.ts +145 -0
|
@@ -0,0 +1,971 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server Bridge
|
|
3
|
+
* Connects Service Worker requests to virtual HTTP servers
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
__requestOverLoopback, __streamOverLoopback, __upgradeOverLoopback, __listening,
|
|
8
|
+
type ResponseData,
|
|
9
|
+
} from './node-lib/http-bridge';
|
|
10
|
+
import { setPortWatchers } from './node-lib/net-module';
|
|
11
|
+
import { EventEmitter } from './node-lib/events-module';
|
|
12
|
+
import { __bridgeConnection } from './node-lib/net-module';
|
|
13
|
+
import type { Socket } from './node-lib/net-module';
|
|
14
|
+
import { Buffer } from './node-lib/buffer-module';
|
|
15
|
+
import { uint8ToBase64 } from './utils/binary-encoding';
|
|
16
|
+
|
|
17
|
+
const _encoder = new TextEncoder();
|
|
18
|
+
|
|
19
|
+
/** The bytes this view names, in a buffer of their own. A pooled Buffer is a window on 8 KB. */
|
|
20
|
+
function ownedBytes(view: Uint8Array): Uint8Array {
|
|
21
|
+
const copy = new Uint8Array(view.byteLength);
|
|
22
|
+
copy.set(new Uint8Array(view.buffer, view.byteOffset, view.byteLength));
|
|
23
|
+
return copy;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Interface for virtual servers that can be registered with the bridge
|
|
28
|
+
*/
|
|
29
|
+
export interface IVirtualServer {
|
|
30
|
+
listening: boolean;
|
|
31
|
+
/** What `net.Server.address()` answers: an address and a port, or the PATH of a unix-domain socket. */
|
|
32
|
+
address(): { port: number; address: string; family: string } | string | null;
|
|
33
|
+
handleRequest(
|
|
34
|
+
method: string,
|
|
35
|
+
url: string,
|
|
36
|
+
headers: Record<string, string>,
|
|
37
|
+
body?: Buffer | string
|
|
38
|
+
): Promise<ResponseData>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A guest that called `listen` is a server: it answers requests over the
|
|
43
|
+
* engine's loopback. The page observes registrations by wrapping
|
|
44
|
+
* `registerServer` and reading `server.handleRequest`; a `null` server there
|
|
45
|
+
* threw, tcp_wrap swallowed it, and a listen on `127.0.0.1` never opened
|
|
46
|
+
* while one with no host (openvscode-server's) could still reach the page
|
|
47
|
+
* through a wrap that did not bind `handleRequest`.
|
|
48
|
+
*/
|
|
49
|
+
function guestListeningServer(port: number, address: string): IVirtualServer {
|
|
50
|
+
const hostname = address || '0.0.0.0';
|
|
51
|
+
return {
|
|
52
|
+
listening: true,
|
|
53
|
+
address: () => ({
|
|
54
|
+
port,
|
|
55
|
+
address: hostname,
|
|
56
|
+
family: hostname.includes(':') ? 'IPv6' : 'IPv4',
|
|
57
|
+
}),
|
|
58
|
+
handleRequest: (method, url, headers, body) =>
|
|
59
|
+
__requestOverLoopback(port, method, url, headers, typeof body === 'string' ? Buffer.from(body) : body),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface VirtualServer {
|
|
64
|
+
/**
|
|
65
|
+
* A server the host registered and answers itself, or null for a guest's
|
|
66
|
+
* own: a guest's server is a port this engine is listening on, and it is
|
|
67
|
+
* reached by connecting to it like any other client.
|
|
68
|
+
*/
|
|
69
|
+
server: IVirtualServer | null;
|
|
70
|
+
port: number;
|
|
71
|
+
hostname: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface BridgeOptions {
|
|
75
|
+
baseUrl?: string;
|
|
76
|
+
onServerReady?: (port: number, url: string) => void;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface InitServiceWorkerOptions {
|
|
80
|
+
/**
|
|
81
|
+
* The URL path to the service worker file
|
|
82
|
+
* @default '/__sw__.js'
|
|
83
|
+
*/
|
|
84
|
+
swUrl?: string;
|
|
85
|
+
/**
|
|
86
|
+
* The page's own documents that are frames of it (a sandbox, a worker
|
|
87
|
+
* host), by path, so the worker does not take one for the preview's.
|
|
88
|
+
*/
|
|
89
|
+
ownDocuments?: string[];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Server Bridge manages virtual HTTP servers and routes requests
|
|
94
|
+
*/
|
|
95
|
+
/**
|
|
96
|
+
* A virtual WebSocket to a port whose server is an HTTP server with an
|
|
97
|
+
* `upgrade` listener, and no `ws` shim server of its own, reaches that listener
|
|
98
|
+
* as Node delivers it: an `upgrade` event with the request and a duplex socket,
|
|
99
|
+
* on which the program's own WebSocket library — `ws` bundled inside Vite among
|
|
100
|
+
* them — writes its 101 response and then frames. The bridge speaks RFC 6455 on
|
|
101
|
+
* the socket's other end: the library's frames become the client's messages,
|
|
102
|
+
* the client's messages become masked frames the library reads, a close either
|
|
103
|
+
* way closes both.
|
|
104
|
+
*/
|
|
105
|
+
/** One client's place in the bridge: who it is, which port, the channel it speaks on. */
|
|
106
|
+
interface UpgradeEntry {
|
|
107
|
+
clientId: string;
|
|
108
|
+
port: number;
|
|
109
|
+
channel: BroadcastChannel;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** A partly read frame stream: bytes not yet a whole frame, and a fragmented message in progress. */
|
|
113
|
+
interface FrameState {
|
|
114
|
+
pending: Uint8Array;
|
|
115
|
+
fragments: { opcode: number; parts: Uint8Array[] } | null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** What a virtual WebSocket client sends over the shared channel. */
|
|
119
|
+
interface UpgradeSignal {
|
|
120
|
+
type: string;
|
|
121
|
+
clientId: string;
|
|
122
|
+
url?: string;
|
|
123
|
+
protocols?: string[];
|
|
124
|
+
/** `"none"` asks for a byte pipe; anything else, or nothing, is a WebSocket. */
|
|
125
|
+
framing?: "websocket" | "none";
|
|
126
|
+
payload?: string | ArrayBuffer;
|
|
127
|
+
code?: number;
|
|
128
|
+
reason?: string;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** What a server must answer for a virtual connect to reach it as an upgrade. */
|
|
132
|
+
interface UpgradeTarget {
|
|
133
|
+
emit(event: string, ...args: unknown[]): boolean;
|
|
134
|
+
listenerCount(event: string): number;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** The end of an upgraded socket the bridge drives, whichever socket the listener was handed. */
|
|
138
|
+
interface UpgradeEnd {
|
|
139
|
+
deliver(payload: string | ArrayBuffer): void;
|
|
140
|
+
disconnect(code: number, reason: string): void;
|
|
141
|
+
destroy(): void;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* The duplex socket an `upgrade` listener is handed: the connection's own
|
|
146
|
+
* `net.Socket`, which is what Node hands one, plus the two calls the bridge
|
|
147
|
+
* drives its own end with, `deliver` and `disconnect`, and the two a Duplex
|
|
148
|
+
* here does not inherit -- `cork`/`uncork` are the Writable base's, Node's
|
|
149
|
+
* socket has them, and `ws` corks a two-part frame before it writes it.
|
|
150
|
+
*/
|
|
151
|
+
type UpgradeSocket = Socket & UpgradeEnd & { cork(): void; uncork(): void };
|
|
152
|
+
|
|
153
|
+
// RFC 6455 on the bridge's side of an upgraded socket: what the server's
|
|
154
|
+
// library writes is parsed into messages for the client, what the client
|
|
155
|
+
// sends is framed, masked as a client must, for the library to read.
|
|
156
|
+
const __substrateUpgradeSockets = new Map<string, UpgradeEnd>();
|
|
157
|
+
function __substrateFrame(opcode: number, payload: Uint8Array, masked: boolean): Uint8Array {
|
|
158
|
+
const length = payload.length;
|
|
159
|
+
const header = [0x80 | opcode];
|
|
160
|
+
if (length < 126) header.push((masked ? 0x80 : 0) | length);
|
|
161
|
+
else if (length < 65536) header.push((masked ? 0x80 : 0) | 126, length >> 8, length & 255);
|
|
162
|
+
else header.push((masked ? 0x80 : 0) | 127, 0, 0, 0, 0, (length >>> 24) & 255, (length >>> 16) & 255, (length >>> 8) & 255, length & 255);
|
|
163
|
+
const mask = masked ? [Math.random() * 256 | 0, Math.random() * 256 | 0, Math.random() * 256 | 0, Math.random() * 256 | 0] : [];
|
|
164
|
+
const out = new Uint8Array(header.length + mask.length + length);
|
|
165
|
+
out.set(header, 0);
|
|
166
|
+
out.set(mask, header.length);
|
|
167
|
+
for (let index = 0; index < length; index += 1) out[header.length + mask.length + index] = masked ? payload[index] ^ mask[index & 3] : payload[index];
|
|
168
|
+
return out;
|
|
169
|
+
}
|
|
170
|
+
function __substrateParseFrames(state: FrameState, chunk: Uint8Array, onFrame: (opcode: number, payload: Uint8Array) => void): void {
|
|
171
|
+
const joined = state.pending.length ? new Uint8Array([...state.pending, ...chunk]) : chunk;
|
|
172
|
+
let offset = 0;
|
|
173
|
+
for (;;) {
|
|
174
|
+
if (joined.length - offset < 2) break;
|
|
175
|
+
const b0 = joined[offset], b1 = joined[offset + 1];
|
|
176
|
+
const fin = (b0 & 0x80) !== 0, opcode = b0 & 0x0f, masked = (b1 & 0x80) !== 0;
|
|
177
|
+
let length = b1 & 0x7f, at = offset + 2;
|
|
178
|
+
if (length === 126) { if (joined.length - at < 2) break; length = (joined[at] << 8) | joined[at + 1]; at += 2; }
|
|
179
|
+
else if (length === 127) { if (joined.length - at < 8) break; length = 0; for (let index = 0; index < 8; index += 1) length = length * 256 + joined[at + index]; at += 8; }
|
|
180
|
+
let mask: Uint8Array | undefined;
|
|
181
|
+
if (masked) { if (joined.length - at < 4) break; mask = joined.subarray(at, at + 4); at += 4; }
|
|
182
|
+
if (joined.length - at < length) break;
|
|
183
|
+
const payload = joined.slice(at, at + length);
|
|
184
|
+
if (mask) for (let index = 0; index < length; index += 1) payload[index] ^= mask[index & 3];
|
|
185
|
+
offset = at + length;
|
|
186
|
+
if (opcode === 0 && state.fragments) { state.fragments.parts.push(payload); if (fin) { const parts = state.fragments.parts; const total = parts.reduce((sum, part) => sum + part.length, 0); const whole = new Uint8Array(total); let cursor = 0; for (const part of parts) { whole.set(part, cursor); cursor += part.length; } const op = state.fragments.opcode; state.fragments = null; onFrame(op, whole); } }
|
|
187
|
+
else if (!fin && (opcode === 1 || opcode === 2)) state.fragments = { opcode, parts: [payload] };
|
|
188
|
+
else onFrame(opcode, payload);
|
|
189
|
+
}
|
|
190
|
+
state.pending = joined.slice(offset);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* The socket an `upgrade` listener is handed for a connect that carries
|
|
194
|
+
* WebSocket frames: Node's own `net.Socket`, over a handle whose peer is the
|
|
195
|
+
* bridge.
|
|
196
|
+
*
|
|
197
|
+
* Node hands an `upgrade` listener the connection's `net.Socket`, and a
|
|
198
|
+
* program does everything a socket can with it. The engine used to hand it an
|
|
199
|
+
* object with `write`, `end` and `destroy` overridden on a socket-shaped
|
|
200
|
+
* class, so pause, resume, pipe, `setEncoding` and `bufferSize` were either
|
|
201
|
+
* wrong or absent -- VS Code's `NodeSocket.drain` resolves on
|
|
202
|
+
* `bufferSize === 0` and otherwise waits for a `drain` event, and waited
|
|
203
|
+
* forever, which is what openvscode-server does before handing its extension
|
|
204
|
+
* host the workbench's connection.
|
|
205
|
+
*
|
|
206
|
+
* So the bridge is a handle, not a fake socket: the listener's end is a
|
|
207
|
+
* `net.Socket` of Node's own making and the bridge holds the other end of the
|
|
208
|
+
* pairing. What the library writes arrives as bytes -- read first as the
|
|
209
|
+
* `101` head and then as RFC 6455 -- and what the client says is framed,
|
|
210
|
+
* masked as a client must, and pushed in as the connection's own inbound
|
|
211
|
+
* bytes.
|
|
212
|
+
*/
|
|
213
|
+
function __substrateUpgradeSocket(entry: UpgradeEntry, socket: Socket): UpgradeSocket {
|
|
214
|
+
// The connection is already made and its `101` already read: this is the
|
|
215
|
+
// client's own end of a connection the guest's server accepted, so the
|
|
216
|
+
// bytes on it from here are the protocol's.
|
|
217
|
+
const end = socket as UpgradeSocket;
|
|
218
|
+
const bridge = {
|
|
219
|
+
push: (frame: Uint8Array): void => { socket.write(frame); },
|
|
220
|
+
destroy: (): void => { socket.destroy(); },
|
|
221
|
+
get destroyed(): boolean { return socket.destroyed; },
|
|
222
|
+
onData: (listener: (chunk: Uint8Array) => void): void => { socket.on("data", listener); },
|
|
223
|
+
};
|
|
224
|
+
let closed = false;
|
|
225
|
+
const headed = true;
|
|
226
|
+
const state: FrameState = { pending: new Uint8Array(0), fragments: null };
|
|
227
|
+
const decoder = new TextDecoder();
|
|
228
|
+
const finish = (code: number, reason: string): void => {
|
|
229
|
+
if (closed) return;
|
|
230
|
+
closed = true;
|
|
231
|
+
__substrateUpgradeSockets.delete(entry.clientId);
|
|
232
|
+
entry.channel.postMessage({ type: "close", targetClient: entry.clientId, code, reason });
|
|
233
|
+
};
|
|
234
|
+
/** What the client says, framed for the server's library, as the socket's own inbound bytes. */
|
|
235
|
+
const receive = (frame: Uint8Array): void => { if (!bridge.destroyed) bridge.push(frame); };
|
|
236
|
+
bridge.onData((chunk) => {
|
|
237
|
+
const bytes = chunk;
|
|
238
|
+
if (!headed) return;
|
|
239
|
+
__substrateParseFrames(state, bytes, (opcode, payload) => {
|
|
240
|
+
if (opcode === 1) entry.channel.postMessage({ type: "message", targetClient: entry.clientId, payload: decoder.decode(payload) });
|
|
241
|
+
else if (opcode === 2) entry.channel.postMessage({ type: "message", targetClient: entry.clientId, payload: payload.buffer.slice(payload.byteOffset, payload.byteOffset + payload.byteLength) });
|
|
242
|
+
else if (opcode === 8) { const code = payload.length >= 2 ? (payload[0] << 8) | payload[1] : 1005; finish(code === 1005 ? 1000 : code, payload.length > 2 ? decoder.decode(payload.subarray(2)) : ""); end.destroy(); }
|
|
243
|
+
else if (opcode === 9) receive(__substrateFrame(10, payload, true));
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
// The listener's own end going is the connection going: the bridge's client
|
|
247
|
+
// is told, once, whichever side ended it.
|
|
248
|
+
end.on("close", () => { finish(1000, ""); bridge.destroy(); });
|
|
249
|
+
end.deliver = (payload: string | ArrayBuffer) => {
|
|
250
|
+
if (closed || end.destroyed) return;
|
|
251
|
+
const bytes = typeof payload === "string" ? _encoder.encode(payload) : new Uint8Array(payload);
|
|
252
|
+
receive(__substrateFrame(typeof payload === "string" ? 1 : 2, bytes, true));
|
|
253
|
+
};
|
|
254
|
+
end.disconnect = (code: number, reason: string) => {
|
|
255
|
+
if (closed || end.destroyed) return;
|
|
256
|
+
const text = _encoder.encode(reason || "");
|
|
257
|
+
const payload = new Uint8Array(2 + text.length); payload[0] = code >> 8; payload[1] = code & 255; payload.set(text, 2);
|
|
258
|
+
receive(__substrateFrame(8, payload, true));
|
|
259
|
+
// The library answers a close frame with its own, and that write ends this
|
|
260
|
+
// end through the reader above; a library that does not answer ends here.
|
|
261
|
+
setTimeout(() => { finish(code, reason || ""); end.destroy(); }, 50);
|
|
262
|
+
};
|
|
263
|
+
return end;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* The socket an `upgrade` listener is handed for a connect that carries no
|
|
267
|
+
* WebSocket frames: the same `net.Socket` over the same pairing, with the
|
|
268
|
+
* bridge speaking bytes rather than frames on its end.
|
|
269
|
+
*
|
|
270
|
+
* vscode-server's `RemoteExtensionHostAgentServer` answers its own upgrade. It
|
|
271
|
+
* writes `HTTP/1.1 101 Switching Protocols` with `socket.write` and then reads
|
|
272
|
+
* and writes its remote protocol on that same socket as bytes, because its
|
|
273
|
+
* client asked for no framing and the server frames nothing either way. The
|
|
274
|
+
* engine handed it the bridge's RFC 6455 socket, which read the server's
|
|
275
|
+
* protocol as frames and found none, so the server's answer went nowhere and
|
|
276
|
+
* the client's connect never opened. Here every write is one message to the
|
|
277
|
+
* client, every message from the client is data on the socket, and the head
|
|
278
|
+
* the server writes is the client's to strip.
|
|
279
|
+
*/
|
|
280
|
+
function __substrateRawUpgradeSocket(entry: UpgradeEntry, socket: Socket): Socket & UpgradeEnd {
|
|
281
|
+
const end = socket as Socket & UpgradeEnd;
|
|
282
|
+
const bridge = {
|
|
283
|
+
push: (data: Uint8Array): void => { socket.write(data); },
|
|
284
|
+
destroy: (): void => { socket.destroy(); },
|
|
285
|
+
end: (): void => { socket.end(); },
|
|
286
|
+
get destroyed(): boolean { return socket.destroyed; },
|
|
287
|
+
onData: (listener: (chunk: Uint8Array) => void): void => { socket.on("data", listener); },
|
|
288
|
+
};
|
|
289
|
+
let closed = false;
|
|
290
|
+
const finish = (code: number, reason: string): void => {
|
|
291
|
+
if (closed) return;
|
|
292
|
+
closed = true;
|
|
293
|
+
__substrateUpgradeSockets.delete(entry.clientId);
|
|
294
|
+
entry.channel.postMessage({ type: "close", targetClient: entry.clientId, code, reason });
|
|
295
|
+
};
|
|
296
|
+
bridge.onData((chunk) => {
|
|
297
|
+
if (closed) return;
|
|
298
|
+
entry.channel.postMessage({ type: "message", targetClient: entry.clientId, payload: chunk.buffer.slice(chunk.byteOffset, chunk.byteOffset + chunk.byteLength) });
|
|
299
|
+
});
|
|
300
|
+
end.on("close", () => { finish(1000, ""); bridge.destroy(); });
|
|
301
|
+
end.deliver = (payload: string | ArrayBuffer) => {
|
|
302
|
+
if (closed || end.destroyed || bridge.destroyed) return;
|
|
303
|
+
bridge.push(typeof payload === "string" ? _encoder.encode(payload) : new Uint8Array(payload));
|
|
304
|
+
};
|
|
305
|
+
end.disconnect = (code: number, reason: string) => {
|
|
306
|
+
if (closed || end.destroyed) return;
|
|
307
|
+
bridge.end();
|
|
308
|
+
finish(code, reason || "");
|
|
309
|
+
end.destroy();
|
|
310
|
+
};
|
|
311
|
+
return end;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* The channel a virtual websocket's connect arrives on, opened for the guest
|
|
315
|
+
* whose servers this bridge holds. It is the bridge's to hold and the
|
|
316
|
+
* bridge's to give back: a subscribed `BroadcastChannel` is a `MessagePort`
|
|
317
|
+
* the host's loop counts, and this one was opened in a closure and never
|
|
318
|
+
* stored, so a Node process that used the engine — `createContainer()` and
|
|
319
|
+
* nothing more — never reached `beforeExit`; the substrate's own
|
|
320
|
+
* `verify:pnpm-workspace` had to run with `--test-force-exit` for it. It is
|
|
321
|
+
* unreferenced where the host counts handles against exit (a browser's
|
|
322
|
+
* `BroadcastChannel` has no `unref` and holds nothing open), and `close()`
|
|
323
|
+
* gives it up altogether. The rule this restores is the one the fork already
|
|
324
|
+
* states for import, carried on to use: a host resource the engine opens for
|
|
325
|
+
* a guest must not outlive the guest, and must never be the reason someone
|
|
326
|
+
* else's process stays alive.
|
|
327
|
+
*/
|
|
328
|
+
function __substrateListenForUpgrades(bridge: ServerBridge): BroadcastChannel | null {
|
|
329
|
+
if (typeof BroadcastChannel === "undefined") return null;
|
|
330
|
+
const channel = new BroadcastChannel("vite-ws-channel");
|
|
331
|
+
(channel as unknown as { unref?: () => void }).unref?.();
|
|
332
|
+
channel.addEventListener("message", (event) => {
|
|
333
|
+
const data2 = event.data as UpgradeSignal;
|
|
334
|
+
if (!data2 || typeof data2 !== "object") return;
|
|
335
|
+
if (data2.type === "connect") {
|
|
336
|
+
let url2: URL;
|
|
337
|
+
try { url2 = new URL(data2.url!); } catch { return; }
|
|
338
|
+
const port = Number(url2.port);
|
|
339
|
+
if (!__listening(port)) return;
|
|
340
|
+
const record = { clientId: data2.clientId, port, channel };
|
|
341
|
+
// A client that asked for no frames is a byte pipe; anything else is a
|
|
342
|
+
// WebSocket, and the bridge speaks RFC 6455 on its own end of it.
|
|
343
|
+
const raw = data2.framing === "none";
|
|
344
|
+
const key = uint8ToBase64(crypto.getRandomValues(new Uint8Array(16)));
|
|
345
|
+
const protocols = Array.isArray(data2.protocols) ? data2.protocols : [];
|
|
346
|
+
// The handshake is a request on a real connection, and the guest's
|
|
347
|
+
// server answers it with its own code: Node's `http` emits `upgrade`
|
|
348
|
+
// with the socket, which is what the real `ws` takes over. What comes
|
|
349
|
+
// back here is the client's end of that same connection.
|
|
350
|
+
void __upgradeOverLoopback(port, "GET", url2.pathname + url2.search, {
|
|
351
|
+
Host: url2.host,
|
|
352
|
+
Upgrade: "websocket",
|
|
353
|
+
"Sec-WebSocket-Version": "13",
|
|
354
|
+
"Sec-WebSocket-Key": key,
|
|
355
|
+
...(protocols.length ? { "Sec-WebSocket-Protocol": protocols.join(", ") } : {}),
|
|
356
|
+
}).then((result) => {
|
|
357
|
+
if (result.statusCode !== 101 || !result.socket) {
|
|
358
|
+
channel.postMessage({ type: "close", targetClient: data2.clientId, code: 1006, reason: "upgrade refused" });
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
const socket = raw
|
|
362
|
+
? __substrateRawUpgradeSocket(record, result.socket)
|
|
363
|
+
: __substrateUpgradeSocket(record, result.socket);
|
|
364
|
+
__substrateUpgradeSockets.set(data2.clientId, socket);
|
|
365
|
+
channel.postMessage({
|
|
366
|
+
type: "connected",
|
|
367
|
+
targetClient: data2.clientId,
|
|
368
|
+
protocol: result.headers["sec-websocket-protocol"] ?? (raw ? protocols[0] ?? "" : ""),
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
} else if (data2.type === "message") {
|
|
372
|
+
const socket = __substrateUpgradeSockets.get(data2.clientId);
|
|
373
|
+
if (socket) socket.deliver(data2.payload!);
|
|
374
|
+
} else if (data2.type === "disconnect") {
|
|
375
|
+
const socket = __substrateUpgradeSockets.get(data2.clientId);
|
|
376
|
+
if (socket) socket.disconnect(typeof data2.code === "number" ? data2.code : 1000, typeof data2.reason === "string" ? data2.reason : "");
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
return channel;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export class ServerBridge extends EventEmitter {
|
|
383
|
+
static DEBUG = false;
|
|
384
|
+
servers: Map<number, VirtualServer> = new Map();
|
|
385
|
+
private baseUrl: string;
|
|
386
|
+
private options: BridgeOptions;
|
|
387
|
+
private messageChannel: MessageChannel | null = null;
|
|
388
|
+
/** The upgrade channel this bridge opened, held so it can be given back. */
|
|
389
|
+
private upgradeChannel: BroadcastChannel | null = null;
|
|
390
|
+
private serviceWorkerReady: boolean = false;
|
|
391
|
+
private keepaliveInterval: ReturnType<typeof setInterval> | null = null;
|
|
392
|
+
|
|
393
|
+
constructor(options: BridgeOptions = {}) {
|
|
394
|
+
super();
|
|
395
|
+
this.options = options;
|
|
396
|
+
|
|
397
|
+
// Handle browser vs Node.js environment
|
|
398
|
+
if (typeof location !== 'undefined') {
|
|
399
|
+
this.baseUrl = options.baseUrl || `${location.protocol}//${location.host}`;
|
|
400
|
+
} else {
|
|
401
|
+
this.baseUrl = options.baseUrl || 'http://localhost';
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// A guest's servers register themselves, from the port they take.
|
|
405
|
+
this.watchGuestPorts();
|
|
406
|
+
this.upgradeChannel = __substrateListenForUpgrades(this);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Give back everything this bridge opened: the upgrade channel and the
|
|
411
|
+
* service worker keepalive. A host that is done with a container calls it
|
|
412
|
+
* and has its process back; calling it twice is nothing.
|
|
413
|
+
*/
|
|
414
|
+
/**
|
|
415
|
+
* Every port a guest starts listening on becomes a server this bridge can
|
|
416
|
+
* answer for, and every port it stops listening on stops being one. The
|
|
417
|
+
* news comes from the net binding, because that is where a port is taken;
|
|
418
|
+
* the engine's `http` is Node's own file and knows nothing of a page.
|
|
419
|
+
*/
|
|
420
|
+
private watchGuestPorts(): void {
|
|
421
|
+
setPortWatchers(
|
|
422
|
+
(port, address) => {
|
|
423
|
+
if (this.servers.has(port)) return;
|
|
424
|
+
const hostname = address || '0.0.0.0';
|
|
425
|
+
this.registerServer(guestListeningServer(port, hostname), port, hostname);
|
|
426
|
+
},
|
|
427
|
+
(port) => {
|
|
428
|
+
const entry = this.servers.get(port);
|
|
429
|
+
if (entry && entry.server === null) this.unregisterServer(port);
|
|
430
|
+
},
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
close(): void {
|
|
435
|
+
if (this.upgradeChannel) {
|
|
436
|
+
try { this.upgradeChannel.close(); } catch { /* a channel the realm already closed is closed */ }
|
|
437
|
+
this.upgradeChannel = null;
|
|
438
|
+
}
|
|
439
|
+
if (this.keepaliveInterval !== null) {
|
|
440
|
+
clearInterval(this.keepaliveInterval);
|
|
441
|
+
this.keepaliveInterval = null;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Register a server on a port
|
|
447
|
+
*/
|
|
448
|
+
registerServer(server: IVirtualServer | null, port: number, hostname: string = '0.0.0.0'): void {
|
|
449
|
+
this.servers.set(port, { server, port, hostname });
|
|
450
|
+
|
|
451
|
+
// Emit server-ready event
|
|
452
|
+
const url = this.getServerUrl(port);
|
|
453
|
+
this.emit('server-ready', port, url);
|
|
454
|
+
|
|
455
|
+
if (this.options.onServerReady) {
|
|
456
|
+
this.options.onServerReady(port, url);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Notify service worker if connected
|
|
460
|
+
this.notifyServiceWorker('server-registered', { port, hostname, primary: port === this.primaryPort });
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Unregister a server
|
|
465
|
+
*/
|
|
466
|
+
unregisterServer(port: number): void {
|
|
467
|
+
this.servers.delete(port);
|
|
468
|
+
if (this.primaryPort === port) this.primaryPort = null;
|
|
469
|
+
this.notifyServiceWorker('server-unregistered', { port });
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/** The server the service worker answers at the origin's root, the preview's; null for none. */
|
|
473
|
+
private primaryPort: number | null = null;
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Names the server the service worker serves at the origin's root, as an
|
|
477
|
+
* app is served at its own origin's root: its documents read their routes
|
|
478
|
+
* from `location.pathname`, which under /__virtual__/<port>/ is not the
|
|
479
|
+
* path they expect. Told to the worker now and again whenever the worker
|
|
480
|
+
* is (re)initialized, since a worker that restarted remembers nothing.
|
|
481
|
+
*/
|
|
482
|
+
setPrimaryPort(port: number | null): void {
|
|
483
|
+
const previous = this.primaryPort;
|
|
484
|
+
this.primaryPort = port;
|
|
485
|
+
if (previous !== null && previous !== port) {
|
|
486
|
+
const entry = this.servers.get(previous);
|
|
487
|
+
if (entry) this.notifyServiceWorker('server-registered', { port: previous, hostname: entry.hostname, primary: false });
|
|
488
|
+
}
|
|
489
|
+
if (port !== null) {
|
|
490
|
+
const entry = this.servers.get(port);
|
|
491
|
+
this.notifyServiceWorker('server-registered', { port, hostname: entry?.hostname ?? '0.0.0.0', primary: true });
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/** Every registration the worker should hold, sent again to a worker that was (re)initialized. */
|
|
496
|
+
private announceServers(): void {
|
|
497
|
+
if (this.ownDocuments) this.notifyServiceWorker('own-documents', { paths: this.ownDocuments });
|
|
498
|
+
for (const [port, entry] of this.servers) {
|
|
499
|
+
this.notifyServiceWorker('server-registered', { port, hostname: entry.hostname, primary: port === this.primaryPort });
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
private ownDocuments: string[] | undefined;
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Get server URL for a port
|
|
506
|
+
*/
|
|
507
|
+
getServerUrl(port: number): string {
|
|
508
|
+
return `${this.baseUrl}/__virtual__/${port}`;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Get all registered server ports
|
|
513
|
+
*/
|
|
514
|
+
getServerPorts(): number[] {
|
|
515
|
+
return [...this.servers.keys()];
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Handle an incoming request from Service Worker
|
|
520
|
+
*/
|
|
521
|
+
async handleRequest(
|
|
522
|
+
port: number,
|
|
523
|
+
method: string,
|
|
524
|
+
url: string,
|
|
525
|
+
headers: Record<string, string>,
|
|
526
|
+
body?: ArrayBuffer
|
|
527
|
+
): Promise<ResponseData> {
|
|
528
|
+
const virtualServer = this.servers.get(port);
|
|
529
|
+
|
|
530
|
+
if (!virtualServer) {
|
|
531
|
+
return {
|
|
532
|
+
statusCode: 503,
|
|
533
|
+
statusMessage: 'Service Unavailable',
|
|
534
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
535
|
+
body: ownedBytes(Buffer.from(`No server listening on port ${port}`)),
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
try {
|
|
540
|
+
const bodyBuffer = body ? Buffer.from(new Uint8Array(body)) : undefined;
|
|
541
|
+
// Every request a server sees carries a Host. HTTP/1.1 requires one and
|
|
542
|
+
// Node's own parser answers 400 without it, so a server is written as
|
|
543
|
+
// though it is always there: Next's middleware reads
|
|
544
|
+
// `req.headers.get('host')` on its first line and Dub's died on null.
|
|
545
|
+
// A browser's `fetch` may not send the header and a service worker may
|
|
546
|
+
// not add it, so the request that arrives here often has none; the
|
|
547
|
+
// server is listening on a port, and that is the authority it is
|
|
548
|
+
// reachable at.
|
|
549
|
+
const named = Object.keys(headers).some((name) => name.toLowerCase() === 'host');
|
|
550
|
+
const authority = `${virtualServer.hostname && virtualServer.hostname !== '0.0.0.0' && virtualServer.hostname !== '::' ? virtualServer.hostname : '127.0.0.1'}:${port}`;
|
|
551
|
+
const withHost = named ? headers : { ...headers, host: authority };
|
|
552
|
+
// A server the host registered answers for itself; a guest's server is
|
|
553
|
+
// a port this engine is listening on, and the request goes to it as
|
|
554
|
+
// bytes on a connection, which is what a server reads.
|
|
555
|
+
if (virtualServer.server) return await virtualServer.server.handleRequest(method, url, withHost, bodyBuffer);
|
|
556
|
+
return await __requestOverLoopback(port, method, url, withHost, bodyBuffer);
|
|
557
|
+
} catch (error) {
|
|
558
|
+
const message = error instanceof Error ? error.message : 'Internal Server Error';
|
|
559
|
+
return {
|
|
560
|
+
statusCode: 500,
|
|
561
|
+
statusMessage: 'Internal Server Error',
|
|
562
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
563
|
+
body: ownedBytes(Buffer.from(message)),
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Initialize Service Worker communication
|
|
570
|
+
* @param options - Configuration options for the service worker
|
|
571
|
+
* @param options.swUrl - Custom URL path to the service worker file (default: '/__sw__.js')
|
|
572
|
+
*/
|
|
573
|
+
async initServiceWorker(options?: InitServiceWorkerOptions): Promise<void> {
|
|
574
|
+
if (!('serviceWorker' in navigator)) {
|
|
575
|
+
throw new Error('Service Workers not supported');
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const swUrl = options?.swUrl ?? '/__sw__.js';
|
|
579
|
+
|
|
580
|
+
// Set up controllerchange listener BEFORE registration so we don't miss the event.
|
|
581
|
+
// clients.claim() in the SW's activate handler fires controllerchange, and it can
|
|
582
|
+
// happen before our activation wait completes.
|
|
583
|
+
const controllerReady = navigator.serviceWorker.controller
|
|
584
|
+
? Promise.resolve()
|
|
585
|
+
: new Promise<void>((resolve) => {
|
|
586
|
+
navigator.serviceWorker.addEventListener('controllerchange', () => resolve(), { once: true });
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
// Register service worker
|
|
590
|
+
const registration = await navigator.serviceWorker.register(swUrl, {
|
|
591
|
+
scope: '/',
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
// Wait for service worker to be active
|
|
595
|
+
const sw = registration.active || registration.waiting || registration.installing;
|
|
596
|
+
|
|
597
|
+
if (!sw) {
|
|
598
|
+
throw new Error('Service Worker registration failed');
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
await new Promise<void>((resolve) => {
|
|
602
|
+
if (sw.state === 'activated') {
|
|
603
|
+
resolve();
|
|
604
|
+
} else {
|
|
605
|
+
const handler = () => {
|
|
606
|
+
if (sw.state === 'activated') {
|
|
607
|
+
sw.removeEventListener('statechange', handler);
|
|
608
|
+
resolve();
|
|
609
|
+
}
|
|
610
|
+
};
|
|
611
|
+
sw.addEventListener('statechange', handler);
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
// Set up message channel for communication
|
|
616
|
+
this.messageChannel = new MessageChannel();
|
|
617
|
+
this.messageChannel.port1.onmessage = this.handleServiceWorkerMessage.bind(this);
|
|
618
|
+
|
|
619
|
+
this.ownDocuments = options?.ownDocuments;
|
|
620
|
+
// Send port to service worker
|
|
621
|
+
sw.postMessage({ type: 'init', port: this.messageChannel.port2, data: { ownDocuments: this.ownDocuments ?? [] } }, [
|
|
622
|
+
this.messageChannel.port2,
|
|
623
|
+
]);
|
|
624
|
+
|
|
625
|
+
// Wait for SW to actually control this page (clients.claim() in SW activate handler)
|
|
626
|
+
// Without this, fetch requests bypass the SW and go directly to the server
|
|
627
|
+
await controllerReady;
|
|
628
|
+
|
|
629
|
+
// Re-establish communication when the SW loses its port (idle termination)
|
|
630
|
+
// or when the SW is replaced (new deployment). The SW sends 'sw-needs-init'
|
|
631
|
+
// to all clients when a request arrives but mainPort is null.
|
|
632
|
+
const reinit = () => {
|
|
633
|
+
if (navigator.serviceWorker.controller) {
|
|
634
|
+
this.messageChannel = new MessageChannel();
|
|
635
|
+
this.messageChannel.port1.onmessage = this.handleServiceWorkerMessage.bind(this);
|
|
636
|
+
navigator.serviceWorker.controller.postMessage(
|
|
637
|
+
{ type: 'init', port: this.messageChannel.port2, data: { ownDocuments: this.ownDocuments ?? [] } },
|
|
638
|
+
[this.messageChannel.port2]
|
|
639
|
+
);
|
|
640
|
+
this.announceServers();
|
|
641
|
+
}
|
|
642
|
+
};
|
|
643
|
+
navigator.serviceWorker.addEventListener('controllerchange', reinit);
|
|
644
|
+
navigator.serviceWorker.addEventListener('message', (event) => {
|
|
645
|
+
if (event.data?.type === 'sw-needs-init') {
|
|
646
|
+
reinit();
|
|
647
|
+
}
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
// Keep the SW alive with periodic pings. Browsers terminate idle SWs
|
|
651
|
+
// after ~30s, losing the MessageChannel port and all in-memory state.
|
|
652
|
+
this.keepaliveInterval = setInterval(() => {
|
|
653
|
+
this.messageChannel?.port1.postMessage({ type: 'keepalive' });
|
|
654
|
+
}, 20_000);
|
|
655
|
+
|
|
656
|
+
this.serviceWorkerReady = true;
|
|
657
|
+
this.announceServers();
|
|
658
|
+
this.emit('sw-ready');
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Handle messages from Service Worker
|
|
663
|
+
*/
|
|
664
|
+
private async handleServiceWorkerMessage(event: MessageEvent): Promise<void> {
|
|
665
|
+
const { type, id, data } = event.data;
|
|
666
|
+
|
|
667
|
+
ServerBridge.DEBUG && console.log('[ServerBridge] SW message:', type, id, data?.url);
|
|
668
|
+
|
|
669
|
+
if (type === 'request') {
|
|
670
|
+
const { port, method, url, headers, body, streaming } = data;
|
|
671
|
+
|
|
672
|
+
ServerBridge.DEBUG && console.log('[ServerBridge] Handling request:', port, method, url, 'streaming:', streaming);
|
|
673
|
+
if (streaming) {
|
|
674
|
+
ServerBridge.DEBUG && console.log('[ServerBridge] 🔴 Will use streaming handler');
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
try {
|
|
678
|
+
if (streaming) {
|
|
679
|
+
// Handle streaming request
|
|
680
|
+
await this.streamToServiceWorker(id, port, method, url, headers, body);
|
|
681
|
+
} else {
|
|
682
|
+
// Handle regular request
|
|
683
|
+
const response = await this.handleRequest(port, method, url, headers, body);
|
|
684
|
+
ServerBridge.DEBUG && console.log('[ServerBridge] Response:', response.statusCode, 'body length:', response.body?.length);
|
|
685
|
+
|
|
686
|
+
// Convert body to base64 string to avoid structured cloning issues with Uint8Array
|
|
687
|
+
let bodyBase64 = '';
|
|
688
|
+
if (response.body && response.body.length > 0) {
|
|
689
|
+
const bytes = response.body instanceof Uint8Array ? response.body : new Uint8Array(0);
|
|
690
|
+
bodyBase64 = uint8ToBase64(bytes);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
ServerBridge.DEBUG && console.log('[ServerBridge] Sending response to SW, body base64 length:', bodyBase64.length);
|
|
694
|
+
|
|
695
|
+
this.messageChannel?.port1.postMessage({
|
|
696
|
+
type: 'response',
|
|
697
|
+
id,
|
|
698
|
+
data: {
|
|
699
|
+
statusCode: response.statusCode,
|
|
700
|
+
statusMessage: response.statusMessage,
|
|
701
|
+
headers: response.headers,
|
|
702
|
+
bodyBase64: bodyBase64,
|
|
703
|
+
},
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
} catch (error) {
|
|
707
|
+
this.messageChannel?.port1.postMessage({
|
|
708
|
+
type: 'response',
|
|
709
|
+
id,
|
|
710
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* Handle a streaming request - sends chunks as they arrive
|
|
718
|
+
*/
|
|
719
|
+
/**
|
|
720
|
+
* A page-side request streamed to the caller as it arrives, the door a host
|
|
721
|
+
* reads a guest's server through when it wants chunks rather than a body:
|
|
722
|
+
* a guest's own server is reached over the loopback as any client reaches
|
|
723
|
+
* it; a server the host registered answers through its own streaming
|
|
724
|
+
* method where it has one, else its buffered answer is delivered whole.
|
|
725
|
+
*/
|
|
726
|
+
async handleStreamingRequest(
|
|
727
|
+
port: number,
|
|
728
|
+
method: string,
|
|
729
|
+
url: string,
|
|
730
|
+
headers: Record<string, string>,
|
|
731
|
+
body: ArrayBuffer | undefined,
|
|
732
|
+
callbacks: {
|
|
733
|
+
start(statusCode: number, statusMessage: string, headers: Record<string, string>): void;
|
|
734
|
+
chunk(chunk: Uint8Array): void;
|
|
735
|
+
end(): void;
|
|
736
|
+
},
|
|
737
|
+
): Promise<boolean> {
|
|
738
|
+
const virtualServer = this.servers.get(port);
|
|
739
|
+
if (!virtualServer) return false;
|
|
740
|
+
const bodyBuffer = body ? Buffer.from(new Uint8Array(body)) : undefined;
|
|
741
|
+
if (virtualServer.server === null) {
|
|
742
|
+
const named = Object.keys(headers).some((name) => name.toLowerCase() === 'host');
|
|
743
|
+
const authority = `${virtualServer.hostname && virtualServer.hostname !== '0.0.0.0' && virtualServer.hostname !== '::' ? virtualServer.hostname : '127.0.0.1'}:${port}`;
|
|
744
|
+
await __streamOverLoopback(
|
|
745
|
+
port, method, url, named ? headers : { ...headers, host: authority }, bodyBuffer,
|
|
746
|
+
(statusCode, statusMessage, respHeaders) => callbacks.start(statusCode, statusMessage, respHeaders),
|
|
747
|
+
(chunk) => callbacks.chunk(ownedBytes(chunk)),
|
|
748
|
+
() => callbacks.end(),
|
|
749
|
+
);
|
|
750
|
+
return true;
|
|
751
|
+
}
|
|
752
|
+
const server = virtualServer.server as IVirtualServer & {
|
|
753
|
+
handleStreamingRequest?: (
|
|
754
|
+
method: string, url: string, headers: Record<string, string>, body: Buffer | undefined,
|
|
755
|
+
onStart: (statusCode: number, statusMessage: string, headers: Record<string, string>) => void,
|
|
756
|
+
onChunk: (chunk: string | Uint8Array) => void,
|
|
757
|
+
onEnd: () => void,
|
|
758
|
+
) => Promise<void>;
|
|
759
|
+
};
|
|
760
|
+
if (typeof server.handleStreamingRequest === 'function') {
|
|
761
|
+
await server.handleStreamingRequest(
|
|
762
|
+
method, url, headers, bodyBuffer,
|
|
763
|
+
(statusCode, statusMessage, respHeaders) => callbacks.start(statusCode, statusMessage, respHeaders),
|
|
764
|
+
(chunk) => callbacks.chunk(typeof chunk === 'string' ? _encoder.encode(chunk) : ownedBytes(chunk)),
|
|
765
|
+
() => callbacks.end(),
|
|
766
|
+
);
|
|
767
|
+
return true;
|
|
768
|
+
}
|
|
769
|
+
const response = await this.handleRequest(port, method, url, headers, body);
|
|
770
|
+
callbacks.start(response.statusCode, response.statusMessage ?? '', response.headers as Record<string, string>);
|
|
771
|
+
if (response.body) {
|
|
772
|
+
const body = typeof response.body === 'string' ? _encoder.encode(response.body) : response.body;
|
|
773
|
+
callbacks.chunk(ownedBytes(body));
|
|
774
|
+
}
|
|
775
|
+
callbacks.end();
|
|
776
|
+
return true;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
private async streamToServiceWorker(
|
|
780
|
+
id: number,
|
|
781
|
+
port: number,
|
|
782
|
+
method: string,
|
|
783
|
+
url: string,
|
|
784
|
+
headers: Record<string, string>,
|
|
785
|
+
body?: ArrayBuffer
|
|
786
|
+
): Promise<void> {
|
|
787
|
+
const virtualServer = this.servers.get(port);
|
|
788
|
+
|
|
789
|
+
if (!virtualServer) {
|
|
790
|
+
this.messageChannel?.port1.postMessage({
|
|
791
|
+
type: 'stream-start',
|
|
792
|
+
id,
|
|
793
|
+
data: { statusCode: 503, statusMessage: 'Service Unavailable', headers: {} },
|
|
794
|
+
});
|
|
795
|
+
this.messageChannel?.port1.postMessage({ type: 'stream-end', id });
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// A guest's server is a port: its answer is streamed off the connection
|
|
800
|
+
// as it arrives, which is what the page reads chunk by chunk.
|
|
801
|
+
if (virtualServer.server === null) {
|
|
802
|
+
const bodyBuffer = body ? Buffer.from(new Uint8Array(body)) : undefined;
|
|
803
|
+
const named = Object.keys(headers).some((name) => name.toLowerCase() === 'host');
|
|
804
|
+
const authority = `${virtualServer.hostname && virtualServer.hostname !== '0.0.0.0' && virtualServer.hostname !== '::' ? virtualServer.hostname : '127.0.0.1'}:${port}`;
|
|
805
|
+
await __streamOverLoopback(
|
|
806
|
+
port, method, url, named ? headers : { ...headers, host: authority }, bodyBuffer,
|
|
807
|
+
(statusCode, statusMessage, respHeaders) => {
|
|
808
|
+
this.messageChannel?.port1.postMessage({ type: 'stream-start', id, data: { statusCode, statusMessage, headers: respHeaders } });
|
|
809
|
+
},
|
|
810
|
+
(chunk) => {
|
|
811
|
+
// A pooled Buffer's `.slice()` is a window on the same 8 KB; the
|
|
812
|
+
// transfer of `copy.buffer` would move the pool. These are the
|
|
813
|
+
// bytes the view names, in a buffer of their own.
|
|
814
|
+
const copy = ownedBytes(chunk);
|
|
815
|
+
this.messageChannel?.port1.postMessage({ type: 'stream-chunk', id, data: copy.buffer }, [copy.buffer]);
|
|
816
|
+
},
|
|
817
|
+
() => { this.messageChannel?.port1.postMessage({ type: 'stream-end', id }); },
|
|
818
|
+
);
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// Check if the server supports streaming (has handleStreamingRequest method)
|
|
823
|
+
const server = virtualServer.server as any;
|
|
824
|
+
if (typeof server.handleStreamingRequest === 'function') {
|
|
825
|
+
ServerBridge.DEBUG && console.log('[ServerBridge] 🟢 Server has streaming support, calling handleStreamingRequest');
|
|
826
|
+
// Use streaming handler
|
|
827
|
+
const bodyBuffer = body ? Buffer.from(new Uint8Array(body)) : undefined;
|
|
828
|
+
|
|
829
|
+
await server.handleStreamingRequest(
|
|
830
|
+
method,
|
|
831
|
+
url,
|
|
832
|
+
headers,
|
|
833
|
+
bodyBuffer,
|
|
834
|
+
// onStart - called with headers
|
|
835
|
+
(statusCode: number, statusMessage: string, respHeaders: Record<string, string>) => {
|
|
836
|
+
ServerBridge.DEBUG && console.log('[ServerBridge] 🟢 onStart called, sending stream-start');
|
|
837
|
+
this.messageChannel?.port1.postMessage({
|
|
838
|
+
type: 'stream-start',
|
|
839
|
+
id,
|
|
840
|
+
data: { statusCode, statusMessage, headers: respHeaders },
|
|
841
|
+
});
|
|
842
|
+
},
|
|
843
|
+
// onChunk - called for each chunk
|
|
844
|
+
(chunk: string | Uint8Array) => {
|
|
845
|
+
const bytes = typeof chunk === 'string' ? _encoder.encode(chunk) : chunk;
|
|
846
|
+
const chunkBase64 = uint8ToBase64(bytes);
|
|
847
|
+
ServerBridge.DEBUG && console.log('[ServerBridge] 🟡 onChunk called, sending stream-chunk, size:', chunkBase64.length);
|
|
848
|
+
this.messageChannel?.port1.postMessage({
|
|
849
|
+
type: 'stream-chunk',
|
|
850
|
+
id,
|
|
851
|
+
data: { chunkBase64 },
|
|
852
|
+
});
|
|
853
|
+
},
|
|
854
|
+
// onEnd - called when response is complete
|
|
855
|
+
() => {
|
|
856
|
+
ServerBridge.DEBUG && console.log('[ServerBridge] 🟢 onEnd called, sending stream-end');
|
|
857
|
+
this.messageChannel?.port1.postMessage({ type: 'stream-end', id });
|
|
858
|
+
}
|
|
859
|
+
);
|
|
860
|
+
} else {
|
|
861
|
+
// Fall back to regular request handling
|
|
862
|
+
const bodyBuffer = body ? Buffer.from(new Uint8Array(body)) : undefined;
|
|
863
|
+
const response = await virtualServer.server.handleRequest(method, url, headers, bodyBuffer);
|
|
864
|
+
|
|
865
|
+
// Send as a single stream
|
|
866
|
+
this.messageChannel?.port1.postMessage({
|
|
867
|
+
type: 'stream-start',
|
|
868
|
+
id,
|
|
869
|
+
data: {
|
|
870
|
+
statusCode: response.statusCode,
|
|
871
|
+
statusMessage: response.statusMessage,
|
|
872
|
+
headers: response.headers,
|
|
873
|
+
},
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
if (response.body && response.body.length > 0) {
|
|
877
|
+
const bytes = response.body instanceof Uint8Array ? response.body : new Uint8Array(0);
|
|
878
|
+
this.messageChannel?.port1.postMessage({
|
|
879
|
+
type: 'stream-chunk',
|
|
880
|
+
id,
|
|
881
|
+
data: { chunkBase64: uint8ToBase64(bytes) },
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
this.messageChannel?.port1.postMessage({ type: 'stream-end', id });
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/**
|
|
890
|
+
* Send message to Service Worker
|
|
891
|
+
*/
|
|
892
|
+
private notifyServiceWorker(type: string, data: unknown): void {
|
|
893
|
+
if (this.serviceWorkerReady && this.messageChannel) {
|
|
894
|
+
this.messageChannel.port1.postMessage({ type, data });
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/**
|
|
899
|
+
* Create a mock request handler for testing without Service Worker
|
|
900
|
+
*/
|
|
901
|
+
createFetchHandler(): (request: Request) => Promise<Response> {
|
|
902
|
+
return async (request: Request): Promise<Response> => {
|
|
903
|
+
const url = new URL(request.url);
|
|
904
|
+
|
|
905
|
+
// Check if this is a virtual server request
|
|
906
|
+
const match = url.pathname.match(/^\/__virtual__\/(\d+)(\/.*)?$/);
|
|
907
|
+
if (!match) {
|
|
908
|
+
throw new Error('Not a virtual server request');
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
const port = parseInt(match[1], 10);
|
|
912
|
+
const path = match[2] || '/';
|
|
913
|
+
|
|
914
|
+
// Build headers object
|
|
915
|
+
const headers: Record<string, string> = {};
|
|
916
|
+
request.headers.forEach((value, key) => {
|
|
917
|
+
headers[key] = value;
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
// Get body if present
|
|
921
|
+
let body: ArrayBuffer | undefined;
|
|
922
|
+
if (request.method !== 'GET' && request.method !== 'HEAD') {
|
|
923
|
+
body = await request.arrayBuffer();
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// Handle request
|
|
927
|
+
const response = await this.handleRequest(
|
|
928
|
+
port,
|
|
929
|
+
request.method,
|
|
930
|
+
path + url.search,
|
|
931
|
+
headers,
|
|
932
|
+
body
|
|
933
|
+
);
|
|
934
|
+
|
|
935
|
+
// Convert to fetch Response
|
|
936
|
+
// A header sent more than once, `set-cookie` above all, is appended as
|
|
937
|
+
// many times; a fetch Response takes each value on its own.
|
|
938
|
+
const answerHeaders = new Headers();
|
|
939
|
+
for (const [name, value] of Object.entries(response.headers)) {
|
|
940
|
+
for (const each of Array.isArray(value) ? value : [value]) answerHeaders.append(name, each);
|
|
941
|
+
}
|
|
942
|
+
return new Response(response.body as unknown as BodyInit, {
|
|
943
|
+
status: response.statusCode,
|
|
944
|
+
statusText: response.statusMessage ?? '',
|
|
945
|
+
headers: answerHeaders,
|
|
946
|
+
});
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// Global bridge instance
|
|
952
|
+
let globalBridge: ServerBridge | null = null;
|
|
953
|
+
|
|
954
|
+
/**
|
|
955
|
+
* Get or create the global server bridge
|
|
956
|
+
*/
|
|
957
|
+
export function getServerBridge(options?: BridgeOptions): ServerBridge {
|
|
958
|
+
if (!globalBridge) {
|
|
959
|
+
globalBridge = new ServerBridge(options);
|
|
960
|
+
}
|
|
961
|
+
return globalBridge;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
/**
|
|
965
|
+
* Reset the global bridge (for testing)
|
|
966
|
+
*/
|
|
967
|
+
export function resetServerBridge(): void {
|
|
968
|
+
globalBridge = null;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
export default ServerBridge;
|