onelobby 0.5.0 → 0.6.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.
Files changed (99) hide show
  1. package/README.md +2 -2
  2. package/blueprint.schema.json +8 -1
  3. package/dist/blueprint.d.ts +4 -0
  4. package/dist/blueprint.js +5 -0
  5. package/dist/blueprint.js.map +1 -1
  6. package/dist/codec.d.ts +23 -0
  7. package/dist/codec.js +28 -0
  8. package/dist/codec.js.map +1 -1
  9. package/dist/direct/browser-host.d.ts +46 -0
  10. package/dist/direct/browser-host.js +93 -0
  11. package/dist/direct/browser-host.js.map +1 -0
  12. package/dist/direct/hub-host.d.ts +34 -23
  13. package/dist/direct/hub-host.js +75 -59
  14. package/dist/direct/hub-host.js.map +1 -1
  15. package/dist/direct/hub-local.d.ts +22 -0
  16. package/dist/direct/hub-local.js +46 -0
  17. package/dist/direct/hub-local.js.map +1 -0
  18. package/dist/direct/hub.d.ts +37 -10
  19. package/dist/direct/hub.js +114 -23
  20. package/dist/direct/hub.js.map +1 -1
  21. package/dist/direct/index.d.ts +36 -3
  22. package/dist/direct/index.js +146 -20
  23. package/dist/direct/index.js.map +1 -1
  24. package/dist/direct/metrics.d.ts +12 -0
  25. package/dist/direct/metrics.js +45 -0
  26. package/dist/direct/metrics.js.map +1 -0
  27. package/dist/direct/portmap.d.ts +9 -2
  28. package/dist/direct/portmap.js +7 -2
  29. package/dist/direct/portmap.js.map +1 -1
  30. package/dist/direct/sha1.d.ts +16 -0
  31. package/dist/direct/sha1.js +111 -0
  32. package/dist/direct/sha1.js.map +1 -0
  33. package/dist/direct-address.d.ts +2 -2
  34. package/dist/direct-address.js.map +1 -1
  35. package/dist/friendly.d.ts +23 -0
  36. package/dist/friendly.js +28 -0
  37. package/dist/friendly.js.map +1 -1
  38. package/dist/index.d.ts +8 -2
  39. package/dist/index.js +6 -2
  40. package/dist/index.js.map +1 -1
  41. package/dist/internals.d.ts +9 -0
  42. package/dist/internals.js +9 -0
  43. package/dist/internals.js.map +1 -1
  44. package/dist/join-code-kind.d.ts +10 -0
  45. package/dist/join-code-kind.js +10 -0
  46. package/dist/join-code-kind.js.map +1 -0
  47. package/dist/joincode.d.ts +10 -1
  48. package/dist/joincode.js +23 -3
  49. package/dist/joincode.js.map +1 -1
  50. package/dist/lobby.d.ts +65 -4
  51. package/dist/lobby.js +151 -12
  52. package/dist/lobby.js.map +1 -1
  53. package/dist/lockstep.d.ts +8 -0
  54. package/dist/lockstep.js +23 -2
  55. package/dist/lockstep.js.map +1 -1
  56. package/dist/nostr/crypto.d.ts +20 -0
  57. package/dist/nostr/crypto.js +78 -0
  58. package/dist/nostr/crypto.js.map +1 -0
  59. package/dist/nostr/host.d.ts +66 -0
  60. package/dist/nostr/host.js +196 -0
  61. package/dist/nostr/host.js.map +1 -0
  62. package/dist/nostr/limits.d.ts +31 -0
  63. package/dist/nostr/limits.js +36 -0
  64. package/dist/nostr/limits.js.map +1 -0
  65. package/dist/nostr/link.d.ts +70 -0
  66. package/dist/nostr/link.js +180 -0
  67. package/dist/nostr/link.js.map +1 -0
  68. package/dist/nostr/relays.d.ts +79 -0
  69. package/dist/nostr/relays.js +315 -0
  70. package/dist/nostr/relays.js.map +1 -0
  71. package/dist/nostr/room.d.ts +43 -0
  72. package/dist/nostr/room.js +104 -0
  73. package/dist/nostr/room.js.map +1 -0
  74. package/dist/nostr/secp256k1.d.ts +10 -0
  75. package/dist/nostr/secp256k1.js +171 -0
  76. package/dist/nostr/secp256k1.js.map +1 -0
  77. package/dist/nostr/socket.d.ts +48 -0
  78. package/dist/nostr/socket.js +141 -0
  79. package/dist/nostr/socket.js.map +1 -0
  80. package/dist/onelobby.bundle.js +146 -0
  81. package/dist/onelobby.bundle.js.map +7 -0
  82. package/dist/onelobby.bundle.mjs +11 -5
  83. package/dist/onelobby.bundle.mjs.map +4 -4
  84. package/dist/onelobby.d.ts +31 -3
  85. package/dist/onelobby.global.js +2599 -46
  86. package/dist/onelobby.global.js.map +4 -4
  87. package/dist/onelobby.global.min.js +11 -5
  88. package/dist/onelobby.global.min.js.map +4 -4
  89. package/dist/onelobby.js +68 -14
  90. package/dist/onelobby.js.map +1 -1
  91. package/dist/pause.d.ts +85 -0
  92. package/dist/pause.js +160 -0
  93. package/dist/pause.js.map +1 -0
  94. package/dist/transport.d.ts +3 -1
  95. package/dist/transport.js.map +1 -1
  96. package/dist/webrtc.d.ts +1 -0
  97. package/dist/webrtc.js +6 -1
  98. package/dist/webrtc.js.map +1 -1
  99. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-host.js","sourceRoot":"","sources":["../../src/direct/browser-host.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `kit.hostNostr()`: host a direct lobby from a browser tab (or any runtime with WebSocket + WebRTC), with no Node process\n * and no listening port. The signaling hub runs inside the page, the host's own socket is wired to it in memory, and\n * joiners reach it through public Nostr relays with a normal dc1 version 1 code (spec/nostr-signaling.md). There is no IP\n * fallback: with no relay reachable it rejects with `direct_unreachable`. Nothing here imports a Node module.\n */\nimport type { HostOptions, OneLobby } from \"../onelobby.js\";\nimport type { Lobby, RateLimit } from \"../lobby.js\";\nimport { encodeRoomCode } from \"../joincode.js\";\nimport { NostrHostBridge } from \"../nostr/host.js\";\nimport { HOST_NOSTR_FIRST_RELAY_MS } from \"../nostr/limits.js\";\nimport { DEFAULT_NOSTR_RELAYS, DEFAULT_NOSTR_STUN } from \"../nostr/relays.js\";\nimport { OneLobbyError } from \"../signaling.js\";\nimport { SignalingHub, directHubOptions } from \"./hub.js\";\nimport { LocalHubSocket } from \"./hub-local.js\";\n\n/** The slice of `document` the visibility watcher uses (a test seam; default `globalThis.document`). */\nexport interface VisibilityDoc {\n visibilityState?: string;\n addEventListener(type: \"visibilitychange\", listener: () => void): void;\n removeEventListener(type: \"visibilitychange\", listener: () => void): void;\n}\n\nexport interface BrowserHostOptions {\n /** Optional; joiners must send the same one or get `bad_password`. */\n password?: string;\n maxPlayers?: number;\n /** Lobby name; default \"<displayName>'s game\". */\n name?: string;\n lateJoin?: boolean;\n meta?: Record<string, string>;\n /** Per-joiner cap on incoming raw messages, e.g. `{ messages: 60, perMs: 1000 }`; the excess is dropped. */\n rateLimit?: RateLimit;\n /** Relays (default: the blueprint's `hosting.nostrRelays`, else the library list) and STUN servers (default Google's and Cloudflare's). */\n nostr?: { relays?: string[]; stun?: string[] };\n /** Receives the startup line (`lobby.directHostLog`). Default `console.log`; `false` prints nothing. */\n log?: ((line: string) => void) | false;\n /** Receives the warning printed when the host tab goes hidden. Default `console.warn`; `false` prints nothing. */\n warn?: ((line: string) => void) | false;\n /** How long to wait for a first relay before rejecting with `direct_unreachable` (default 10000). */\n relayTimeoutMs?: number;\n /** Test seam: the document whose `visibilityState` is watched. Default `globalThis.document`. */\n document?: VisibilityDoc | null;\n}\n\n/** A browser host lobby: the normal Lobby (`hostHidden` / `hostHiddenChanged` tell when the tab is throttled). */\nexport type BrowserHostLobby = Lobby;\n\n/**\n * The startup line of a browser host, same family as `formatHostLog`:\n * `OneLobby: browser host (no port); code 7HQ-4K2-... (Nostr); nostr 4/5 relays`.\n */\nexport function formatBrowserHostLog(code: string, relays: string): string {\n return `OneLobby: browser host (no port); code ${code} (Nostr); nostr ${relays} relays`;\n}\n\nexport async function hostNostr(kit: OneLobby, opts: BrowserHostOptions = {}): Promise<BrowserHostLobby> {\n const bp = kit.blueprint;\n if (bp && !bp.hosting.nostr) {\n throw new OneLobbyError(\"nostr_disabled\", \"this game has Nostr signaling turned off (hosting.nostr is false), and a browser host has no other way to be reached\");\n }\n const defined = Object.fromEntries(Object.entries(opts).filter(([, v]) => v !== undefined)) as BrowserHostOptions;\n const o: BrowserHostOptions = bp\n ? {\n maxPlayers: bp.lobby.maxPlayers,\n lateJoin: bp.lobby.lateJoin,\n meta: { ...bp.lobby.meta },\n ...(bp.session.rateLimit ? { rateLimit: { ...bp.session.rateLimit } } : {}),\n ...defined,\n nostr: { ...defined.nostr, relays: defined.nostr?.relays ?? [...bp.hosting.nostrRelays] },\n }\n : defined;\n const maxPlayers = o.maxPlayers ?? 4;\n const relays = o.nostr?.relays && o.nostr.relays.length > 0 ? o.nostr.relays : [...DEFAULT_NOSTR_RELAYS];\n const stunUrls = o.nostr?.stun ?? [...DEFAULT_NOSTR_STUN];\n\n const hub = new SignalingHub({ ...directHubOptions(maxPlayers, o.password), stunUrls });\n const ticker = setInterval(() => hub.tick(), 1000);\n (ticker as { unref?: () => void }).unref?.();\n const code = encodeRoomCode();\n const env = kit.nostrHostEnv();\n let bridge: NostrHostBridge | undefined;\n const teardown = () => {\n clearInterval(ticker);\n bridge?.close();\n };\n try {\n try {\n bridge = await NostrHostBridge.start(hub, { code, relays, connect: env.connect, clock: env.clock, wall: env.wall, maxPlayers });\n } catch (e) {\n throw new OneLobbyError(\"direct_unreachable\", `cannot reach any Nostr relay (${e instanceof Error ? e.message : String(e)}); a browser host has no other way to be reached`);\n }\n if (!(await bridge.settle(o.relayTimeoutMs ?? HOST_NOSTR_FIRST_RELAY_MS))) {\n throw new OneLobbyError(\"direct_unreachable\", `no Nostr relay reachable (tried ${bridge.relaysTotal}); a browser host has no IP fallback, check the connection or pass other relays`);\n }\n\n const name = o.name ?? `${kit.options.displayName || \"Player\"}'s game`;\n const h: HostOptions = { name, maxPlayers, lateJoin: o.lateJoin ?? true, meta: o.meta, rateLimit: o.rateLimit };\n // joiners have no address to dial: links use the hub's STUN list, no port pin\n const lobby = await kit.hostDirectWith(h, { socketFactory: () => new LocalHubSocket(hub), wrapLinks: (inner) => inner });\n lobby.linkAddressSink = (peerId, ip) => hub.reportLinkAddress(peerId, ip);\n lobby.on(\"sessionEnded\", teardown);\n\n lobby.joinCode = code;\n lobby.joinCodeKind = \"nostr\";\n lobby.directCode = \"\";\n lobby.directCodeKind = \"none\";\n lobby.portMapping = \"off\";\n lobby.directHostLog = formatBrowserHostLog(code, `${bridge.relaysConnected}/${bridge.relaysTotal}`);\n watchVisibility(lobby, o);\n if (o.log !== false) (o.log ?? console.log)(lobby.directHostLog);\n return lobby;\n } catch (e) {\n teardown();\n throw e;\n }\n}\n\n/** Sets `lobby.hostHidden` and warns when the tab goes hidden: browsers throttle timers there (about 1/s, ~1/min after 5 min). */\nfunction watchVisibility(lobby: Lobby, o: BrowserHostOptions): void {\n const doc: VisibilityDoc | null | undefined = o.document !== undefined ? o.document : (globalThis as { document?: VisibilityDoc }).document;\n if (!doc || typeof doc.addEventListener !== \"function\") return;\n const warn = o.warn === false ? null : (o.warn ?? ((s: string) => console.warn(s)));\n const check = () => {\n const hidden = doc.visibilityState === \"hidden\";\n const changed = hidden !== lobby.hostHidden;\n lobby.setHostHidden(hidden);\n if (changed && hidden) warn?.(\"OneLobby: the host tab is hidden; the browser slows its timers, so players may lag or drop. Keep this tab visible while hosting.\");\n };\n doc.addEventListener(\"visibilitychange\", check);\n lobby.on(\"sessionEnded\", () => doc.removeEventListener(\"visibilitychange\", check));\n check();\n}\n"]}
@@ -1,6 +1,11 @@
1
1
  import type { SocketLike } from "../transport.js";
2
+ import type { IpCodeKind, JoinCodeKind } from "../join-code-kind.js";
2
3
  import { SignalingHub, type HubDeps, type HubOptions } from "./hub.js";
4
+ import { LocalHubSocket } from "./hub-local.js";
3
5
  import { PortMapper, type PortMapperOptions, type PortMapResult } from "./portmap.js";
6
+ import type { NostrHostBridge } from "../nostr/host.js";
7
+ /** A joiner socket with more than this much unsent data is closed (1008 "backpressure"); spec/signaling.md. */
8
+ export declare const MAX_PENDING_WRITE_BYTES: number;
4
9
  /**
5
10
  * Whether a WebSocket handshake carrying `origin` is accepted. A web page can open a WebSocket to any address it can
6
11
  * reach, so a browser's Origin must be one we expect: in `allowed` ("*" = all), the same host:port as the request's
@@ -8,6 +13,8 @@ import { PortMapper, type PortMapperOptions, type PortMapResult } from "./portma
8
13
  * no Origin header is a native client and never gets here.
9
14
  */
10
15
  export declare function originAllowed(origin: string, host: string | undefined, allowed: readonly string[]): boolean;
16
+ /** Whether `ip` (as the socket reports it) is 127.0.0.0/8, ::1 or an IPv4-mapped loopback address. */
17
+ export declare function isLoopback(ip: string): boolean;
11
18
  export declare class PortInUseError extends Error {
12
19
  readonly code = "port_in_use";
13
20
  /** Windows reserved the port (EACCES: an excluded port range, often from Hyper-V, WSL or Docker); not another program. */
@@ -18,9 +25,10 @@ export declare class PortInUseError extends Error {
18
25
  export declare function portFailureHint(anyBusy: boolean, anyReserved: boolean): string;
19
26
  /**
20
27
  * Runs a {@link SignalingHub} inside a Node process and listens for joiners' signaling WebSockets on TCP `port`: the
21
- * Node twin of C#'s `DirectHubHost` (docs/direct-ip.md s.2, s.5 step 3). A plain `net` server with a minimal HTTP/1.1
28
+ * Node twin of C#'s `DirectHubHost` (docs/design/direct-ip.md s.2, s.5 step 3). A plain `net` server with a minimal HTTP/1.1
22
29
  * upgrade and RFC 6455 framing (text, close, ping/pong, fragments; client frames must be masked), so the package keeps
23
- * zero runtime dependencies. Any path upgrades; `GET /healthz` answers `ok`. The host's own client talks to the hub in
30
+ * zero runtime dependencies. Any path upgrades; `GET /healthz` answers `ok` and `GET /metrics` the hub's Prometheus text
31
+ * (loopback callers only), as onelobby-server does, ahead of the `http` callback. The host's own client talks to the hub in
24
32
  * memory through {@link connectLocal}. `hub.tick()` runs once a second on an unref'd timer.
25
33
  */
26
34
  /** One plain-HTTP answer from the host's port, e.g. a browser join page. */
@@ -30,6 +38,23 @@ export interface HttpReply {
30
38
  /** Content-Type, e.g. "text/html; charset=utf-8". */
31
39
  type: string;
32
40
  body: string | Uint8Array;
41
+ /**
42
+ * Extra response headers, e.g. `{ "Cache-Control": "max-age=3600" }` for a static asset (default `no-store`). The
43
+ * hub owns Content-Type, Content-Length and Connection; a name or value with a line break answers 500.
44
+ */
45
+ headers?: Record<string, string>;
46
+ }
47
+ /** `GET /joininfo` on a direct host: what a page served from the host shows as its share link (`directJoinInfo()`). */
48
+ export interface DirectJoinInfo {
49
+ /** What to hand out: the Nostr code (version 1) by default, else the IP code. */
50
+ joinCode: string;
51
+ joinCodeKind: JoinCodeKind;
52
+ /** The IP code (address and port); equals `joinCode` when Nostr is off. */
53
+ directCode: string;
54
+ directCodeKind: IpCodeKind;
55
+ /** With `http`: `http://<the code's address>:<port>/`; else null. */
56
+ joinUrl: string | null;
57
+ port: number;
33
58
  }
34
59
  /** Serves plain `GET` requests on the direct port (path without the query); null answers 404. */
35
60
  export type HttpHandler = (path: string) => HttpReply | null;
@@ -45,9 +70,13 @@ export declare class DirectHubHost {
45
70
  * clients), the host's own page (Origin equal to the request's Host) and localhost pages are always allowed.
46
71
  */
47
72
  allowedOrigins: string[];
73
+ /** Served as JSON at `GET /joininfo` (404 while null); `hostDirect` keeps it current as the join code changes. */
74
+ joinInfo: DirectJoinInfo | null;
48
75
  /** The bound TCP port (the requested one, or the one the OS picked for 0). */
49
76
  readonly port: number;
50
77
  portMapper: PortMapper | null;
78
+ /** Nostr signaling for joiners that cannot reach the port (spec/nostr-signaling.md); set by `hostDirect`. */
79
+ nostr: NostrHostBridge | null;
51
80
  private readonly server;
52
81
  private readonly sockets;
53
82
  private readonly ticker;
@@ -68,33 +97,15 @@ export declare class DirectHubHost {
68
97
  * kit as `advanced.socketFactory: () => host.connectLocal()`.
69
98
  */
70
99
  connectLocal(): SocketLike;
71
- /** Asks the home router to forward TCP and UDP `port` here (UPnP, then NAT-PMP; docs/direct-ip.md s.2a). */
100
+ /** Asks the home router to forward TCP and UDP `port` here (UPnP, then NAT-PMP; docs/design/direct-ip.md s.2a). */
72
101
  openPort(options?: PortMapperOptions): Promise<PortMapResult>;
73
102
  /** Stops listening, closes every joiner socket and removes the router mapping. Every call gets the same promise. */
74
103
  close(): Promise<void>;
75
104
  private closing?;
76
105
  private closeOnce;
77
106
  private accept;
78
- /** RFC 6455 s.4.2: answers 101 with Sec-WebSocket-Accept, or 200 for /healthz, or 400/426. */
107
+ /** RFC 6455 s.4.2: answers 101 with Sec-WebSocket-Accept, or 200 for /healthz and /metrics (loopback only), or 400/426. */
79
108
  private handshake;
80
109
  private run;
81
110
  }
82
- /**
83
- * The direct-IP host's own signaling socket (C#'s `HubSignalingSocket`): sends go straight into the hub; the hub's
84
- * replies are delivered on a microtask, so callbacks never run inside the hub.
85
- */
86
- export declare class LocalHubSocket implements SocketLike {
87
- private readonly hub;
88
- /** Address the hub sees for the host itself (rate limits and kicks are per address). */
89
- static readonly ADDRESS = "local";
90
- readyState: number;
91
- onopen: SocketLike["onopen"];
92
- onmessage: SocketLike["onmessage"];
93
- onclose: SocketLike["onclose"];
94
- onerror: SocketLike["onerror"];
95
- private session;
96
- constructor(hub: SignalingHub);
97
- send(data: string): void;
98
- close(): void;
99
- private finish;
100
- }
111
+ export { LocalHubSocket };
@@ -3,10 +3,14 @@ import { createHash } from "node:crypto";
3
3
  import { createSocket } from "node:dgram";
4
4
  import { createServer } from "node:net";
5
5
  import { SignalingHub, directHubOptions } from "./hub.js";
6
+ import { LocalHubSocket } from "./hub-local.js";
6
7
  import { PortMapper } from "./portmap.js";
7
8
  const WS_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
8
9
  const MAX_HEADER_BYTES = 8192;
9
10
  const HANDSHAKE_TIMEOUT_MS = 5000;
11
+ /** A joiner socket with more than this much unsent data is closed (1008 "backpressure"); spec/signaling.md. */
12
+ export const MAX_PENDING_WRITE_BYTES = 1024 * 1024;
13
+ const BACKPRESSURE_GRACE_MS = 3000;
10
14
  const STATUS_TEXT = { 200: "OK", 301: "Moved Permanently", 302: "Found", 404: "Not Found", 500: "Internal Server Error" };
11
15
  /**
12
16
  * Whether a WebSocket handshake carrying `origin` is accepted. A web page can open a WebSocket to any address it can
@@ -30,6 +34,11 @@ export function originAllowed(origin, host, allowed) {
30
34
  return true;
31
35
  return host !== undefined && u.host.toLowerCase() === host.trim().toLowerCase();
32
36
  }
37
+ /** Whether `ip` (as the socket reports it) is 127.0.0.0/8, ::1 or an IPv4-mapped loopback address. */
38
+ export function isLoopback(ip) {
39
+ const a = ip.replace(/^::ffff:/i, "");
40
+ return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(a) || a === "::1";
41
+ }
33
42
  /**
34
43
  * Rejects (EADDRINUSE) when another program holds IPv4 TCP or UDP `port`. Windows lets the dual-stack listener bind
35
44
  * over a program on 0.0.0.0 (every IPv4 joiner would then reach that program), and the peer links need UDP `port`
@@ -68,6 +77,20 @@ export function portFailureHint(anyBusy, anyReserved) {
68
77
  return "Windows has reserved it (an excluded port range, often from Hyper-V, WSL or Docker); pick another port";
69
78
  return "each one is busy or reserved by Windows (an excluded port range); is another copy of the game hosting?";
70
79
  }
80
+ const HUB_OWNED_HEADERS = new Set(["content-type", "content-length", "connection", "transfer-encoding"]);
81
+ function replyHeaders(extra) {
82
+ const h = new Map([
83
+ ["cache-control", ["Cache-Control", "no-store"]],
84
+ ["x-content-type-options", ["X-Content-Type-Options", "nosniff"]],
85
+ ]);
86
+ for (const [k, v] of Object.entries(extra ?? {})) {
87
+ if (/[\r\n:]/.test(k) || /[\r\n]/.test(String(v)) || !k)
88
+ throw new Error("bad header");
89
+ if (!HUB_OWNED_HEADERS.has(k.toLowerCase()))
90
+ h.set(k.toLowerCase(), [k, String(v)]);
91
+ }
92
+ return [...h.values()].map(([k, v]) => `${k}: ${v}\r\n`).join("");
93
+ }
71
94
  export class DirectHubHost {
72
95
  hub;
73
96
  /**
@@ -80,9 +103,13 @@ export class DirectHubHost {
80
103
  * clients), the host's own page (Origin equal to the request's Host) and localhost pages are always allowed.
81
104
  */
82
105
  allowedOrigins = [];
106
+ /** Served as JSON at `GET /joininfo` (404 while null); `hostDirect` keeps it current as the join code changes. */
107
+ joinInfo = null;
83
108
  /** The bound TCP port (the requested one, or the one the OS picked for 0). */
84
109
  port;
85
110
  portMapper = null;
111
+ /** Nostr signaling for joiners that cannot reach the port (spec/nostr-signaling.md); set by `hostDirect`. */
112
+ nostr = null;
86
113
  server;
87
114
  sockets = new Set();
88
115
  ticker;
@@ -146,7 +173,7 @@ export class DirectHubHost {
146
173
  connectLocal() {
147
174
  return new LocalHubSocket(this.hub);
148
175
  }
149
- /** Asks the home router to forward TCP and UDP `port` here (UPnP, then NAT-PMP; docs/direct-ip.md s.2a). */
176
+ /** Asks the home router to forward TCP and UDP `port` here (UPnP, then NAT-PMP; docs/design/direct-ip.md s.2a). */
150
177
  openPort(options) {
151
178
  if (this.portMapper)
152
179
  throw new Error("the port is already being opened");
@@ -161,6 +188,7 @@ export class DirectHubHost {
161
188
  async closeOnce() {
162
189
  this.closed = true;
163
190
  clearInterval(this.ticker);
191
+ this.nostr?.close();
164
192
  const unmap = this.portMapper?.close();
165
193
  for (const s of this.sockets)
166
194
  s.end(closeFrame(1001, "host left"));
@@ -192,14 +220,14 @@ export class DirectHubHost {
192
220
  clearTimeout(timer);
193
221
  sock.off("data", onData);
194
222
  const rest = head.subarray(end + 4);
195
- if (!this.handshake(sock, head.subarray(0, end).toString("latin1")))
223
+ if (!this.handshake(sock, ip, head.subarray(0, end).toString("latin1")))
196
224
  return;
197
225
  this.run(sock, ip, rest);
198
226
  };
199
227
  sock.on("data", onData);
200
228
  }
201
- /** RFC 6455 s.4.2: answers 101 with Sec-WebSocket-Accept, or 200 for /healthz, or 400/426. */
202
- handshake(sock, head) {
229
+ /** RFC 6455 s.4.2: answers 101 with Sec-WebSocket-Accept, or 200 for /healthz and /metrics (loopback only), or 400/426. */
230
+ handshake(sock, ip, head) {
203
231
  const [requestLine, ...lines] = head.split("\r\n");
204
232
  const headers = new Map();
205
233
  for (const l of lines) {
@@ -208,26 +236,45 @@ export class DirectHubHost {
208
236
  headers.set(l.slice(0, i).trim().toLowerCase(), l.slice(i + 1).trim());
209
237
  }
210
238
  const [method, path] = requestLine.split(" ");
211
- const reply = (status, body) => sock.end(`HTTP/1.1 ${status}\r\nContent-Type: text/plain\r\nContent-Length: ${Buffer.byteLength(body)}\r\nConnection: close\r\n\r\n${body}`);
239
+ const reply = (status, body, type = "text/plain") => sock.end(`HTTP/1.1 ${status}\r\nContent-Type: ${type}\r\nContent-Length: ${Buffer.byteLength(body)}\r\nConnection: close\r\n\r\n${body}`);
240
+ // The same two endpoints as onelobby-server; /metrics stays local because this port faces the internet.
212
241
  if (method === "GET" && path === "/healthz") {
213
- reply("200 OK", "ok");
242
+ reply("200 OK", "ok\n");
243
+ return false;
244
+ }
245
+ if (method === "GET" && path === "/metrics") {
246
+ if (isLoopback(ip))
247
+ reply("200 OK", this.hub.metrics.render(), "text/plain; version=0.0.4");
248
+ else
249
+ reply("403 Forbidden", "metrics are local only (OneLobby direct host)");
214
250
  return false;
215
251
  }
216
252
  const key = headers.get("sec-websocket-key");
217
253
  const upgrade = /websocket/i.test(headers.get("upgrade") ?? "");
254
+ // The share link for a page the host serves: a player who opened it at a LAN address still shares the public one.
255
+ if (method === "GET" && !upgrade && path?.split("?")[0] === "/joininfo") {
256
+ if (this.joinInfo)
257
+ reply("200 OK", JSON.stringify(this.joinInfo), "application/json");
258
+ else
259
+ reply("404 Not Found", "no join code yet");
260
+ return false;
261
+ }
218
262
  if (method === "GET" && !upgrade && this.http && path) {
219
263
  let page = null;
264
+ let extra = "";
220
265
  try {
221
266
  page = this.http(path.split("?")[0]);
267
+ extra = replyHeaders(page?.headers);
222
268
  }
223
269
  catch {
224
270
  page = { status: 500, type: "text/plain", body: "error" };
271
+ extra = replyHeaders(undefined);
225
272
  }
226
273
  const p = page ?? { status: 404, type: "text/plain", body: "not found" };
227
274
  const body = typeof p.body === "string" ? Buffer.from(p.body, "utf8") : Buffer.from(p.body);
228
275
  const status = p.status ?? 200;
229
276
  sock.write(`HTTP/1.1 ${status} ${STATUS_TEXT[status] ?? "OK"}\r\nContent-Type: ${p.type}\r\n` +
230
- `Content-Length: ${body.length}\r\nCache-Control: no-store\r\nX-Content-Type-Options: nosniff\r\nConnection: close\r\n\r\n`);
277
+ `Content-Length: ${body.length}\r\n${extra}Connection: close\r\n\r\n`);
231
278
  sock.end(body);
232
279
  return false;
233
280
  }
@@ -246,19 +293,32 @@ export class DirectHubHost {
246
293
  }
247
294
  run(sock, ip, initial) {
248
295
  let open = true;
249
- const sendText = (text) => {
250
- if (open && !sock.destroyed)
251
- sock.write(frame(0x1, Buffer.from(text, "utf8")));
252
- };
296
+ let session = null;
253
297
  const shut = (code, reason) => {
254
298
  if (!open)
255
299
  return;
256
300
  open = false;
257
- if (!sock.destroyed)
301
+ if (sock.destroyed)
302
+ return;
303
+ // a reader that stopped reading never drains its buffer: give the close frame a moment, then cut it off
304
+ if (code === 1008 && reason === "backpressure") {
305
+ sock.write(closeFrame(code, reason));
306
+ setTimeout(() => sock.destroy(), BACKPRESSURE_GRACE_MS).unref?.();
307
+ if (session)
308
+ this.hub.disconnect(session);
309
+ }
310
+ else
258
311
  sock.end(closeFrame(code, reason));
259
312
  };
260
- let session = null;
261
- session = this.hub.connect(ip, sendText, () => shut(1000, "closed by the hub"));
313
+ const sendText = (text) => {
314
+ if (!open || sock.destroyed)
315
+ return;
316
+ sock.write(frame(0x1, Buffer.from(text, "utf8")));
317
+ // a slow or malicious reader must not grow this process's memory without bound
318
+ if (sock.writableLength > MAX_PENDING_WRITE_BYTES)
319
+ shut(1008, "backpressure");
320
+ };
321
+ session = this.hub.connect(ip, sendText, (code, reason) => shut(code ?? 1000, reason ?? "closed by the hub"));
262
322
  if (!session)
263
323
  return;
264
324
  const s = session;
@@ -414,49 +474,5 @@ class FrameReader {
414
474
  this.sink.fail(code, why);
415
475
  }
416
476
  }
417
- // ------------------------------------------------------------------------------------------------ in-memory socket
418
- /**
419
- * The direct-IP host's own signaling socket (C#'s `HubSignalingSocket`): sends go straight into the hub; the hub's
420
- * replies are delivered on a microtask, so callbacks never run inside the hub.
421
- */
422
- export class LocalHubSocket {
423
- hub;
424
- /** Address the hub sees for the host itself (rate limits and kicks are per address). */
425
- static ADDRESS = "local";
426
- readyState = 0;
427
- onopen = null;
428
- onmessage = null;
429
- onclose = null;
430
- onerror = null;
431
- session;
432
- constructor(hub) {
433
- this.hub = hub;
434
- this.session = hub.connect(LocalHubSocket.ADDRESS, (text) => queueMicrotask(() => this.readyState === 1 && this.onmessage?.({ data: text })), () => this.finish());
435
- queueMicrotask(() => {
436
- if (!this.session) {
437
- this.finish();
438
- return;
439
- }
440
- this.readyState = 1;
441
- this.onopen?.();
442
- });
443
- }
444
- send(data) {
445
- if (this.readyState === 1 && this.session)
446
- this.hub.receive(this.session, data);
447
- }
448
- close() {
449
- const s = this.session;
450
- const wasOpen = this.readyState === 1;
451
- this.finish();
452
- if (wasOpen && s)
453
- this.hub.disconnect(s);
454
- }
455
- finish() {
456
- if (this.readyState === 3)
457
- return;
458
- this.readyState = 3;
459
- queueMicrotask(() => this.onclose?.());
460
- }
461
- }
477
+ export { LocalHubSocket };
462
478
  //# sourceMappingURL=hub-host.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"hub-host.js","sourceRoot":"","sources":["../../src/direct/hub-host.ts"],"names":[],"mappings":"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","sourcesContent":["/// <reference types=\"node\" />\nimport { createHash } from \"node:crypto\";\nimport { createSocket } from \"node:dgram\";\nimport { createServer, type Server, type Socket } from \"node:net\";\nimport type { SocketLike } from \"../transport.js\";\nimport { SignalingHub, directHubOptions, type HubDeps, type HubOptions, type Session } from \"./hub.js\";\nimport { PortMapper, type PortMapperOptions, type PortMapResult } from \"./portmap.js\";\n\nconst WS_GUID = \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\";\nconst MAX_HEADER_BYTES = 8192;\nconst HANDSHAKE_TIMEOUT_MS = 5000;\nconst STATUS_TEXT: Record<number, string> = { 200: \"OK\", 301: \"Moved Permanently\", 302: \"Found\", 404: \"Not Found\", 500: \"Internal Server Error\" };\n\n/**\n * Whether a WebSocket handshake carrying `origin` is accepted. A web page can open a WebSocket to any address it can\n * reach, so a browser's Origin must be one we expect: in `allowed` (\"*\" = all), the same host:port as the request's\n * Host header (the page this host serves itself), or loopback (localhost, 127.0.0.1, [::1], any port). A request with\n * no Origin header is a native client and never gets here.\n */\nexport function originAllowed(origin: string, host: string | undefined, allowed: readonly string[]): boolean {\n if (allowed.includes(\"*\") || allowed.includes(origin)) return true;\n let u: URL;\n try {\n u = new URL(origin);\n } catch {\n return false;\n }\n if (u.protocol !== \"http:\" && u.protocol !== \"https:\") return false;\n if (u.hostname === \"localhost\" || u.hostname === \"127.0.0.1\" || u.hostname === \"[::1]\") return true;\n return host !== undefined && u.host.toLowerCase() === host.trim().toLowerCase();\n}\n\n/**\n * Rejects (EADDRINUSE) when another program holds IPv4 TCP or UDP `port`. Windows lets the dual-stack listener bind\n * over a program on 0.0.0.0 (every IPv4 joiner would then reach that program), and the peer links need UDP `port`\n * too, so both are tried and released before the real listen.\n */\nasync function probeIPv4(port: number): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n const s = createServer();\n s.once(\"error\", reject);\n s.listen({ port, host: \"0.0.0.0\", exclusive: true }, () => s.close(() => resolve()));\n });\n await new Promise<void>((resolve, reject) => {\n const u = createSocket({ type: \"udp4\" });\n u.once(\"error\", (e) => {\n u.close();\n reject(e);\n });\n u.bind({ port, address: \"0.0.0.0\", exclusive: true }, () => u.close(() => resolve()));\n });\n}\n\nexport class PortInUseError extends Error {\n readonly code = \"port_in_use\";\n /** Windows reserved the port (EACCES: an excluded port range, often from Hyper-V, WSL or Docker); not another program. */\n readonly reserved: boolean;\n constructor(port: number, detail: string) {\n const reserved = detail === \"EACCES\";\n super(`port_in_use: cannot listen on port ${port} (${detail}): ${portFailureHint(!reserved, reserved)}`);\n this.reserved = reserved;\n }\n}\n\n/** The end of a `port_in_use` message: busy (another program) and reserved (Windows) need different fixes. */\nexport function portFailureHint(anyBusy: boolean, anyReserved: boolean): string {\n if (!anyReserved) return \"is another copy of the game hosting?\";\n if (!anyBusy) return \"Windows has reserved it (an excluded port range, often from Hyper-V, WSL or Docker); pick another port\";\n return \"each one is busy or reserved by Windows (an excluded port range); is another copy of the game hosting?\";\n}\n\n/**\n * Runs a {@link SignalingHub} inside a Node process and listens for joiners' signaling WebSockets on TCP `port`: the\n * Node twin of C#'s `DirectHubHost` (docs/direct-ip.md s.2, s.5 step 3). A plain `net` server with a minimal HTTP/1.1\n * upgrade and RFC 6455 framing (text, close, ping/pong, fragments; client frames must be masked), so the package keeps\n * zero runtime dependencies. Any path upgrades; `GET /healthz` answers `ok`. The host's own client talks to the hub in\n * memory through {@link connectLocal}. `hub.tick()` runs once a second on an unref'd timer.\n */\n/** One plain-HTTP answer from the host's port, e.g. a browser join page. */\nexport interface HttpReply {\n /** Default 200. */\n status?: number;\n /** Content-Type, e.g. \"text/html; charset=utf-8\". */\n type: string;\n body: string | Uint8Array;\n}\n\n/** Serves plain `GET` requests on the direct port (path without the query); null answers 404. */\nexport type HttpHandler = (path: string) => HttpReply | null;\n\nexport class DirectHubHost {\n readonly hub: SignalingHub;\n /**\n * Plain `GET`s that are not WebSocket upgrades go here, so the host can serve its own join page: a browser opening\n * `http://<host address>:<port>/` gets a page that joins with `kit.joinDirect(location.host)`. Null (default): 426.\n */\n http: HttpHandler | null = null;\n /**\n * Browser `Origin` values allowed to open a signaling socket, exact match, \"*\" for all. No Origin header (native\n * clients), the host's own page (Origin equal to the request's Host) and localhost pages are always allowed.\n */\n allowedOrigins: string[] = [];\n /** The bound TCP port (the requested one, or the one the OS picked for 0). */\n readonly port: number;\n portMapper: PortMapper | null = null;\n private readonly server: Server;\n private readonly sockets = new Set<Socket>();\n private readonly ticker: ReturnType<typeof setInterval>;\n private readonly maxMessageBytes: number;\n private closed = false;\n\n private constructor(server: Server, port: number, hub: SignalingHub, maxMessageBytes: number) {\n this.server = server;\n this.port = port;\n this.hub = hub;\n this.maxMessageBytes = maxMessageBytes;\n this.ticker = setInterval(() => this.hub.tick(), 1000);\n this.ticker.unref?.();\n server.on(\"connection\", (sock) => this.accept(sock));\n }\n\n /**\n * Binds TCP `port` on every interface (IPv6 dual-stack where available). Hub options default to\n * {@link directHubOptions} (one lobby, no codes, no ICE servers). Rejects with {@link PortInUseError}.\n */\n static async listen(port: number, options?: Partial<HubOptions>, deps?: HubDeps & { host?: string }): Promise<DirectHubHost> {\n const opts = { ...directHubOptions(8), ...options };\n const server = createServer({ noDelay: true });\n const tryListen = (host: string, ipv6Only?: boolean) =>\n new Promise<void>((resolve, reject) => {\n const onError = (e: NodeJS.ErrnoException) => reject(e);\n server.once(\"error\", onError);\n server.listen({ port, host, ipv6Only }, () => {\n server.off(\"error\", onError);\n resolve();\n });\n });\n try {\n if (deps?.host) await tryListen(deps.host);\n else {\n if (port !== 0) await probeIPv4(port);\n try {\n await tryListen(\"::\", false);\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === \"EADDRINUSE\") throw e;\n await tryListen(\"0.0.0.0\"); // no IPv6 on this machine\n }\n }\n } catch (e) {\n throw new PortInUseError(port, (e as NodeJS.ErrnoException).code ?? String(e));\n }\n const addr = server.address();\n const bound = typeof addr === \"object\" && addr ? addr.port : port;\n return new DirectHubHost(server, bound, new SignalingHub(opts, deps), opts.maxMessageBytes);\n }\n\n /** Joiner sockets currently open. */\n get connectionCount(): number {\n return this.sockets.size;\n }\n\n /**\n * The host's own signaling socket: a WebSocket look-alike wired straight into the hub (no network). Pass it to the\n * kit as `advanced.socketFactory: () => host.connectLocal()`.\n */\n connectLocal(): SocketLike {\n return new LocalHubSocket(this.hub);\n }\n\n /** Asks the home router to forward TCP and UDP `port` here (UPnP, then NAT-PMP; docs/direct-ip.md s.2a). */\n openPort(options?: PortMapperOptions): Promise<PortMapResult> {\n if (this.portMapper) throw new Error(\"the port is already being opened\");\n this.portMapper = new PortMapper(this.port, options);\n return this.portMapper.open();\n }\n\n /** Stops listening, closes every joiner socket and removes the router mapping. Every call gets the same promise. */\n close(): Promise<void> {\n return (this.closing ??= this.closeOnce());\n }\n\n private closing?: Promise<void>;\n\n private async closeOnce(): Promise<void> {\n this.closed = true;\n clearInterval(this.ticker);\n const unmap = this.portMapper?.close();\n for (const s of this.sockets) s.end(closeFrame(1001, \"host left\"));\n await new Promise<void>((resolve) => this.server.close(() => resolve()));\n setTimeout(() => { for (const s of this.sockets) s.destroy(); }, 300).unref?.();\n await unmap;\n }\n\n // ---------------------------------------------------------------- one TCP connection\n\n private accept(sock: Socket): void {\n if (this.closed) {\n sock.destroy();\n return;\n }\n this.sockets.add(sock);\n sock.on(\"close\", () => this.sockets.delete(sock));\n sock.on(\"error\", () => {});\n const ip = (sock.remoteAddress ?? \"\").replace(/^::ffff:/, \"\");\n let head = Buffer.alloc(0);\n const timer = setTimeout(() => sock.destroy(), HANDSHAKE_TIMEOUT_MS);\n const onData = (chunk: Buffer) => {\n head = Buffer.concat([head, chunk]);\n const end = head.indexOf(\"\\r\\n\\r\\n\");\n if (end < 0) {\n if (head.length > MAX_HEADER_BYTES) sock.destroy();\n return;\n }\n clearTimeout(timer);\n sock.off(\"data\", onData);\n const rest = head.subarray(end + 4);\n if (!this.handshake(sock, head.subarray(0, end).toString(\"latin1\"))) return;\n this.run(sock, ip, rest);\n };\n sock.on(\"data\", onData);\n }\n\n /** RFC 6455 s.4.2: answers 101 with Sec-WebSocket-Accept, or 200 for /healthz, or 400/426. */\n private handshake(sock: Socket, head: string): boolean {\n const [requestLine, ...lines] = head.split(\"\\r\\n\");\n const headers = new Map<string, string>();\n for (const l of lines) {\n const i = l.indexOf(\":\");\n if (i > 0) headers.set(l.slice(0, i).trim().toLowerCase(), l.slice(i + 1).trim());\n }\n const [method, path] = requestLine.split(\" \");\n const reply = (status: string, body: string) =>\n sock.end(`HTTP/1.1 ${status}\\r\\nContent-Type: text/plain\\r\\nContent-Length: ${Buffer.byteLength(body)}\\r\\nConnection: close\\r\\n\\r\\n${body}`);\n if (method === \"GET\" && path === \"/healthz\") {\n reply(\"200 OK\", \"ok\");\n return false;\n }\n const key = headers.get(\"sec-websocket-key\");\n const upgrade = /websocket/i.test(headers.get(\"upgrade\") ?? \"\");\n if (method === \"GET\" && !upgrade && this.http && path) {\n let page: HttpReply | null = null;\n try {\n page = this.http(path.split(\"?\")[0]);\n } catch {\n page = { status: 500, type: \"text/plain\", body: \"error\" };\n }\n const p = page ?? { status: 404, type: \"text/plain\", body: \"not found\" };\n const body = typeof p.body === \"string\" ? Buffer.from(p.body, \"utf8\") : Buffer.from(p.body);\n const status = p.status ?? 200;\n sock.write(`HTTP/1.1 ${status} ${STATUS_TEXT[status] ?? \"OK\"}\\r\\nContent-Type: ${p.type}\\r\\n` +\n `Content-Length: ${body.length}\\r\\nCache-Control: no-store\\r\\nX-Content-Type-Options: nosniff\\r\\nConnection: close\\r\\n\\r\\n`);\n sock.end(body);\n return false;\n }\n if (method !== \"GET\" || !upgrade || !key) {\n reply(\"426 Upgrade Required\", \"this port speaks WebSocket (OneLobby direct host)\");\n return false;\n }\n const origin = headers.get(\"origin\");\n if (origin !== undefined && !originAllowed(origin, headers.get(\"host\"), this.allowedOrigins)) {\n reply(\"403 Forbidden\", \"this origin may not join (OneLobby direct host)\");\n return false;\n }\n const accept = createHash(\"sha1\").update(key + WS_GUID).digest(\"base64\");\n sock.write(`HTTP/1.1 101 Switching Protocols\\r\\nUpgrade: websocket\\r\\nConnection: Upgrade\\r\\nSec-WebSocket-Accept: ${accept}\\r\\n\\r\\n`);\n return true;\n }\n\n private run(sock: Socket, ip: string, initial: Buffer): void {\n let open = true;\n const sendText = (text: string) => {\n if (open && !sock.destroyed) sock.write(frame(0x1, Buffer.from(text, \"utf8\")));\n };\n const shut = (code: number, reason: string) => {\n if (!open) return;\n open = false;\n if (!sock.destroyed) sock.end(closeFrame(code, reason));\n };\n let session: Session | null = null;\n session = this.hub.connect(ip, sendText, () => shut(1000, \"closed by the hub\"));\n if (!session) return;\n const s = session;\n const reader = new FrameReader(this.maxMessageBytes + 1024, {\n text: (t) => this.hub.receive(s, t),\n ping: (p) => !sock.destroyed && sock.write(frame(0xa, p)),\n close: () => {\n shut(1000, \"\");\n this.hub.disconnect(s);\n },\n fail: (code, why) => {\n shut(code, why);\n this.hub.disconnect(s);\n },\n });\n sock.on(\"data\", (b: Buffer) => reader.push(b));\n sock.on(\"close\", () => {\n open = false;\n this.hub.disconnect(s);\n });\n if (initial.length) reader.push(initial);\n }\n}\n\n// ------------------------------------------------------------------------------------------------ RFC 6455 framing\n\n/** One unmasked server frame (FIN set). */\nfunction frame(opcode: number, payload: Buffer): Buffer {\n const n = payload.length;\n const head = n < 126 ? Buffer.from([0x80 | opcode, n]) : n < 65536 ? Buffer.alloc(4) : Buffer.alloc(10);\n if (n >= 126) {\n head[0] = 0x80 | opcode;\n if (n < 65536) {\n head[1] = 126;\n head.writeUInt16BE(n, 2);\n } else {\n head[1] = 127;\n head.writeBigUInt64BE(BigInt(n), 2);\n }\n }\n return Buffer.concat([head, payload]);\n}\n\nfunction closeFrame(code: number, reason: string): Buffer {\n const r = Buffer.from(reason.slice(0, 100), \"utf8\");\n const p = Buffer.alloc(2 + r.length);\n p.writeUInt16BE(code, 0);\n r.copy(p, 2);\n return frame(0x8, p);\n}\n\ninterface FrameSink {\n text(t: string): void;\n ping(payload: Buffer): void;\n close(): void;\n fail(code: number, why: string): void;\n}\n\n/** Incremental client-frame parser: masked frames only, text messages (fragmented or not), control frames between. */\nclass FrameReader {\n private buf: Buffer = Buffer.alloc(0);\n private parts: Buffer[] = [];\n private partBytes = 0;\n private inMessage = false;\n private binary = false;\n private done = false;\n private readonly utf8 = new TextDecoder(\"utf-8\", { fatal: true });\n\n constructor(private readonly maxMessage: number, private readonly sink: FrameSink) {}\n\n push(chunk: Buffer): void {\n if (this.done) return;\n this.buf = this.buf.length ? Buffer.concat([this.buf, chunk]) : chunk;\n while (!this.done) {\n if (this.buf.length < 2) return;\n const b0 = this.buf[0];\n const b1 = this.buf[1];\n const fin = (b0 & 0x80) !== 0;\n const opcode = b0 & 0x0f;\n if (b0 & 0x70) return this.fail(1002, \"reserved bits set\");\n if (!(b1 & 0x80)) return this.fail(1002, \"client frames must be masked\");\n let len = b1 & 0x7f;\n let off = 2;\n if (len === 126) {\n if (this.buf.length < 4) return;\n len = this.buf.readUInt16BE(2);\n off = 4;\n } else if (len === 127) {\n if (this.buf.length < 10) return;\n const big = this.buf.readBigUInt64BE(2);\n if (big > BigInt(this.maxMessage)) return this.fail(1009, \"message too big\");\n len = Number(big);\n off = 10;\n }\n if (len > this.maxMessage) return this.fail(1009, \"message too big\");\n if (this.buf.length < off + 4 + len) return;\n const mask = this.buf.subarray(off, off + 4);\n const payload = Buffer.from(this.buf.subarray(off + 4, off + 4 + len));\n for (let i = 0; i < payload.length; i++) payload[i] ^= mask[i & 3];\n this.buf = this.buf.subarray(off + 4 + len);\n if (opcode >= 0x8) {\n if (!fin || len > 125) return this.fail(1002, \"bad control frame\");\n if (opcode === 0x8) {\n this.done = true;\n this.sink.close();\n return;\n }\n if (opcode === 0x9) this.sink.ping(payload);\n continue; // pong or unknown control: ignore\n }\n if (opcode === 0x1 || opcode === 0x2) {\n if (this.inMessage) return this.fail(1002, \"new message inside a fragmented one\");\n this.inMessage = true;\n this.binary = opcode === 0x2; // spec/signaling.md: binary frames are ignored\n } else if (opcode !== 0x0 || !this.inMessage) return this.fail(1002, \"unexpected continuation\");\n this.partBytes += payload.length;\n if (this.partBytes > this.maxMessage) return this.fail(1009, \"message too big\");\n if (!this.binary) this.parts.push(payload);\n if (fin) {\n const all = this.parts.length === 1 ? this.parts[0] : Buffer.concat(this.parts);\n const skip = this.binary;\n this.parts = [];\n this.partBytes = 0;\n this.inMessage = false;\n this.binary = false;\n if (skip) continue;\n let text: string;\n try {\n text = this.utf8.decode(all);\n } catch {\n return this.fail(1007, \"invalid UTF-8\");\n }\n this.sink.text(text);\n }\n }\n }\n\n private fail(code: number, why: string): void {\n this.done = true;\n this.sink.fail(code, why);\n }\n}\n\n// ------------------------------------------------------------------------------------------------ in-memory socket\n\n/**\n * The direct-IP host's own signaling socket (C#'s `HubSignalingSocket`): sends go straight into the hub; the hub's\n * replies are delivered on a microtask, so callbacks never run inside the hub.\n */\nexport class LocalHubSocket implements SocketLike {\n /** Address the hub sees for the host itself (rate limits and kicks are per address). */\n static readonly ADDRESS = \"local\";\n readyState = 0;\n onopen: SocketLike[\"onopen\"] = null;\n onmessage: SocketLike[\"onmessage\"] = null;\n onclose: SocketLike[\"onclose\"] = null;\n onerror: SocketLike[\"onerror\"] = null;\n private session: Session | null;\n\n constructor(private readonly hub: SignalingHub) {\n this.session = hub.connect(LocalHubSocket.ADDRESS, (text) => queueMicrotask(() => this.readyState === 1 && this.onmessage?.({ data: text })), () =>\n this.finish(),\n );\n queueMicrotask(() => {\n if (!this.session) {\n this.finish();\n return;\n }\n this.readyState = 1;\n this.onopen?.();\n });\n }\n\n send(data: string): void {\n if (this.readyState === 1 && this.session) this.hub.receive(this.session, data);\n }\n\n close(): void {\n const s = this.session;\n const wasOpen = this.readyState === 1;\n this.finish();\n if (wasOpen && s) this.hub.disconnect(s);\n }\n\n private finish(): void {\n if (this.readyState === 3) return;\n this.readyState = 3;\n queueMicrotask(() => this.onclose?.());\n }\n}\n"]}
1
+ {"version":3,"file":"hub-host.js","sourceRoot":"","sources":["../../src/direct/hub-host.ts"],"names":[],"mappings":"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","sourcesContent":["/// <reference types=\"node\" />\nimport { createHash } from \"node:crypto\";\nimport { createSocket } from \"node:dgram\";\nimport { createServer, type Server, type Socket } from \"node:net\";\nimport type { SocketLike } from \"../transport.js\";\nimport type { IpCodeKind, JoinCodeKind } from \"../join-code-kind.js\";\nimport { SignalingHub, directHubOptions, type HubDeps, type HubOptions, type Session } from \"./hub.js\";\nimport { LocalHubSocket } from \"./hub-local.js\";\nimport { PortMapper,type PortMapperOptions, type PortMapResult } from \"./portmap.js\";\nimport type { NostrHostBridge } from \"../nostr/host.js\";\n\nconst WS_GUID = \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\";\nconst MAX_HEADER_BYTES = 8192;\nconst HANDSHAKE_TIMEOUT_MS = 5000;\n/** A joiner socket with more than this much unsent data is closed (1008 \"backpressure\"); spec/signaling.md. */\nexport const MAX_PENDING_WRITE_BYTES = 1024 * 1024;\nconst BACKPRESSURE_GRACE_MS = 3000;\nconst STATUS_TEXT: Record<number, string> = { 200: \"OK\", 301: \"Moved Permanently\", 302: \"Found\", 404: \"Not Found\", 500: \"Internal Server Error\" };\n\n/**\n * Whether a WebSocket handshake carrying `origin` is accepted. A web page can open a WebSocket to any address it can\n * reach, so a browser's Origin must be one we expect: in `allowed` (\"*\" = all), the same host:port as the request's\n * Host header (the page this host serves itself), or loopback (localhost, 127.0.0.1, [::1], any port). A request with\n * no Origin header is a native client and never gets here.\n */\nexport function originAllowed(origin: string, host: string | undefined, allowed: readonly string[]): boolean {\n if (allowed.includes(\"*\") || allowed.includes(origin)) return true;\n let u: URL;\n try {\n u = new URL(origin);\n } catch {\n return false;\n }\n if (u.protocol !== \"http:\" && u.protocol !== \"https:\") return false;\n if (u.hostname === \"localhost\" || u.hostname === \"127.0.0.1\" || u.hostname === \"[::1]\") return true;\n return host !== undefined && u.host.toLowerCase() === host.trim().toLowerCase();\n}\n\n/** Whether `ip` (as the socket reports it) is 127.0.0.0/8, ::1 or an IPv4-mapped loopback address. */\nexport function isLoopback(ip: string): boolean {\n const a = ip.replace(/^::ffff:/i, \"\");\n return /^127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/.test(a) || a === \"::1\";\n}\n\n/**\n * Rejects (EADDRINUSE) when another program holds IPv4 TCP or UDP `port`. Windows lets the dual-stack listener bind\n * over a program on 0.0.0.0 (every IPv4 joiner would then reach that program), and the peer links need UDP `port`\n * too, so both are tried and released before the real listen.\n */\nasync function probeIPv4(port: number): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n const s = createServer();\n s.once(\"error\", reject);\n s.listen({ port, host: \"0.0.0.0\", exclusive: true }, () => s.close(() => resolve()));\n });\n await new Promise<void>((resolve, reject) => {\n const u = createSocket({ type: \"udp4\" });\n u.once(\"error\", (e) => {\n u.close();\n reject(e);\n });\n u.bind({ port, address: \"0.0.0.0\", exclusive: true }, () => u.close(() => resolve()));\n });\n}\n\nexport class PortInUseError extends Error {\n readonly code = \"port_in_use\";\n /** Windows reserved the port (EACCES: an excluded port range, often from Hyper-V, WSL or Docker); not another program. */\n readonly reserved: boolean;\n constructor(port: number, detail: string) {\n const reserved = detail === \"EACCES\";\n super(`port_in_use: cannot listen on port ${port} (${detail}): ${portFailureHint(!reserved, reserved)}`);\n this.reserved = reserved;\n }\n}\n\n/** The end of a `port_in_use` message: busy (another program) and reserved (Windows) need different fixes. */\nexport function portFailureHint(anyBusy: boolean, anyReserved: boolean): string {\n if (!anyReserved) return \"is another copy of the game hosting?\";\n if (!anyBusy) return \"Windows has reserved it (an excluded port range, often from Hyper-V, WSL or Docker); pick another port\";\n return \"each one is busy or reserved by Windows (an excluded port range); is another copy of the game hosting?\";\n}\n\n/**\n * Runs a {@link SignalingHub} inside a Node process and listens for joiners' signaling WebSockets on TCP `port`: the\n * Node twin of C#'s `DirectHubHost` (docs/design/direct-ip.md s.2, s.5 step 3). A plain `net` server with a minimal HTTP/1.1\n * upgrade and RFC 6455 framing (text, close, ping/pong, fragments; client frames must be masked), so the package keeps\n * zero runtime dependencies. Any path upgrades; `GET /healthz` answers `ok` and `GET /metrics` the hub's Prometheus text\n * (loopback callers only), as onelobby-server does, ahead of the `http` callback. The host's own client talks to the hub in\n * memory through {@link connectLocal}. `hub.tick()` runs once a second on an unref'd timer.\n */\n/** One plain-HTTP answer from the host's port, e.g. a browser join page. */\nexport interface HttpReply {\n /** Default 200. */\n status?: number;\n /** Content-Type, e.g. \"text/html; charset=utf-8\". */\n type: string;\n body: string | Uint8Array;\n /**\n * Extra response headers, e.g. `{ \"Cache-Control\": \"max-age=3600\" }` for a static asset (default `no-store`). The\n * hub owns Content-Type, Content-Length and Connection; a name or value with a line break answers 500.\n */\n headers?: Record<string, string>;\n}\n\n/** `GET /joininfo` on a direct host: what a page served from the host shows as its share link (`directJoinInfo()`). */\nexport interface DirectJoinInfo {\n /** What to hand out: the Nostr code (version 1) by default, else the IP code. */\n joinCode: string;\n joinCodeKind: JoinCodeKind;\n /** The IP code (address and port); equals `joinCode` when Nostr is off. */\n directCode: string;\n directCodeKind: IpCodeKind;\n /** With `http`: `http://<the code's address>:<port>/`; else null. */\n joinUrl: string | null;\n port: number;\n}\n\nconst HUB_OWNED_HEADERS = new Set([\"content-type\", \"content-length\", \"connection\", \"transfer-encoding\"]);\n\nfunction replyHeaders(extra: Record<string, string> | undefined): string {\n const h = new Map<string, [string, string]>([\n [\"cache-control\", [\"Cache-Control\", \"no-store\"]],\n [\"x-content-type-options\", [\"X-Content-Type-Options\", \"nosniff\"]],\n ]);\n for (const [k, v] of Object.entries(extra ?? {})) {\n if (/[\\r\\n:]/.test(k) || /[\\r\\n]/.test(String(v)) || !k) throw new Error(\"bad header\");\n if (!HUB_OWNED_HEADERS.has(k.toLowerCase())) h.set(k.toLowerCase(), [k, String(v)]);\n }\n return [...h.values()].map(([k, v]) => `${k}: ${v}\\r\\n`).join(\"\");\n}\n\n/** Serves plain `GET` requests on the direct port (path without the query); null answers 404. */\nexport type HttpHandler = (path: string) => HttpReply | null;\n\nexport class DirectHubHost {\n readonly hub: SignalingHub;\n /**\n * Plain `GET`s that are not WebSocket upgrades go here, so the host can serve its own join page: a browser opening\n * `http://<host address>:<port>/` gets a page that joins with `kit.joinDirect(location.host)`. Null (default): 426.\n */\n http: HttpHandler | null = null;\n /**\n * Browser `Origin` values allowed to open a signaling socket, exact match, \"*\" for all. No Origin header (native\n * clients), the host's own page (Origin equal to the request's Host) and localhost pages are always allowed.\n */\n allowedOrigins: string[] = [];\n /** Served as JSON at `GET /joininfo` (404 while null); `hostDirect` keeps it current as the join code changes. */\n joinInfo: DirectJoinInfo | null = null;\n /** The bound TCP port (the requested one, or the one the OS picked for 0). */\n readonly port: number;\n portMapper: PortMapper | null = null;\n /** Nostr signaling for joiners that cannot reach the port (spec/nostr-signaling.md); set by `hostDirect`. */\n nostr: NostrHostBridge | null = null;\n private readonly server: Server;\n private readonly sockets = new Set<Socket>();\n private readonly ticker: ReturnType<typeof setInterval>;\n private readonly maxMessageBytes: number;\n private closed = false;\n\n private constructor(server: Server, port: number, hub: SignalingHub, maxMessageBytes: number) {\n this.server = server;\n this.port = port;\n this.hub = hub;\n this.maxMessageBytes = maxMessageBytes;\n this.ticker = setInterval(() => this.hub.tick(), 1000);\n this.ticker.unref?.();\n server.on(\"connection\", (sock) => this.accept(sock));\n }\n\n /**\n * Binds TCP `port` on every interface (IPv6 dual-stack where available). Hub options default to\n * {@link directHubOptions} (one lobby, no codes, no ICE servers). Rejects with {@link PortInUseError}.\n */\n static async listen(port: number, options?: Partial<HubOptions>, deps?: HubDeps & { host?: string }): Promise<DirectHubHost> {\n const opts = { ...directHubOptions(8), ...options };\n const server = createServer({ noDelay: true });\n const tryListen = (host: string, ipv6Only?: boolean) =>\n new Promise<void>((resolve, reject) => {\n const onError = (e: NodeJS.ErrnoException) => reject(e);\n server.once(\"error\", onError);\n server.listen({ port, host, ipv6Only }, () => {\n server.off(\"error\", onError);\n resolve();\n });\n });\n try {\n if (deps?.host) await tryListen(deps.host);\n else {\n if (port !== 0) await probeIPv4(port);\n try {\n await tryListen(\"::\", false);\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === \"EADDRINUSE\") throw e;\n await tryListen(\"0.0.0.0\"); // no IPv6 on this machine\n }\n }\n } catch (e) {\n throw new PortInUseError(port, (e as NodeJS.ErrnoException).code ?? String(e));\n }\n const addr = server.address();\n const bound = typeof addr === \"object\" && addr ? addr.port : port;\n return new DirectHubHost(server, bound, new SignalingHub(opts, deps), opts.maxMessageBytes);\n }\n\n /** Joiner sockets currently open. */\n get connectionCount(): number {\n return this.sockets.size;\n }\n\n /**\n * The host's own signaling socket: a WebSocket look-alike wired straight into the hub (no network). Pass it to the\n * kit as `advanced.socketFactory: () => host.connectLocal()`.\n */\n connectLocal(): SocketLike {\n return new LocalHubSocket(this.hub);\n }\n\n /** Asks the home router to forward TCP and UDP `port` here (UPnP, then NAT-PMP; docs/design/direct-ip.md s.2a). */\n openPort(options?: PortMapperOptions): Promise<PortMapResult> {\n if (this.portMapper) throw new Error(\"the port is already being opened\");\n this.portMapper = new PortMapper(this.port, options);\n return this.portMapper.open();\n }\n\n /** Stops listening, closes every joiner socket and removes the router mapping. Every call gets the same promise. */\n close(): Promise<void> {\n return (this.closing ??= this.closeOnce());\n }\n\n private closing?: Promise<void>;\n\n private async closeOnce(): Promise<void> {\n this.closed = true;\n clearInterval(this.ticker);\n this.nostr?.close();\n const unmap = this.portMapper?.close();\n for (const s of this.sockets) s.end(closeFrame(1001, \"host left\"));\n await new Promise<void>((resolve) => this.server.close(() => resolve()));\n setTimeout(() => { for (const s of this.sockets) s.destroy(); }, 300).unref?.();\n await unmap;\n }\n\n // ---------------------------------------------------------------- one TCP connection\n\n private accept(sock: Socket): void {\n if (this.closed) {\n sock.destroy();\n return;\n }\n this.sockets.add(sock);\n sock.on(\"close\", () => this.sockets.delete(sock));\n sock.on(\"error\", () => {});\n const ip = (sock.remoteAddress ?? \"\").replace(/^::ffff:/, \"\");\n let head = Buffer.alloc(0);\n const timer = setTimeout(() => sock.destroy(), HANDSHAKE_TIMEOUT_MS);\n const onData = (chunk: Buffer) => {\n head = Buffer.concat([head, chunk]);\n const end = head.indexOf(\"\\r\\n\\r\\n\");\n if (end < 0) {\n if (head.length > MAX_HEADER_BYTES) sock.destroy();\n return;\n }\n clearTimeout(timer);\n sock.off(\"data\", onData);\n const rest = head.subarray(end + 4);\n if (!this.handshake(sock, ip, head.subarray(0, end).toString(\"latin1\"))) return;\n this.run(sock, ip, rest);\n };\n sock.on(\"data\", onData);\n }\n\n /** RFC 6455 s.4.2: answers 101 with Sec-WebSocket-Accept, or 200 for /healthz and /metrics (loopback only), or 400/426. */\n private handshake(sock: Socket, ip: string, head: string): boolean {\n const [requestLine, ...lines] = head.split(\"\\r\\n\");\n const headers = new Map<string, string>();\n for (const l of lines) {\n const i = l.indexOf(\":\");\n if (i > 0) headers.set(l.slice(0, i).trim().toLowerCase(), l.slice(i + 1).trim());\n }\n const [method, path] = requestLine.split(\" \");\n const reply = (status: string, body: string, type = \"text/plain\") =>\n sock.end(`HTTP/1.1 ${status}\\r\\nContent-Type: ${type}\\r\\nContent-Length: ${Buffer.byteLength(body)}\\r\\nConnection: close\\r\\n\\r\\n${body}`);\n // The same two endpoints as onelobby-server; /metrics stays local because this port faces the internet.\n if (method === \"GET\" && path === \"/healthz\") {\n reply(\"200 OK\", \"ok\\n\");\n return false;\n }\n if (method === \"GET\" && path === \"/metrics\") {\n if (isLoopback(ip)) reply(\"200 OK\", this.hub.metrics.render(), \"text/plain; version=0.0.4\");\n else reply(\"403 Forbidden\", \"metrics are local only (OneLobby direct host)\");\n return false;\n }\n const key = headers.get(\"sec-websocket-key\");\n const upgrade = /websocket/i.test(headers.get(\"upgrade\") ?? \"\");\n // The share link for a page the host serves: a player who opened it at a LAN address still shares the public one.\n if (method === \"GET\" && !upgrade && path?.split(\"?\")[0] === \"/joininfo\") {\n if (this.joinInfo) reply(\"200 OK\", JSON.stringify(this.joinInfo), \"application/json\");\n else reply(\"404 Not Found\", \"no join code yet\");\n return false;\n }\n if (method === \"GET\" && !upgrade && this.http && path) {\n let page: HttpReply | null = null;\n let extra = \"\";\n try {\n page = this.http(path.split(\"?\")[0]);\n extra = replyHeaders(page?.headers);\n } catch {\n page = { status: 500, type: \"text/plain\", body: \"error\" };\n extra = replyHeaders(undefined);\n }\n const p = page ?? { status: 404, type: \"text/plain\", body: \"not found\" };\n const body = typeof p.body === \"string\" ? Buffer.from(p.body, \"utf8\") : Buffer.from(p.body);\n const status = p.status ?? 200;\n sock.write(`HTTP/1.1 ${status} ${STATUS_TEXT[status] ?? \"OK\"}\\r\\nContent-Type: ${p.type}\\r\\n` +\n `Content-Length: ${body.length}\\r\\n${extra}Connection: close\\r\\n\\r\\n`);\n sock.end(body);\n return false;\n }\n if (method !== \"GET\" || !upgrade || !key) {\n reply(\"426 Upgrade Required\", \"this port speaks WebSocket (OneLobby direct host)\");\n return false;\n }\n const origin = headers.get(\"origin\");\n if (origin !== undefined && !originAllowed(origin, headers.get(\"host\"), this.allowedOrigins)) {\n reply(\"403 Forbidden\", \"this origin may not join (OneLobby direct host)\");\n return false;\n }\n const accept = createHash(\"sha1\").update(key + WS_GUID).digest(\"base64\");\n sock.write(`HTTP/1.1 101 Switching Protocols\\r\\nUpgrade: websocket\\r\\nConnection: Upgrade\\r\\nSec-WebSocket-Accept: ${accept}\\r\\n\\r\\n`);\n return true;\n }\n\n private run(sock: Socket, ip: string, initial: Buffer): void {\n let open = true;\n let session: Session | null = null;\n const shut = (code: number, reason: string) => {\n if (!open) return;\n open = false;\n if (sock.destroyed) return;\n // a reader that stopped reading never drains its buffer: give the close frame a moment, then cut it off\n if (code === 1008 && reason === \"backpressure\") {\n sock.write(closeFrame(code, reason));\n setTimeout(() => sock.destroy(), BACKPRESSURE_GRACE_MS).unref?.();\n if (session) this.hub.disconnect(session);\n } else sock.end(closeFrame(code, reason));\n };\n const sendText = (text: string) => {\n if (!open || sock.destroyed) return;\n sock.write(frame(0x1, Buffer.from(text, \"utf8\")));\n // a slow or malicious reader must not grow this process's memory without bound\n if (sock.writableLength > MAX_PENDING_WRITE_BYTES) shut(1008, \"backpressure\");\n };\n session = this.hub.connect(ip, sendText, (code, reason) => shut(code ?? 1000, reason ?? \"closed by the hub\"));\n if (!session) return;\n const s = session;\n const reader = new FrameReader(this.maxMessageBytes + 1024, {\n text: (t) => this.hub.receive(s, t),\n ping: (p) => !sock.destroyed && sock.write(frame(0xa, p)),\n close: () => {\n shut(1000, \"\");\n this.hub.disconnect(s);\n },\n fail: (code, why) => {\n shut(code, why);\n this.hub.disconnect(s);\n },\n });\n sock.on(\"data\", (b: Buffer) => reader.push(b));\n sock.on(\"close\", () => {\n open = false;\n this.hub.disconnect(s);\n });\n if (initial.length) reader.push(initial);\n }\n}\n\n// ------------------------------------------------------------------------------------------------ RFC 6455 framing\n\n/** One unmasked server frame (FIN set). */\nfunction frame(opcode: number, payload: Buffer): Buffer {\n const n = payload.length;\n const head = n < 126 ? Buffer.from([0x80 | opcode, n]) : n < 65536 ? Buffer.alloc(4) : Buffer.alloc(10);\n if (n >= 126) {\n head[0] = 0x80 | opcode;\n if (n < 65536) {\n head[1] = 126;\n head.writeUInt16BE(n, 2);\n } else {\n head[1] = 127;\n head.writeBigUInt64BE(BigInt(n), 2);\n }\n }\n return Buffer.concat([head, payload]);\n}\n\nfunction closeFrame(code: number, reason: string): Buffer {\n const r = Buffer.from(reason.slice(0, 100), \"utf8\");\n const p = Buffer.alloc(2 + r.length);\n p.writeUInt16BE(code, 0);\n r.copy(p, 2);\n return frame(0x8, p);\n}\n\ninterface FrameSink {\n text(t: string): void;\n ping(payload: Buffer): void;\n close(): void;\n fail(code: number, why: string): void;\n}\n\n/** Incremental client-frame parser: masked frames only, text messages (fragmented or not), control frames between. */\nclass FrameReader {\n private buf: Buffer = Buffer.alloc(0);\n private parts: Buffer[] = [];\n private partBytes = 0;\n private inMessage = false;\n private binary = false;\n private done = false;\n private readonly utf8 = new TextDecoder(\"utf-8\", { fatal: true });\n\n constructor(private readonly maxMessage: number, private readonly sink: FrameSink) {}\n\n push(chunk: Buffer): void {\n if (this.done) return;\n this.buf = this.buf.length ? Buffer.concat([this.buf, chunk]) : chunk;\n while (!this.done) {\n if (this.buf.length < 2) return;\n const b0 = this.buf[0];\n const b1 = this.buf[1];\n const fin = (b0 & 0x80) !== 0;\n const opcode = b0 & 0x0f;\n if (b0 & 0x70) return this.fail(1002, \"reserved bits set\");\n if (!(b1 & 0x80)) return this.fail(1002, \"client frames must be masked\");\n let len = b1 & 0x7f;\n let off = 2;\n if (len === 126) {\n if (this.buf.length < 4) return;\n len = this.buf.readUInt16BE(2);\n off = 4;\n } else if (len === 127) {\n if (this.buf.length < 10) return;\n const big = this.buf.readBigUInt64BE(2);\n if (big > BigInt(this.maxMessage)) return this.fail(1009, \"message too big\");\n len = Number(big);\n off = 10;\n }\n if (len > this.maxMessage) return this.fail(1009, \"message too big\");\n if (this.buf.length < off + 4 + len) return;\n const mask = this.buf.subarray(off, off + 4);\n const payload = Buffer.from(this.buf.subarray(off + 4, off + 4 + len));\n for (let i = 0; i < payload.length; i++) payload[i] ^= mask[i & 3];\n this.buf = this.buf.subarray(off + 4 + len);\n if (opcode >= 0x8) {\n if (!fin || len > 125) return this.fail(1002, \"bad control frame\");\n if (opcode === 0x8) {\n this.done = true;\n this.sink.close();\n return;\n }\n if (opcode === 0x9) this.sink.ping(payload);\n continue; // pong or unknown control: ignore\n }\n if (opcode === 0x1 || opcode === 0x2) {\n if (this.inMessage) return this.fail(1002, \"new message inside a fragmented one\");\n this.inMessage = true;\n this.binary = opcode === 0x2; // spec/signaling.md: binary frames are ignored\n } else if (opcode !== 0x0 || !this.inMessage) return this.fail(1002, \"unexpected continuation\");\n this.partBytes += payload.length;\n if (this.partBytes > this.maxMessage) return this.fail(1009, \"message too big\");\n if (!this.binary) this.parts.push(payload);\n if (fin) {\n const all = this.parts.length === 1 ? this.parts[0] : Buffer.concat(this.parts);\n const skip = this.binary;\n this.parts = [];\n this.partBytes = 0;\n this.inMessage = false;\n this.binary = false;\n if (skip) continue;\n let text: string;\n try {\n text = this.utf8.decode(all);\n } catch {\n return this.fail(1007, \"invalid UTF-8\");\n }\n this.sink.text(text);\n }\n }\n }\n\n private fail(code: number, why: string): void {\n this.done = true;\n this.sink.fail(code, why);\n }\n}\n\nexport { LocalHubSocket };\n"]}
@@ -0,0 +1,22 @@
1
+ import type { SocketLike } from "../transport.js";
2
+ import type { SignalingHub } from "./hub.js";
3
+ /**
4
+ * A direct host's own signaling socket (C#'s `HubSignalingSocket`): sends go straight into the hub; the hub's
5
+ * replies are delivered on a microtask, so callbacks never run inside the hub. No Node APIs, so a browser tab can
6
+ * use it too (`kit.hostNostr`).
7
+ */
8
+ export declare class LocalHubSocket implements SocketLike {
9
+ private readonly hub;
10
+ /** Address the hub sees for the host itself (rate limits and kicks are per address). */
11
+ static readonly ADDRESS = "local";
12
+ readyState: number;
13
+ onopen: SocketLike["onopen"];
14
+ onmessage: SocketLike["onmessage"];
15
+ onclose: SocketLike["onclose"];
16
+ onerror: SocketLike["onerror"];
17
+ private session;
18
+ constructor(hub: SignalingHub);
19
+ send(data: string): void;
20
+ close(): void;
21
+ private finish;
22
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * A direct host's own signaling socket (C#'s `HubSignalingSocket`): sends go straight into the hub; the hub's
3
+ * replies are delivered on a microtask, so callbacks never run inside the hub. No Node APIs, so a browser tab can
4
+ * use it too (`kit.hostNostr`).
5
+ */
6
+ export class LocalHubSocket {
7
+ hub;
8
+ /** Address the hub sees for the host itself (rate limits and kicks are per address). */
9
+ static ADDRESS = "local";
10
+ readyState = 0;
11
+ onopen = null;
12
+ onmessage = null;
13
+ onclose = null;
14
+ onerror = null;
15
+ session;
16
+ constructor(hub) {
17
+ this.hub = hub;
18
+ this.session = hub.connect(LocalHubSocket.ADDRESS, (text) => queueMicrotask(() => this.readyState === 1 && this.onmessage?.({ data: text })), () => this.finish());
19
+ queueMicrotask(() => {
20
+ if (!this.session) {
21
+ this.finish();
22
+ return;
23
+ }
24
+ this.readyState = 1;
25
+ this.onopen?.();
26
+ });
27
+ }
28
+ send(data) {
29
+ if (this.readyState === 1 && this.session)
30
+ this.hub.receive(this.session, data);
31
+ }
32
+ close() {
33
+ const s = this.session;
34
+ const wasOpen = this.readyState === 1;
35
+ this.finish();
36
+ if (wasOpen && s)
37
+ this.hub.disconnect(s);
38
+ }
39
+ finish() {
40
+ if (this.readyState === 3)
41
+ return;
42
+ this.readyState = 3;
43
+ queueMicrotask(() => this.onclose?.());
44
+ }
45
+ }
46
+ //# sourceMappingURL=hub-local.js.map