@statewalker/cellb 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -25
- package/dist/adapters/wasm-sqlite.d.ts +11 -0
- package/dist/adapters/wasm-sqlite.js +26 -3
- package/dist/adapters/wasm-sqlite.js.map +1 -1
- package/dist/bindings/assets.js +2 -2
- package/dist/bindings/assets.js.map +1 -1
- package/dist/bindings/r2.js +2 -2
- package/dist/bindings/r2.js.map +1 -1
- package/dist/browser.d.ts +0 -1
- package/dist/browser.js +0 -1
- package/dist/browser.js.map +1 -1
- package/dist/bucket/celld-cells.js +1 -1
- package/dist/bucket/celld-cells.js.map +1 -1
- package/dist/bucket/s3-object-store.js +1 -1
- package/dist/bucket/s3-object-store.js.map +1 -1
- package/dist/core/handles.d.ts +5 -2
- package/dist/core/handles.js +12 -1
- package/dist/core/handles.js.map +1 -1
- package/dist/core/id.js +3 -3
- package/dist/core/id.js.map +1 -1
- package/dist/core/namespace.d.ts +1 -1
- package/dist/core/namespace.js +2 -1
- package/dist/core/namespace.js.map +1 -1
- package/dist/core/object-host.d.ts +23 -0
- package/dist/core/object-host.js +87 -6
- package/dist/core/object-host.js.map +1 -1
- package/dist/core/runtime.d.ts +10 -0
- package/dist/core/runtime.js +43 -2
- package/dist/core/runtime.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/isolate/browser.js +1 -1
- package/dist/isolate/browser.js.map +1 -1
- package/dist/isolate/host.d.ts +5 -1
- package/dist/isolate/host.js +29 -2
- package/dist/isolate/host.js.map +1 -1
- package/dist/isolate/isolate-worker.js +11 -1
- package/dist/isolate/isolate-worker.js.map +1 -1
- package/dist/isolate/isolate.js +20 -0
- package/dist/isolate/isolate.js.map +1 -1
- package/dist/isolate/node-entry.js +11 -0
- package/dist/isolate/node-entry.js.map +1 -1
- package/dist/isolate/protocol.d.ts +5 -0
- package/dist/isolate/protocol.js +114 -2
- package/dist/isolate/protocol.js.map +1 -1
- package/dist/node-compat/crypto.js +3 -3
- package/dist/node-compat/crypto.js.map +1 -1
- package/dist/peer/celld-peer.d.ts +17 -0
- package/dist/peer/celld-peer.js +49 -7
- package/dist/peer/celld-peer.js.map +1 -1
- package/dist/project/browser-host.js +3 -2
- package/dist/project/browser-host.js.map +1 -1
- package/dist/project/node-project.js +2 -1
- package/dist/project/node-project.js.map +1 -1
- package/dist/project/site-loader.d.ts +45 -0
- package/dist/project/site-loader.js +97 -0
- package/dist/project/site-loader.js.map +1 -0
- package/dist/project/site.d.ts +0 -36
- package/dist/project/site.js +11 -99
- package/dist/project/site.js.map +1 -1
- package/dist/project/wrangler.js +10 -0
- package/dist/project/wrangler.js.map +1 -1
- package/dist/router/cell-router.js +1 -1
- package/dist/router/cell-router.js.map +1 -1
- package/dist/site.d.ts +1 -0
- package/dist/site.js +6 -0
- package/dist/site.js.map +1 -0
- package/dist/transport/port.d.ts +3 -4
- package/dist/transport/port.js +21 -79
- package/dist/transport/port.js.map +1 -1
- package/dist/ws/client.d.ts +23 -8
- package/dist/ws/client.js +50 -189
- package/dist/ws/client.js.map +1 -1
- package/dist/ws/frames.d.ts +31 -8
- package/dist/ws/frames.js +54 -7
- package/dist/ws/frames.js.map +1 -1
- package/dist/ws/server.d.ts +4 -0
- package/dist/ws/server.js +29 -5
- package/dist/ws/server.js.map +1 -1
- package/package.json +29 -10
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cell-router.js","sourceRoot":"","sources":["../../src/router/cell-router.ts"],"names":[],"mappings":"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","sourcesContent":["import { idFromName, namespaceKey } from \"../core/id.js\";\nimport { shardOf } from \"../core/shard.js\";\nimport { errorResponse, stripControl } from \"../transport/cells-http.js\";\nimport { H, type Send } from \"../transport/cells-link.js\";\nimport {\n type ControlMessage,\n type ControlPort,\n PortRendezvous,\n shardOfControlId,\n} from \"../ws/control.js\";\n\nexport interface CellRouterOptions {\n shards: number;\n /** Durable Object binding → class name, as in wrangler config. */\n bindings: Record<string, string>;\n /** The Worker script's name: ids derive from it (default \"worker\", as the runtime's). */\n scriptName?: string;\n /** Called when a request needs a shard that has no live link (e.g. ask pages to re-attach). */\n onMissingShard?: (shard: number) => void;\n /** How long a request waits for its shard to be attached (default 10 s). */\n attachTimeoutMs?: number;\n /**\n * Liveness probe of the owner that attached `token`. Polled every\n * `livenessIntervalMs` while the shard has requests in flight; `false`\n * invalidates the link. In a ServiceWorker: is the shard's Web Lock still\n * held by the same holder? (MessagePort has no close event in Chromium.)\n */\n isAlive?: (shard: number, token: unknown) => Promise<boolean>;\n livenessIntervalMs?: number;\n /**\n * Event-driven liveness (preferred over polling): resolves when the owner that\n * attached `token` is gone. Browser: a pending request on the owner's own Web\n * Lock, granted the moment its worker/tab dies. Node: worker \"exit\".\n */\n watchOwner?: (shard: number, token: unknown) => Promise<unknown>;\n}\n\n/** Thrown into in-flight requests of a shard whose owner is gone (CF: \"Durable Object reset\"). */\nexport class ShardOwnerLostError extends Error {\n constructor(shard: number) {\n super(`Owner of cell shard ${shard} was lost; the request may or may not have been applied`);\n this.name = \"ShardOwnerLostError\";\n }\n}\n\ninterface Link {\n send: Send;\n token: unknown;\n inflight: Set<AbortController>;\n ctl?: ControlPort;\n /** Being handed over: no new requests (they wait for the next owner), the current ones finish. */\n draining?: boolean;\n}\n\n/**\n * The single \"server\" of an origin (ServiceWorker, page or Node main thread):\n * knows which shard owns which cell and holds one HTTP link per shard worker.\n *\n * - `ingress(req)`: public requests → any live shard's Worker `fetch`. Control\n * headers are stripped first, so clients cannot address objects directly.\n * - `internal(req)`: requests from shard workers (DO → DO across shards) →\n * forwarded to the owning shard, control headers intact.\n */\nexport class CellRouter {\n readonly #opts: CellRouterOptions;\n readonly #links = new Map<number, Link>();\n #watchdog: ReturnType<typeof setInterval> | undefined;\n readonly #waiters = new Map<number, (() => void)[]>();\n #rr = 0;\n #wsSeq = 0;\n readonly #wsShard = new Map<string, number>();\n readonly #ports = new PortRendezvous();\n\n constructor(opts: CellRouterOptions) {\n this.#opts = opts;\n }\n\n get attachedShards(): number[] {\n return [...this.#links]\n .filter(([, l]) => !l.draining)\n .map(([shard]) => shard)\n .sort((a, b) => a - b);\n }\n\n /**\n * Attaches the link of a shard. Replacing a link means the previous owner is\n * gone (Web Locks / leases guarantee a single live owner): its in-flight\n * requests fail at once instead of hanging on a dead port.\n */\n attach(shard: number, send: Send, token?: unknown, ctl?: ControlPort): void {\n const prev = this.#links.get(shard);\n this.#links.set(shard, { send, token, inflight: new Set(), ctl });\n if (ctl) {\n ctl.onmessage = (e) => this.#onControl(e.data as ControlMessage);\n ctl.start?.();\n }\n if (prev) this.#fail(shard, prev);\n if (token !== undefined && this.#opts.watchOwner)\n this.#opts.watchOwner(shard, token).then(\n () => this.#links.get(shard)?.token === token && this.detach(shard),\n () => {},\n );\n for (const wake of this.#waiters.get(shard) ?? []) wake();\n this.#waiters.delete(shard);\n }\n\n detach(shard: number, send?: Send): void {\n const link = this.#links.get(shard);\n if (!link || (send && link.send !== send)) return;\n this.#links.delete(shard);\n this.#fail(shard, link);\n }\n\n /**\n * A planned hand-over of `shard`: new requests wait for the next owner to attach, the ones in\n * flight get up to `timeoutMs` to finish, then the link goes (the rest fail as when an owner is\n * lost). Only the link attached with `token` is drained.\n */\n async drain(shard: number, token: unknown, timeoutMs: number): Promise<void> {\n const link = this.#links.get(shard);\n if (!link || link.token !== token) return;\n link.draining = true;\n const end = Date.now() + timeoutMs;\n while (link.inflight.size && Date.now() < end && this.#links.get(shard) === link)\n await new Promise((r) => setTimeout(r, 20));\n this.detach(shard, link.send);\n }\n\n /** Number of requests currently forwarded to a shard (diagnostics/tests). */\n inflight(shard: number): number {\n return this.#links.get(shard)?.inflight.size ?? 0;\n }\n\n /**\n * WebSocket ingress (a ServiceWorker cannot intercept WebSockets): asks a live\n * shard, over its control channel, to run the Worker's fetch with an Upgrade\n * request; resolves with the client end's port (transfer it to the page).\n */\n async connectWebSocket(url: string, protocols: string[] = [], headers?: [string, string][]) {\n const shard = this.#pick();\n const link = await this.#linkFor(shard);\n if (!link.ctl) throw new Error(`shard ${shard} has no control channel: WebSockets unavailable`);\n const id = `r:${++this.#wsSeq}`;\n this.#wsShard.set(id, shard);\n link.ctl.postMessage({ t: \"ws-connect\", id, url, protocols, headers } satisfies ControlMessage);\n try {\n return await this.#ports.wait(id);\n } finally {\n this.#wsShard.delete(id);\n }\n }\n\n #onControl(m: ControlMessage): void {\n if (m.t !== \"ws-accept\" && m.t !== \"ws-refuse\") return;\n const target = shardOfControlId(m.id);\n if (target !== undefined) {\n // Cross-shard hand-over: the Worker that asked lives in shard `target`.\n const ctl = this.#links.get(target)?.ctl;\n if (!ctl) {\n if (m.t === \"ws-accept\") m.port.close();\n return;\n }\n ctl.postMessage(m, m.t === \"ws-accept\" ? [m.port as unknown as Transferable] : []);\n return;\n }\n this.#ports.deliver(\n m.id,\n m.t === \"ws-accept\" ? { port: m.port, protocol: m.protocol } : new Error(m.message),\n );\n }\n\n #fail(shard: number, link: Link): void {\n this.#ports.failAll((id) => this.#wsShard.get(id) === shard, new ShardOwnerLostError(shard));\n for (const ac of link.inflight) ac.abort(new ShardOwnerLostError(shard));\n link.inflight.clear();\n }\n\n /** Forwards with an abort handle; the handle lives until the response body ends. */\n async #forward(shard: number, req: Request): Promise<Response> {\n const link = await this.#linkFor(shard);\n const ac = new AbortController();\n link.inflight.add(ac);\n this.#ensureWatchdog();\n const done = () => link.inflight.delete(ac);\n const lost = new Promise<never>((_, reject) =>\n ac.signal.addEventListener(\"abort\", () => reject(ac.signal.reason), { once: true }),\n );\n lost.catch(() => {});\n try {\n const signal = AbortSignal.any([req.signal, ac.signal]);\n const res = await Promise.race([\n link.send(new Request(req, { signal, duplex: \"half\" } as RequestInit)),\n lost,\n ]);\n if (!res.body) return done(), res;\n const { readable, writable } = new TransformStream();\n res.body.pipeTo(writable, { signal: ac.signal }).then(done, done);\n return new Response(readable, res);\n } catch (e) {\n done();\n throw ac.signal.aborted ? ac.signal.reason : e;\n }\n }\n\n #ensureWatchdog(): void {\n if (this.#watchdog || !this.#opts.isAlive) return;\n this.#watchdog = setInterval(async () => {\n const busy = [...this.#links].filter(([, l]) => l.inflight.size > 0);\n if (!busy.length) {\n clearInterval(this.#watchdog);\n this.#watchdog = undefined;\n return;\n }\n for (const [shard, link] of busy) {\n const alive = await this.#opts.isAlive!(shard, link.token).catch(() => true);\n if (!alive && this.#links.get(shard) === link) this.detach(shard);\n }\n }, this.#opts.livenessIntervalMs ?? 1000);\n }\n\n /**\n * The shard for ingress: the live one with the fewest requests in flight (a long request\n * keeps its shard busy, wherever its Durable Objects run), round robin among equals.\n */\n #pick(): number {\n const live = this.attachedShards;\n if (!live.length) return 0;\n const start = this.#rr++ % live.length;\n let best = live[start];\n for (let i = 1; i < live.length; i++) {\n const s = live[(start + i) % live.length];\n if (this.inflight(s) < this.inflight(best)) best = s;\n }\n return best;\n }\n\n ingress = async (req: Request): Promise<Response> => {\n const shard = this.#pick();\n try {\n // The page's cookies (x-cellb-cookie, set by the ServiceWorker) go on to the shard.\n return await this.#forward(shard, stripControl(req, [H.cookie]));\n } catch (e) {\n return new Response(`Cell shard ${shard} unavailable: ${(e as Error).message}`, {\n status: 503,\n });\n }\n };\n\n internal = async (req: Request): Promise<Response> => {\n const kind = req.headers.get(H.kind);\n try {\n if (kind === \"describe\") return Response.json({ bindings: this.#opts.bindings });\n if (kind !== \"fetch\" && kind !== \"rpc\") return await this.ingress(req);\n return await this.#forward(this.shardOfRequest(req), req);\n } catch (e) {\n return errorResponse(e, 503);\n }\n };\n\n shardOfRequest(req: Request): number {\n const explicit = req.headers.get(H.shard);\n if (explicit !== null) {\n const shard = Number(explicit);\n if (Number.isInteger(shard) && shard >= 0 && shard < this.#opts.shards) return shard;\n throw new Error(`Invalid cell shard \"${explicit}\"`);\n }\n const binding = req.headers.get(H.binding) ?? \"\";\n const target = req.headers.get(H.target) ?? \"\";\n const className = this.#opts.bindings[binding];\n if (!className) throw new Error(`Unknown Durable Object binding \"${binding}\"`);\n const hex = target.startsWith(\"name:\")\n ? idFromName(\n namespaceKey(this.#opts.scriptName ?? \"worker\", className),\n target.slice(5),\n ).toString()\n : target.slice(3);\n return shardOf(hex, this.#opts.shards);\n }\n\n async #linkFor(shard: number): Promise<Link> {\n const link = this.#links.get(shard);\n if (link && !link.draining) return link;\n this.#opts.onMissingShard?.(shard);\n const timeout = this.#opts.attachTimeoutMs ?? 10_000;\n await new Promise<void>((resolve, reject) => {\n const t = setTimeout(\n () => reject(new Error(`shard ${shard} not attached within ${timeout} ms`)),\n timeout,\n );\n const list = this.#waiters.get(shard) ?? [];\n list.push(() => (clearTimeout(t), resolve()));\n this.#waiters.set(shard, list);\n });\n return this.#links.get(shard)!;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"cell-router.js","sourceRoot":"","sources":["../../src/router/cell-router.ts"],"names":[],"mappings":"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","sourcesContent":["import { idFromName, namespaceKey } from \"../core/id.js\";\nimport { shardOf } from \"../core/shard.js\";\nimport { errorResponse, stripControl } from \"../transport/cells-http.js\";\nimport { H, type Send } from \"../transport/cells-link.js\";\nimport {\n type ControlMessage,\n type ControlPort,\n PortRendezvous,\n shardOfControlId,\n} from \"../ws/control.js\";\n\nexport interface CellRouterOptions {\n shards: number;\n /** Durable Object binding → class name, as in wrangler config. */\n bindings: Record<string, string>;\n /** The Worker script's name: ids derive from it (default \"worker\", as the runtime's). */\n scriptName?: string;\n /** Called when a request needs a shard that has no live link (e.g. ask pages to re-attach). */\n onMissingShard?: (shard: number) => void;\n /** How long a request waits for its shard to be attached (default 10 s). */\n attachTimeoutMs?: number;\n /**\n * Liveness probe of the owner that attached `token`. Polled every\n * `livenessIntervalMs` while the shard has requests in flight; `false`\n * invalidates the link. In a ServiceWorker: is the shard's Web Lock still\n * held by the same holder? (MessagePort has no close event in Chromium.)\n */\n isAlive?: (shard: number, token: unknown) => Promise<boolean>;\n livenessIntervalMs?: number;\n /**\n * Event-driven liveness (preferred over polling): resolves when the owner that\n * attached `token` is gone. Browser: a pending request on the owner's own Web\n * Lock, granted the moment its worker/tab dies. Node: worker \"exit\".\n */\n watchOwner?: (shard: number, token: unknown) => Promise<unknown>;\n}\n\n/** Thrown into in-flight requests of a shard whose owner is gone (CF: \"Durable Object reset\"). */\nexport class ShardOwnerLostError extends Error {\n constructor(shard: number) {\n super(`Owner of cell shard ${shard} was lost; the request may or may not have been applied`);\n this.name = \"ShardOwnerLostError\";\n }\n}\n\ninterface Link {\n send: Send;\n token: unknown;\n inflight: Set<AbortController>;\n ctl?: ControlPort;\n /** Being handed over: no new requests (they wait for the next owner), the current ones finish. */\n draining?: boolean;\n}\n\n/**\n * The single \"server\" of an origin (ServiceWorker, page or Node main thread):\n * knows which shard owns which cell and holds one HTTP link per shard worker.\n *\n * - `ingress(req)`: public requests → any live shard's Worker `fetch`. Control\n * headers are stripped first, so clients cannot address objects directly.\n * - `internal(req)`: requests from shard workers (DO → DO across shards) →\n * forwarded to the owning shard, control headers intact.\n */\nexport class CellRouter {\n readonly #opts: CellRouterOptions;\n readonly #links = new Map<number, Link>();\n #watchdog: ReturnType<typeof setInterval> | undefined;\n readonly #waiters = new Map<number, (() => void)[]>();\n #rr = 0;\n #wsSeq = 0;\n readonly #wsShard = new Map<string, number>();\n readonly #ports = new PortRendezvous();\n\n constructor(opts: CellRouterOptions) {\n this.#opts = opts;\n }\n\n get attachedShards(): number[] {\n return [...this.#links]\n .filter(([, l]) => !l.draining)\n .map(([shard]) => shard)\n .sort((a, b) => a - b);\n }\n\n /**\n * Attaches the link of a shard. Replacing a link means the previous owner is\n * gone (Web Locks / leases guarantee a single live owner): its in-flight\n * requests fail at once instead of hanging on a dead port.\n */\n attach(shard: number, send: Send, token?: unknown, ctl?: ControlPort): void {\n const prev = this.#links.get(shard);\n this.#links.set(shard, { send, token, inflight: new Set(), ctl });\n if (ctl) {\n ctl.onmessage = (e) => this.#onControl(e.data as ControlMessage);\n ctl.start?.();\n }\n if (prev) this.#fail(shard, prev);\n if (token !== undefined && this.#opts.watchOwner)\n this.#opts.watchOwner(shard, token).then(\n () => this.#links.get(shard)?.token === token && this.detach(shard),\n () => {},\n );\n for (const wake of this.#waiters.get(shard) ?? []) wake();\n this.#waiters.delete(shard);\n }\n\n detach(shard: number, send?: Send): void {\n const link = this.#links.get(shard);\n if (!link || (send && link.send !== send)) return;\n this.#links.delete(shard);\n this.#fail(shard, link);\n }\n\n /**\n * A planned hand-over of `shard`: new requests wait for the next owner to attach, the ones in\n * flight get up to `timeoutMs` to finish, then the link goes (the rest fail as when an owner is\n * lost). Only the link attached with `token` is drained.\n */\n async drain(shard: number, token: unknown, timeoutMs: number): Promise<void> {\n const link = this.#links.get(shard);\n if (!link || link.token !== token) return;\n link.draining = true;\n const end = Date.now() + timeoutMs;\n while (link.inflight.size && Date.now() < end && this.#links.get(shard) === link)\n await new Promise((r) => setTimeout(r, 20));\n this.detach(shard, link.send);\n }\n\n /** Number of requests currently forwarded to a shard (diagnostics/tests). */\n inflight(shard: number): number {\n return this.#links.get(shard)?.inflight.size ?? 0;\n }\n\n /**\n * WebSocket ingress (a ServiceWorker cannot intercept WebSockets): asks a live\n * shard, over its control channel, to run the Worker's fetch with an Upgrade\n * request; resolves with the client end's port (transfer it to the page).\n */\n async connectWebSocket(url: string, protocols: string[] = [], headers?: [string, string][]) {\n const shard = this.#pick();\n const link = await this.#linkFor(shard);\n if (!link.ctl) throw new Error(`shard ${shard} has no control channel: WebSockets unavailable`);\n const id = `r:${++this.#wsSeq}`;\n this.#wsShard.set(id, shard);\n link.ctl.postMessage({ t: \"ws-connect\", id, url, protocols, headers } satisfies ControlMessage);\n try {\n return await this.#ports.wait(id);\n } finally {\n this.#wsShard.delete(id);\n }\n }\n\n #onControl(m: ControlMessage): void {\n if (m.t !== \"ws-accept\" && m.t !== \"ws-refuse\") return;\n const target = shardOfControlId(m.id);\n if (target !== undefined) {\n // Cross-shard hand-over: the Worker that asked lives in shard `target`.\n const ctl = this.#links.get(target)?.ctl;\n if (!ctl) {\n if (m.t === \"ws-accept\") m.port.close();\n return;\n }\n ctl.postMessage(m, m.t === \"ws-accept\" ? [m.port as unknown as Transferable] : []);\n return;\n }\n this.#ports.deliver(\n m.id,\n m.t === \"ws-accept\" ? { port: m.port, protocol: m.protocol } : new Error(m.message),\n );\n }\n\n #fail(shard: number, link: Link): void {\n this.#ports.failAll((id) => this.#wsShard.get(id) === shard, new ShardOwnerLostError(shard));\n for (const ac of link.inflight) ac.abort(new ShardOwnerLostError(shard));\n link.inflight.clear();\n }\n\n /** Forwards with an abort handle; the handle lives until the response body ends. */\n async #forward(shard: number, req: Request): Promise<Response> {\n const link = await this.#linkFor(shard);\n const ac = new AbortController();\n link.inflight.add(ac);\n this.#ensureWatchdog();\n const done = () => link.inflight.delete(ac);\n const lost = new Promise<never>((_, reject) =>\n ac.signal.addEventListener(\"abort\", () => reject(ac.signal.reason), { once: true }),\n );\n lost.catch(() => {});\n try {\n const signal = AbortSignal.any([req.signal, ac.signal]);\n const res = await Promise.race([\n link.send(new Request(req, { signal, duplex: \"half\" } as RequestInit)),\n lost,\n ]);\n if (!res.body) return (done(), res);\n const { readable, writable } = new TransformStream();\n res.body.pipeTo(writable, { signal: ac.signal }).then(done, done);\n return new Response(readable, res);\n } catch (e) {\n done();\n throw ac.signal.aborted ? ac.signal.reason : e;\n }\n }\n\n #ensureWatchdog(): void {\n if (this.#watchdog || !this.#opts.isAlive) return;\n this.#watchdog = setInterval(async () => {\n const busy = [...this.#links].filter(([, l]) => l.inflight.size > 0);\n if (!busy.length) {\n clearInterval(this.#watchdog);\n this.#watchdog = undefined;\n return;\n }\n for (const [shard, link] of busy) {\n const alive = await this.#opts.isAlive!(shard, link.token).catch(() => true);\n if (!alive && this.#links.get(shard) === link) this.detach(shard);\n }\n }, this.#opts.livenessIntervalMs ?? 1000);\n }\n\n /**\n * The shard for ingress: the live one with the fewest requests in flight (a long request\n * keeps its shard busy, wherever its Durable Objects run), round robin among equals.\n */\n #pick(): number {\n const live = this.attachedShards;\n if (!live.length) return 0;\n const start = this.#rr++ % live.length;\n let best = live[start];\n for (let i = 1; i < live.length; i++) {\n const s = live[(start + i) % live.length];\n if (this.inflight(s) < this.inflight(best)) best = s;\n }\n return best;\n }\n\n ingress = async (req: Request): Promise<Response> => {\n const shard = this.#pick();\n try {\n // The page's cookies (x-cellb-cookie, set by the ServiceWorker) go on to the shard.\n return await this.#forward(shard, stripControl(req, [H.cookie]));\n } catch (e) {\n return new Response(`Cell shard ${shard} unavailable: ${(e as Error).message}`, {\n status: 503,\n });\n }\n };\n\n internal = async (req: Request): Promise<Response> => {\n const kind = req.headers.get(H.kind);\n try {\n if (kind === \"describe\") return Response.json({ bindings: this.#opts.bindings });\n if (kind !== \"fetch\" && kind !== \"rpc\") return await this.ingress(req);\n return await this.#forward(this.shardOfRequest(req), req);\n } catch (e) {\n return errorResponse(e, 503);\n }\n };\n\n shardOfRequest(req: Request): number {\n const explicit = req.headers.get(H.shard);\n if (explicit !== null) {\n const shard = Number(explicit);\n if (Number.isInteger(shard) && shard >= 0 && shard < this.#opts.shards) return shard;\n throw new Error(`Invalid cell shard \"${explicit}\"`);\n }\n const binding = req.headers.get(H.binding) ?? \"\";\n const target = req.headers.get(H.target) ?? \"\";\n const className = this.#opts.bindings[binding];\n if (!className) throw new Error(`Unknown Durable Object binding \"${binding}\"`);\n const hex = target.startsWith(\"name:\")\n ? idFromName(\n namespaceKey(this.#opts.scriptName ?? \"worker\", className),\n target.slice(5),\n ).toString()\n : target.slice(3);\n return shardOf(hex, this.#opts.shards);\n }\n\n async #linkFor(shard: number): Promise<Link> {\n const link = this.#links.get(shard);\n if (link && !link.draining) return link;\n this.#opts.onMissingShard?.(shard);\n const timeout = this.#opts.attachTimeoutMs ?? 10_000;\n await new Promise<void>((resolve, reject) => {\n const t = setTimeout(\n () => reject(new Error(`shard ${shard} not attached within ${timeout} ms`)),\n timeout,\n );\n const list = this.#waiters.get(shard) ?? [];\n list.push(() => (clearTimeout(t), resolve()));\n this.#waiters.set(shard, list);\n });\n return this.#links.get(shard)!;\n }\n}\n"]}
|
package/dist/site.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createProjectSite } from "./project/site.js";
|
package/dist/site.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// @statewalker/cellb/site: a project's sources compiled and served in the browser (an in-browser
|
|
2
|
+
// HTTP server for `browserProjectRuntime({ site })`). It needs the optional peer dependencies
|
|
3
|
+
// @statewalker/webrun-modules and @statewalker/webrun-site-builder; the embedder hosts the site in
|
|
4
|
+
// a ServiceWorker with @statewalker/webrun-site-host, the third optional peer.
|
|
5
|
+
export { createProjectSite } from "./project/site.js";
|
|
6
|
+
//# sourceMappingURL=site.js.map
|
package/dist/site.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"site.js","sourceRoot":"","sources":["../src/site.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,8FAA8F;AAC9F,mGAAmG;AACnG,+EAA+E;AAC/E,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC","sourcesContent":["// @statewalker/cellb/site: a project's sources compiled and served in the browser (an in-browser\n// HTTP server for `browserProjectRuntime({ site })`). It needs the optional peer dependencies\n// @statewalker/webrun-modules and @statewalker/webrun-site-builder; the embedder hosts the site in\n// a ServiceWorker with @statewalker/webrun-site-host, the third optional peer.\nexport { createProjectSite } from \"./project/site.js\";\n"]}
|
package/dist/transport/port.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { overPipe } from "@statewalker/webrun-rpc";
|
|
1
|
+
import type { overPipe } from "@statewalker/webrun-rpc";
|
|
2
2
|
import type { CellRuntime } from "../core/runtime.js";
|
|
3
3
|
import { type CellsHttpOptions, type RemoteRuntime } from "./cells-http.js";
|
|
4
4
|
import { type Send } from "./cells-link.js";
|
|
@@ -8,9 +8,8 @@ import { type Send } from "./cells-link.js";
|
|
|
8
8
|
*/
|
|
9
9
|
export type CellPort = Parameters<typeof overPipe>[0];
|
|
10
10
|
/**
|
|
11
|
-
* Cell-host side: serves the runtime's HTTP surface over a port
|
|
12
|
-
*
|
|
13
|
-
* Duplex, streaming bodies). Resolves to an async disposer.
|
|
11
|
+
* Cell-host side: serves the runtime's HTTP surface over a port. Resolves to an async
|
|
12
|
+
* disposer.
|
|
14
13
|
*/
|
|
15
14
|
export declare function serveRuntime(port: CellPort, runtime: CellRuntime, opts?: CellsHttpOptions): Promise<() => Promise<void>>;
|
|
16
15
|
/** Front side (page, SW bridge, another tab): a RemoteRuntime over a port. */
|
package/dist/transport/port.js
CHANGED
|
@@ -1,100 +1,42 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { connect, overPipe, serve, structuredCodec } from "@statewalker/webrun-rpc";
|
|
1
|
+
import { connectHttpLink, serveHttpLink } from "@statewalker/webrun-http-browser/sw";
|
|
3
2
|
import { cellsHttpHandler, remoteRuntime, } from "./cells-http.js";
|
|
4
3
|
import { withScheme } from "./cells-link.js";
|
|
5
4
|
/**
|
|
6
|
-
* Multiplexed ports per link: one per exchange in flight
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* Multiplexed ports per link: one per exchange in flight, given back when the exchange ends
|
|
6
|
+
* (p31-port-leak.test.ts). 4096 per link, cellb's ceiling before the links were the package's (its
|
|
7
|
+
* default is 1024): a streaming response holds its port for its whole life.
|
|
8
|
+
* `globalThis.__cellbMaxPorts` overrides it: tests that check nothing leaks set it low.
|
|
9
9
|
*/
|
|
10
10
|
const maxPorts = () => globalThis.__cellbMaxPorts ?? 4096;
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
* webrun-rpc
|
|
14
|
-
*
|
|
12
|
+
* The links are webrun-http-browser's (`connectHttpLink` / `serveHttpLink`): HTTP/1.1 with
|
|
13
|
+
* streaming bodies over webrun-rpc calls, one multiplexed port per exchange. They take a
|
|
14
|
+
* MessagePort; a worker_threads port has the same `postMessage` / `addEventListener` surface.
|
|
15
|
+
*/
|
|
16
|
+
const asPort = (port) => port;
|
|
17
|
+
/**
|
|
18
|
+
* Cell-host side: serves the runtime's HTTP surface over a port. Resolves to an async
|
|
19
|
+
* disposer.
|
|
15
20
|
*/
|
|
16
21
|
export function serveRuntime(port, runtime, opts) {
|
|
17
|
-
return
|
|
18
|
-
}
|
|
19
|
-
async function fetchClosing(call, req) {
|
|
20
|
-
let output;
|
|
21
|
-
const res = await fetchOverDuplex(((input) => {
|
|
22
|
-
output = call(input);
|
|
23
|
-
return output;
|
|
24
|
-
}), req);
|
|
25
|
-
const drain = () => void (async () => {
|
|
26
|
-
try {
|
|
27
|
-
for await (const _ of output)
|
|
28
|
-
;
|
|
29
|
-
}
|
|
30
|
-
catch { }
|
|
31
|
-
})();
|
|
32
|
-
if (!output)
|
|
33
|
-
return res;
|
|
34
|
-
if (res.body === null) {
|
|
35
|
-
drain();
|
|
36
|
-
return res;
|
|
37
|
-
}
|
|
38
|
-
// The same once the body is read to its end, or cancelled by its reader (then the exchange
|
|
39
|
-
// is abandoned: the call's iterator is returned, which closes its port).
|
|
40
|
-
const reader = res.body.getReader();
|
|
41
|
-
const body = new ReadableStream({
|
|
42
|
-
async pull(ctl) {
|
|
43
|
-
const r = await reader.read();
|
|
44
|
-
if (r.done) {
|
|
45
|
-
ctl.close();
|
|
46
|
-
drain();
|
|
47
|
-
}
|
|
48
|
-
else
|
|
49
|
-
ctl.enqueue(r.value);
|
|
50
|
-
},
|
|
51
|
-
async cancel(reason) {
|
|
52
|
-
await reader.cancel(reason).catch(() => { });
|
|
53
|
-
await output.return?.(undefined).catch?.(() => { });
|
|
54
|
-
},
|
|
55
|
-
});
|
|
56
|
-
return Object.assign(new Response(body, res), {
|
|
57
|
-
webSocket: res.webSocket ?? null,
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
function servingClosing(duplex) {
|
|
61
|
-
return async function* (input) {
|
|
62
|
-
const it = input[Symbol.asyncIterator]();
|
|
63
|
-
const rest = { [Symbol.asyncIterator]: () => it };
|
|
64
|
-
try {
|
|
65
|
-
yield* duplex(rest);
|
|
66
|
-
}
|
|
67
|
-
finally {
|
|
68
|
-
void (async () => {
|
|
69
|
-
try {
|
|
70
|
-
for (let r = await it.next(); !r.done; r = await it.next())
|
|
71
|
-
;
|
|
72
|
-
}
|
|
73
|
-
catch { }
|
|
74
|
-
})();
|
|
75
|
-
}
|
|
76
|
-
};
|
|
22
|
+
return serveHttpLink(asPort(port), cellsHttpHandler(runtime, opts), { maxPorts: maxPorts() });
|
|
77
23
|
}
|
|
78
24
|
/** Front side (page, SW bridge, another tab): a RemoteRuntime over a port. */
|
|
79
25
|
export async function connectRuntime(port, opts) {
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
});
|
|
83
|
-
const remote = await remoteRuntime((req) => fetchClosing(call, req), opts);
|
|
84
|
-
return Object.assign(remote, { close: async () => void (await close()) });
|
|
26
|
+
const link = await connectHttpLink(asPort(port), { maxPorts: maxPorts() });
|
|
27
|
+
const remote = await remoteRuntime((req) => link.send(req), opts);
|
|
28
|
+
return Object.assign(remote, { close: () => link.close() });
|
|
85
29
|
}
|
|
86
30
|
/** Serves any HTTP handler over a port (responder side of a link). */
|
|
87
31
|
export function serveHandlerOverPort(port, handler) {
|
|
88
|
-
return
|
|
32
|
+
return serveHttpLink(asPort(port), handler, { maxPorts: maxPorts() });
|
|
89
33
|
}
|
|
90
34
|
/** A `send` over a port (initiator side of a link); the scheme rides in a header. */
|
|
91
35
|
export async function linkOverPort(port) {
|
|
92
|
-
const
|
|
93
|
-
mux: overPipe(port, { codec: structuredCodec, side: "initiator", maxPorts: maxPorts() }),
|
|
94
|
-
});
|
|
36
|
+
const link = await connectHttpLink(asPort(port), { maxPorts: maxPorts() });
|
|
95
37
|
return {
|
|
96
|
-
send: (req) =>
|
|
97
|
-
close:
|
|
38
|
+
send: (req) => link.send(withScheme(req)),
|
|
39
|
+
close: () => link.close(),
|
|
98
40
|
};
|
|
99
41
|
}
|
|
100
42
|
//# sourceMappingURL=port.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"port.js","sourceRoot":"","sources":["../../src/transport/port.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,
|
|
1
|
+
{"version":3,"file":"port.js","sourceRoot":"","sources":["../../src/transport/port.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,qCAAqC,CAAC;AAGrF,OAAO,EAEL,gBAAgB,EAEhB,aAAa,GACd,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAa,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAExD;;;;;GAKG;AACH,MAAM,QAAQ,GAAG,GAAG,EAAE,CAAE,UAA2C,CAAC,eAAe,IAAI,IAAI,CAAC;AAQ5F;;;;GAIG;AACH,MAAM,MAAM,GAAG,CAAC,IAAc,EAAE,EAAE,CAAC,IAA8B,CAAC;AAElE;;;GAGG;AACH,MAAM,UAAU,YAAY,CAC1B,IAAc,EACd,OAAoB,EACpB,IAAuB;IAEvB,OAAO,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,gBAAgB,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;AAChG,CAAC;AAED,8EAA8E;AAC9E,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAAc,EACd,IAAuB;IAEvB,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;IAC3E,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC;IAClE,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAC9D,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,oBAAoB,CAAC,IAAc,EAAE,OAAa;IAChE,OAAO,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;AACxE,CAAC;AAED,qFAAqF;AACrF,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,IAAc;IAEd,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;IAC3E,OAAO;QACL,IAAI,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QACzC,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE;KAC1B,CAAC;AACJ,CAAC","sourcesContent":["import { connectHttpLink, serveHttpLink } from \"@statewalker/webrun-http-browser/sw\";\nimport type { overPipe } from \"@statewalker/webrun-rpc\";\nimport type { CellRuntime } from \"../core/runtime.js\";\nimport {\n type CellsHttpOptions,\n cellsHttpHandler,\n type RemoteRuntime,\n remoteRuntime,\n} from \"./cells-http.js\";\nimport { type Send, withScheme } from \"./cells-link.js\";\n\n/**\n * Multiplexed ports per link: one per exchange in flight, given back when the exchange ends\n * (p31-port-leak.test.ts). 4096 per link, cellb's ceiling before the links were the package's (its\n * default is 1024): a streaming response holds its port for its whole life.\n * `globalThis.__cellbMaxPorts` overrides it: tests that check nothing leaks set it low.\n */\nconst maxPorts = () => (globalThis as { __cellbMaxPorts?: number }).__cellbMaxPorts ?? 4096;\n\n/**\n * A DEDICATED message pipe (MessagePort, or a worker_threads port). Don't pass a\n * Worker object that also carries other messages — transfer a MessageChannel port.\n */\nexport type CellPort = Parameters<typeof overPipe>[0];\n\n/**\n * The links are webrun-http-browser's (`connectHttpLink` / `serveHttpLink`): HTTP/1.1 with\n * streaming bodies over webrun-rpc calls, one multiplexed port per exchange. They take a\n * MessagePort; a worker_threads port has the same `postMessage` / `addEventListener` surface.\n */\nconst asPort = (port: CellPort) => port as unknown as MessagePort;\n\n/**\n * Cell-host side: serves the runtime's HTTP surface over a port. Resolves to an async\n * disposer.\n */\nexport function serveRuntime(\n port: CellPort,\n runtime: CellRuntime,\n opts?: CellsHttpOptions,\n): Promise<() => Promise<void>> {\n return serveHttpLink(asPort(port), cellsHttpHandler(runtime, opts), { maxPorts: maxPorts() });\n}\n\n/** Front side (page, SW bridge, another tab): a RemoteRuntime over a port. */\nexport async function connectRuntime(\n port: CellPort,\n opts?: CellsHttpOptions,\n): Promise<RemoteRuntime & { close(): Promise<void> }> {\n const link = await connectHttpLink(asPort(port), { maxPorts: maxPorts() });\n const remote = await remoteRuntime((req) => link.send(req), opts);\n return Object.assign(remote, { close: () => link.close() });\n}\n\n/** Serves any HTTP handler over a port (responder side of a link). */\nexport function serveHandlerOverPort(port: CellPort, handler: Send): Promise<() => Promise<void>> {\n return serveHttpLink(asPort(port), handler, { maxPorts: maxPorts() });\n}\n\n/** A `send` over a port (initiator side of a link); the scheme rides in a header. */\nexport async function linkOverPort(\n port: CellPort,\n): Promise<{ send: Send; close(): Promise<void> }> {\n const link = await connectHttpLink(asPort(port), { maxPorts: maxPorts() });\n return {\n send: (req) => link.send(withScheme(req)),\n close: () => link.close(),\n };\n}\n"]}
|
package/dist/ws/client.d.ts
CHANGED
|
@@ -3,23 +3,38 @@ export type WebSocketConnector = (url: string, protocols: string[]) => Promise<{
|
|
|
3
3
|
port: MessagePort;
|
|
4
4
|
protocol?: string;
|
|
5
5
|
}>;
|
|
6
|
+
type IntervalTimers = {
|
|
7
|
+
setInterval(fn: () => void, ms: number): unknown;
|
|
8
|
+
clearInterval(id: unknown): void;
|
|
9
|
+
};
|
|
6
10
|
export interface CellsWebSocketOptions {
|
|
7
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Liveness ping period (ms, default 10 s); 0 disables the heartbeat. The peer is declared dead
|
|
13
|
+
* after 2 periods without a pong.
|
|
14
|
+
*/
|
|
8
15
|
heartbeatMs?: number;
|
|
9
16
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
17
|
+
* Timers for the heartbeat (and, when given, the close handshake's timeout). On a page's main
|
|
18
|
+
* thread pass worker-timers' functions: background-tab throttling of main-thread timers would
|
|
12
19
|
* otherwise delay pings and make the server drop a live socket. Workers and Node do not
|
|
13
20
|
* throttle, so the built-in timers are the default.
|
|
14
21
|
*/
|
|
15
|
-
timers?: {
|
|
16
|
-
|
|
17
|
-
|
|
22
|
+
timers?: IntervalTimers & {
|
|
23
|
+
setTimeout?(fn: () => void, ms: number): unknown;
|
|
24
|
+
clearTimeout?(id: unknown): void;
|
|
18
25
|
};
|
|
26
|
+
/** How long `close()` waits for the server's echo before closing with 1006 (default 5 s). */
|
|
27
|
+
closeTimeoutMs?: number;
|
|
19
28
|
}
|
|
20
29
|
/**
|
|
21
|
-
* A WHATWG `WebSocket` class whose sockets reach a Worker through `connect
|
|
22
|
-
*
|
|
30
|
+
* A WHATWG `WebSocket` class whose sockets reach a Worker through `connect` (page →
|
|
31
|
+
* ServiceWorker → the shard worker that owns the object, or in-process). It is
|
|
32
|
+
* `@statewalker/webrun-http-browser`'s client (`createWebSocketClass`): the socket's frames on its
|
|
33
|
+
* MessagePort are that package's (version `WS_PORT_PROTOCOL_VERSION`), which the server ends of
|
|
34
|
+
* cellb's WebSocketPairs speak. Installed for user code through webrun-modules `~deps` (provided
|
|
35
|
+
* `WebSocket`), so the real `globalThis.WebSocket` is left untouched. Typed as the platform's
|
|
36
|
+
* `WebSocket` (the class implements that interface).
|
|
23
37
|
*/
|
|
24
38
|
export declare function createWebSocketClass(connect: WebSocketConnector, options?: CellsWebSocketOptions): typeof WebSocket;
|
|
25
39
|
export type CellsWebSocketClass = ReturnType<typeof createWebSocketClass>;
|
|
40
|
+
export {};
|
package/dist/ws/client.js
CHANGED
|
@@ -1,203 +1,64 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createWebSocketClass as createPortWebSocketClass } from "@statewalker/webrun-http-browser/sw";
|
|
2
|
+
/** The longest period a timer takes (2³¹−1 ms, about 24.8 days): `heartbeatMs: 0` pings that rarely. */
|
|
3
|
+
const LONGEST_PERIOD_MS = 2 ** 31 - 1;
|
|
2
4
|
/**
|
|
3
|
-
* A WHATWG
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* A WHATWG `WebSocket` class whose sockets reach a Worker through `connect`. Typed as the
|
|
12
|
-
* platform's `WebSocket` (the class implements that interface; its own state is private).
|
|
5
|
+
* A WHATWG `WebSocket` class whose sockets reach a Worker through `connect` (page →
|
|
6
|
+
* ServiceWorker → the shard worker that owns the object, or in-process). It is
|
|
7
|
+
* `@statewalker/webrun-http-browser`'s client (`createWebSocketClass`): the socket's frames on its
|
|
8
|
+
* MessagePort are that package's (version `WS_PORT_PROTOCOL_VERSION`), which the server ends of
|
|
9
|
+
* cellb's WebSocketPairs speak. Installed for user code through webrun-modules `~deps` (provided
|
|
10
|
+
* `WebSocket`), so the real `globalThis.WebSocket` is left untouched. Typed as the platform's
|
|
11
|
+
* `WebSocket` (the class implements that interface).
|
|
13
12
|
*/
|
|
14
13
|
export function createWebSocketClass(connect, options = {}) {
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
/** Sizes of the messages posted and not acknowledged yet, in order (the port keeps it). */
|
|
35
|
-
#unacked = [];
|
|
36
|
-
/** Bytes sent and not yet received by the server end (it acknowledges each message). */
|
|
37
|
-
get bufferedAmount() {
|
|
38
|
-
return this.#buffered;
|
|
39
|
-
}
|
|
40
|
-
#binaryType = "blob";
|
|
41
|
-
get binaryType() {
|
|
42
|
-
return this.#binaryType;
|
|
43
|
-
}
|
|
44
|
-
/** Only "blob" and "arraybuffer" are taken; any other value is ignored (WHATWG). */
|
|
45
|
-
set binaryType(v) {
|
|
46
|
-
if (v === "blob" || v === "arraybuffer")
|
|
47
|
-
this.#binaryType = v;
|
|
48
|
-
}
|
|
49
|
-
onopen = null;
|
|
50
|
-
onmessage = null;
|
|
51
|
-
onclose = null;
|
|
52
|
-
onerror = null;
|
|
53
|
-
#port;
|
|
54
|
-
#pendingClose;
|
|
55
|
-
#beat;
|
|
56
|
-
#lastPong = 0;
|
|
57
|
-
#startHeartbeat() {
|
|
58
|
-
if (!heartbeatMs)
|
|
59
|
-
return;
|
|
60
|
-
this.#lastPong = Date.now();
|
|
61
|
-
this.#beat = timers.setInterval(() => {
|
|
62
|
-
if (this.readyState >= 3)
|
|
63
|
-
return this.#stopHeartbeat();
|
|
64
|
-
if (Date.now() - this.#lastPong > 2 * heartbeatMs)
|
|
65
|
-
return this.#fail(); // peer gone
|
|
66
|
-
this.#port?.postMessage({ t: "p", hb: heartbeatMs });
|
|
67
|
-
}, heartbeatMs);
|
|
68
|
-
this.#port?.postMessage({ t: "p", hb: heartbeatMs });
|
|
69
|
-
}
|
|
70
|
-
#stopHeartbeat() {
|
|
71
|
-
if (this.#beat !== undefined)
|
|
72
|
-
timers.clearInterval(this.#beat);
|
|
73
|
-
this.#beat = undefined;
|
|
74
|
-
}
|
|
14
|
+
const { timers } = options;
|
|
15
|
+
const setTimer = timers?.setTimeout?.bind(timers) ?? ((fn, ms) => setTimeout(fn, ms));
|
|
16
|
+
const clearTimer = timers?.clearTimeout?.bind(timers) ??
|
|
17
|
+
((id) => clearTimeout(id));
|
|
18
|
+
const Base = createPortWebSocketClass(async (url, protocols) => {
|
|
19
|
+
const { port, protocol } = await connect(url, protocols);
|
|
20
|
+
return { port, protocol: protocol ?? "" };
|
|
21
|
+
}, {
|
|
22
|
+
heartbeatMs: options.heartbeatMs === 0 ? LONGEST_PERIOD_MS : options.heartbeatMs,
|
|
23
|
+
closeTimeoutMs: options.closeTimeoutMs,
|
|
24
|
+
timers: timers && {
|
|
25
|
+
setInterval: (fn, ms) => timers.setInterval(fn, ms),
|
|
26
|
+
clearInterval: (id) => timers.clearInterval(id),
|
|
27
|
+
setTimeout: setTimer,
|
|
28
|
+
clearTimeout: clearTimer,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
// Two of cellb's own client's rules the package's does not apply.
|
|
32
|
+
class CellsWebSocket extends Base {
|
|
75
33
|
constructor(url, protocols = []) {
|
|
76
|
-
super();
|
|
77
|
-
// WHATWG "new WebSocket(url, protocols)" validation, in spec order.
|
|
78
|
-
let u;
|
|
79
|
-
try {
|
|
80
|
-
u = new URL(String(url), globalThis.location?.href ?? "http://localhost/");
|
|
81
|
-
}
|
|
82
|
-
catch {
|
|
83
|
-
throw new DOMException(`The URL '${String(url)}' is invalid.`, "SyntaxError");
|
|
84
|
-
}
|
|
85
|
-
if (u.protocol === "http:")
|
|
86
|
-
u.protocol = "ws:";
|
|
87
|
-
if (u.protocol === "https:")
|
|
88
|
-
u.protocol = "wss:";
|
|
89
|
-
if (u.protocol !== "ws:" && u.protocol !== "wss:")
|
|
90
|
-
throw new DOMException(`The URL's scheme must be either 'ws' or 'wss'.`, "SyntaxError");
|
|
91
|
-
if (u.hash || String(url).includes("#"))
|
|
92
|
-
throw new DOMException("The URL contains a fragment identifier.", "SyntaxError");
|
|
93
34
|
const list = typeof protocols === "string" ? [protocols] : [...protocols];
|
|
35
|
+
// Subprotocols are duplicates case-insensitively (WHATWG; WPT
|
|
36
|
+
// Create-protocols-repeated-case-insensitive).
|
|
94
37
|
const seen = new Set();
|
|
95
38
|
for (const p of list) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (seen.has(p.toLowerCase()))
|
|
39
|
+
const key = p.toLowerCase();
|
|
40
|
+
if (seen.has(key))
|
|
99
41
|
throw new DOMException(`The subprotocol '${p}' is duplicated.`, "SyntaxError");
|
|
100
|
-
seen.add(
|
|
42
|
+
seen.add(key);
|
|
101
43
|
}
|
|
102
|
-
|
|
103
|
-
connect(this.url, list).then(({ port, protocol }) => this.#opened(port, protocol ?? ""), () => this.#fail());
|
|
104
|
-
}
|
|
105
|
-
#emit(ev, handler) {
|
|
106
|
-
handler?.call(this, ev);
|
|
107
|
-
this.dispatchEvent(ev);
|
|
108
|
-
}
|
|
109
|
-
#opened(port, protocol) {
|
|
110
|
-
if (this.readyState === 3) {
|
|
111
|
-
port.close();
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
this.#port = port;
|
|
115
|
-
this.protocol = protocol;
|
|
116
|
-
port.onmessage = (e) => this.#frame(e.data);
|
|
117
|
-
port.start?.();
|
|
118
|
-
if (this.#pendingClose) {
|
|
119
|
-
// close() was called while connecting
|
|
120
|
-
this.readyState = 2;
|
|
121
|
-
port.postMessage({
|
|
122
|
-
t: "c",
|
|
123
|
-
code: this.#pendingClose.code ?? 1000,
|
|
124
|
-
reason: this.#pendingClose.reason ?? "",
|
|
125
|
-
});
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
this.readyState = 1;
|
|
129
|
-
this.#startHeartbeat();
|
|
130
|
-
this.#emit(new Event("open"), this.onopen);
|
|
131
|
-
}
|
|
132
|
-
#fail() {
|
|
133
|
-
if (this.readyState === 3)
|
|
134
|
-
return;
|
|
135
|
-
this.#stopHeartbeat();
|
|
136
|
-
this.#port?.close();
|
|
137
|
-
this.readyState = 3;
|
|
138
|
-
this.#emit(new Event("error"), this.onerror);
|
|
139
|
-
this.#emit(new CellsCloseEvent("close", { code: 1006, reason: "", wasClean: false }), this.onclose);
|
|
140
|
-
}
|
|
141
|
-
#frame(f) {
|
|
142
|
-
if (f.t === "q") {
|
|
143
|
-
this.#lastPong = Date.now();
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
if (f.t === "p")
|
|
147
|
-
return;
|
|
148
|
-
if (f.t === "a") {
|
|
149
|
-
this.#buffered -= this.#unacked.shift() ?? 0;
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
if (f.t === "m") {
|
|
153
|
-
if (this.readyState !== 1)
|
|
154
|
-
return;
|
|
155
|
-
let data = f.d;
|
|
156
|
-
if (typeof data !== "string" && this.binaryType === "blob")
|
|
157
|
-
data = new Blob([data]);
|
|
158
|
-
this.#emit(new MessageEvent("message", { data, origin: new URL(this.url).origin }), this.onmessage);
|
|
159
|
-
}
|
|
160
|
-
else {
|
|
161
|
-
const echo = this.readyState === 1;
|
|
162
|
-
if (echo)
|
|
163
|
-
this.#port.postMessage({ t: "c", code: f.code, reason: f.reason }); // close handshake
|
|
164
|
-
this.readyState = 3;
|
|
165
|
-
this.#stopHeartbeat();
|
|
166
|
-
this.#port.close();
|
|
167
|
-
this.#emit(new CellsCloseEvent("close", { code: f.code, reason: f.reason, wasClean: true }), this.onclose);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
send(data) {
|
|
171
|
-
if (this.readyState === 0)
|
|
172
|
-
throw new DOMException("Still in CONNECTING state.", "InvalidStateError");
|
|
173
|
-
if (this.readyState !== 1)
|
|
174
|
-
return; // spec: silently discarded after closing
|
|
175
|
-
if (data instanceof Blob) {
|
|
176
|
-
this.#buffered += data.size;
|
|
177
|
-
void data.arrayBuffer().then((b) => this.readyState === 1 && this.#post(b));
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
const d = toFrameData(data);
|
|
181
|
-
this.#buffered += byteSize(d);
|
|
182
|
-
this.#post(d);
|
|
183
|
-
}
|
|
184
|
-
#post(d) {
|
|
185
|
-
this.#unacked.push(byteSize(d));
|
|
186
|
-
this.#port.postMessage({ t: "m", d }, typeof d === "string" ? [] : [d]);
|
|
187
|
-
}
|
|
188
|
-
close(code, reason) {
|
|
189
|
-
checkClose(code, reason);
|
|
190
|
-
if (this.readyState >= 2)
|
|
191
|
-
return;
|
|
192
|
-
if (this.readyState === 0) {
|
|
193
|
-
this.#pendingClose = { code, reason };
|
|
194
|
-
this.readyState = 2;
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
this.readyState = 2;
|
|
198
|
-
this.#port.postMessage({ t: "c", code: code ?? 1005, reason: reason ?? "" });
|
|
44
|
+
super(resolved(url), list);
|
|
199
45
|
}
|
|
200
46
|
}
|
|
201
47
|
return CellsWebSocket;
|
|
202
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* A relative url resolved against the page, or, where there is none (Node, a worker without
|
|
51
|
+
* `location`), against `http://localhost/`, as cellb's client always did. An url that does not
|
|
52
|
+
* parse is passed on, for the class to refuse with its SyntaxError.
|
|
53
|
+
*/
|
|
54
|
+
function resolved(url) {
|
|
55
|
+
if (globalThis.location)
|
|
56
|
+
return url;
|
|
57
|
+
try {
|
|
58
|
+
return new URL(String(url), "http://localhost/").href;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return url;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
203
64
|
//# sourceMappingURL=client.js.map
|