neon 2.47.0 → 3.1.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 (198) hide show
  1. package/README.md +54 -0
  2. package/dist/_shared/auth_selection.js +76 -79
  3. package/dist/_shared/credentials.js +166 -187
  4. package/dist/_shared/env-core/env.js +395 -0
  5. package/dist/_shared/env-core/reuse-secrets.js +179 -0
  6. package/dist/_shared/paths.js +129 -126
  7. package/dist/_shared/profiles.js +192 -242
  8. package/dist/_shared/secure_file.js +36 -38
  9. package/dist/_virtual/_rolldown/runtime.js +13 -0
  10. package/dist/analytics.js +163 -207
  11. package/dist/api.js +815 -758
  12. package/dist/auth.js +121 -141
  13. package/dist/auth_context.js +39 -53
  14. package/dist/cli.js +4 -7
  15. package/dist/commands/api.js +220 -250
  16. package/dist/commands/api_keys.js +251 -314
  17. package/dist/commands/auth.js +283 -328
  18. package/dist/commands/bootstrap.js +372 -437
  19. package/dist/commands/branches.js +304 -455
  20. package/dist/commands/bucket.js +374 -514
  21. package/dist/commands/checkout.js +213 -298
  22. package/dist/commands/config.js +575 -658
  23. package/dist/commands/connection_string.js +137 -165
  24. package/dist/commands/data_api.js +238 -260
  25. package/dist/commands/databases.js +67 -76
  26. package/dist/commands/deploy.js +31 -25
  27. package/dist/commands/dev.js +642 -681
  28. package/dist/commands/diff.js +156 -200
  29. package/dist/commands/env.js +243 -303
  30. package/dist/commands/functions.js +275 -341
  31. package/dist/commands/index.js +70 -65
  32. package/dist/commands/init.js +84 -119
  33. package/dist/commands/inspect.js +55 -55
  34. package/dist/commands/ip_allow.js +88 -120
  35. package/dist/commands/link.js +874 -1019
  36. package/dist/commands/logs.js +291 -0
  37. package/dist/commands/neon_auth.js +725 -933
  38. package/dist/commands/operations.js +34 -25
  39. package/dist/commands/orgs.js +28 -18
  40. package/dist/commands/profile.js +614 -845
  41. package/dist/commands/projects.js +313 -373
  42. package/dist/commands/psql.js +60 -58
  43. package/dist/commands/roles.js +55 -58
  44. package/dist/commands/schema_diff.js +87 -131
  45. package/dist/commands/set_context.js +34 -26
  46. package/dist/commands/snapshots.js +288 -413
  47. package/dist/commands/status.js +41 -37
  48. package/dist/commands/user.js +21 -10
  49. package/dist/commands/vpc_endpoints.js +85 -113
  50. package/dist/config.js +7 -6
  51. package/dist/config_format.js +50 -66
  52. package/dist/config_template.js +128 -157
  53. package/dist/context.js +183 -235
  54. package/dist/current_branch_fast_path.js +40 -49
  55. package/dist/dev/env.js +313 -394
  56. package/dist/dev/functions.js +54 -64
  57. package/dist/dev/inputs.js +46 -58
  58. package/dist/dev/runtime.js +135 -164
  59. package/dist/dev/websocket.js +766 -959
  60. package/dist/env.js +27 -33
  61. package/dist/env_file.js +118 -132
  62. package/dist/env_services.js +36 -38
  63. package/dist/errors.js +57 -68
  64. package/dist/functions_api.js +45 -43
  65. package/dist/help.js +189 -140
  66. package/dist/index.js +182 -257
  67. package/dist/init/agents.js +137 -118
  68. package/dist/init/auth.js +58 -68
  69. package/dist/init/bootstrap.js +325 -396
  70. package/dist/init/build_config.js +4 -2
  71. package/dist/init/detect_agent.js +56 -101
  72. package/dist/init/editors.js +35 -52
  73. package/dist/init/enrich_output.js +51 -66
  74. package/dist/init/extension.js +134 -171
  75. package/dist/init/inspect.js +179 -266
  76. package/dist/init/interactive.js +510 -622
  77. package/dist/init/neonctl.js +117 -168
  78. package/dist/init/orchestrate.js +157 -173
  79. package/dist/init/phases/auth.js +188 -202
  80. package/dist/init/phases/cleanup.js +23 -23
  81. package/dist/init/phases/db.js +251 -277
  82. package/dist/init/phases/getting_started.js +213 -223
  83. package/dist/init/phases/mcp.js +174 -224
  84. package/dist/init/phases/migrations.js +247 -248
  85. package/dist/init/phases/neon_auth.js +114 -133
  86. package/dist/init/phases/setup.js +546 -703
  87. package/dist/init/phases/skills.js +75 -86
  88. package/dist/init/phases/status.js +72 -67
  89. package/dist/init/resolve_context.js +102 -99
  90. package/dist/init/route_command.js +91 -98
  91. package/dist/init/skills.js +174 -218
  92. package/dist/init/vsix.js +77 -99
  93. package/dist/log.js +17 -16
  94. package/dist/neon_services.js +104 -129
  95. package/dist/parameters.gen.js +481 -471
  96. package/dist/pkg.js +17 -19
  97. package/dist/profile_keys.js +44 -47
  98. package/dist/psql/cli.js +44 -47
  99. package/dist/psql/command/cmd_cond.js +231 -406
  100. package/dist/psql/command/cmd_connect.js +557 -764
  101. package/dist/psql/command/cmd_copy.js +727 -983
  102. package/dist/psql/command/cmd_describe.js +1499 -1688
  103. package/dist/psql/command/cmd_format.js +733 -905
  104. package/dist/psql/command/cmd_io.js +1293 -2082
  105. package/dist/psql/command/cmd_lo.js +297 -359
  106. package/dist/psql/command/cmd_meta.js +727 -878
  107. package/dist/psql/command/cmd_misc.js +138 -172
  108. package/dist/psql/command/cmd_pipeline.js +547 -1099
  109. package/dist/psql/command/cmd_restrict.js +119 -155
  110. package/dist/psql/command/cmd_show.js +529 -688
  111. package/dist/psql/command/dispatch.js +261 -325
  112. package/dist/psql/command/inputQueue.js +35 -33
  113. package/dist/psql/command/shared.js +49 -63
  114. package/dist/psql/complete/filenames.js +90 -133
  115. package/dist/psql/complete/index.js +59 -97
  116. package/dist/psql/complete/matcher.js +236 -300
  117. package/dist/psql/complete/psqlVars.js +218 -223
  118. package/dist/psql/complete/queries.js +159 -177
  119. package/dist/psql/complete/rules.js +1493 -2299
  120. package/dist/psql/core/common.js +762 -1180
  121. package/dist/psql/core/help.js +456 -546
  122. package/dist/psql/core/mainloop.js +692 -1302
  123. package/dist/psql/core/prompt.js +391 -408
  124. package/dist/psql/core/settings.js +429 -644
  125. package/dist/psql/core/sqlHelp.js +480 -554
  126. package/dist/psql/core/startup.js +626 -815
  127. package/dist/psql/core/syncVars.js +67 -110
  128. package/dist/psql/core/variables.js +156 -278
  129. package/dist/psql/describe/formatters.js +884 -1285
  130. package/dist/psql/describe/processNamePattern.js +173 -260
  131. package/dist/psql/describe/queries.js +1368 -2403
  132. package/dist/psql/describe/versionGate.js +32 -41
  133. package/dist/psql/index.js +1414 -1927
  134. package/dist/psql/io/history.js +232 -271
  135. package/dist/psql/io/input.js +103 -108
  136. package/dist/psql/io/lineEditor/buffer.js +238 -319
  137. package/dist/psql/io/lineEditor/complete.js +135 -213
  138. package/dist/psql/io/lineEditor/filename.js +139 -148
  139. package/dist/psql/io/lineEditor/index.js +653 -870
  140. package/dist/psql/io/lineEditor/keymap.js +544 -702
  141. package/dist/psql/io/lineEditor/vt100.js +294 -341
  142. package/dist/psql/io/pgpass.js +158 -187
  143. package/dist/psql/io/pgservice.js +146 -183
  144. package/dist/psql/io/psqlrc.js +328 -403
  145. package/dist/psql/print/aligned.js +1020 -1683
  146. package/dist/psql/print/asciidoc.js +180 -214
  147. package/dist/psql/print/crosstab.js +281 -442
  148. package/dist/psql/print/csv.js +48 -70
  149. package/dist/psql/print/html.js +195 -226
  150. package/dist/psql/print/json.js +75 -88
  151. package/dist/psql/print/latex.js +291 -364
  152. package/dist/psql/print/pager.js +171 -242
  153. package/dist/psql/print/troff.js +194 -226
  154. package/dist/psql/print/unaligned.js +69 -95
  155. package/dist/psql/print/units.js +167 -169
  156. package/dist/psql/scanner/slash.js +428 -483
  157. package/dist/psql/scanner/sql.js +445 -889
  158. package/dist/psql/scanner/stringutils.js +309 -379
  159. package/dist/psql/types/index.js +2 -7
  160. package/dist/psql/types/scanner.js +25 -22
  161. package/dist/psql/wire/connection.js +2042 -2803
  162. package/dist/psql/wire/copy.js +84 -100
  163. package/dist/psql/wire/notify.js +39 -59
  164. package/dist/psql/wire/pipeline.js +305 -518
  165. package/dist/psql/wire/protocol.js +349 -417
  166. package/dist/psql/wire/sasl.js +180 -265
  167. package/dist/psql/wire/tls.js +400 -561
  168. package/dist/storage_api.js +115 -129
  169. package/dist/test_utils/fixtures.js +94 -113
  170. package/dist/test_utils/oauth_server.js +10 -7
  171. package/dist/test_utils/project_dir.js +33 -0
  172. package/dist/utils/ai_gateway_notice.js +131 -162
  173. package/dist/utils/api_enums.js +21 -28
  174. package/dist/utils/auth.js +10 -4
  175. package/dist/utils/branch_notice.js +20 -19
  176. package/dist/utils/branch_picker.js +83 -89
  177. package/dist/utils/cli_name.js +15 -12
  178. package/dist/utils/compute_units.js +20 -27
  179. package/dist/utils/config_diff.js +127 -158
  180. package/dist/utils/enrichers.js +95 -148
  181. package/dist/utils/esbuild.js +133 -147
  182. package/dist/utils/flags.js +35 -47
  183. package/dist/utils/formats.js +8 -15
  184. package/dist/utils/git_diff.js +69 -80
  185. package/dist/utils/inspect_db.js +101 -143
  186. package/dist/utils/inspect_queries.js +179 -142
  187. package/dist/utils/middlewares.js +37 -44
  188. package/dist/utils/openapi.js +87 -99
  189. package/dist/utils/package_manager.js +312 -110
  190. package/dist/utils/point_in_time.js +49 -53
  191. package/dist/utils/psql.js +89 -106
  192. package/dist/utils/service_picker.js +55 -58
  193. package/dist/utils/string.js +5 -5
  194. package/dist/utils/ui.js +38 -55
  195. package/dist/utils/write_sync.js +26 -35
  196. package/dist/utils/zip.js +4 -3
  197. package/dist/writer.js +67 -87
  198. package/package.json +9 -7
@@ -1,1031 +1,838 @@
1
- /**
2
- * WebSocket support for the `neon dev` server.
3
- *
4
- * Node's `http.Server` emits `'upgrade'` — not `'request'` — for anything carrying
5
- * `Connection: Upgrade`. With no `'upgrade'` listener registered, Node routes the
6
- * handshake to the ordinary request handler, which answers `200 OK` on a connection
7
- * the client is waiting to see a `101` on. That is why WebSockets did not work under
8
- * `neon dev`: a function's `upgrade` export was never called, and the client saw a
9
- * nonsense 200 rather than an error.
10
- *
11
- * This module provides both shapes the deployed runtime supports:
12
- *
13
- * 1. `export function upgrade(req, socket, head)` — handed the raw Node triple,
14
- * exactly like production. The bundle owns the handshake and framing.
15
- * 2. `upgradeWebSocket(request)` from `@neon/functions`, called inside `fetch` —
16
- * reached only when there is no `upgrade` export, matching the runtime's
17
- * precedence rule so local behaviour cannot diverge from deployed behaviour.
18
- *
19
- * The protocol implementation here mirrors the deployed runtime's. Duplicating it is
20
- * deliberate for now: the runtime is a zero-dependency file baked into a microVM image
21
- * and cannot import from npm, so sharing a module would mean new vendoring machinery.
22
- * The shared contract is the bridge global and the observable behaviour, which the
23
- * tests on both sides pin. permessage-deflate is not negotiated on either side.
24
- */
25
- var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
26
- if (kind === "m") throw new TypeError("Private method is not writable");
27
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
28
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
29
- return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
30
- };
31
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
32
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
33
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
34
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
35
- };
36
- var _DevServerWebSocket_instances, _DevServerWebSocket_socket, _DevServerWebSocket_protocol, _DevServerWebSocket_readyState, _DevServerWebSocket_binaryType, _DevServerWebSocket_bufferedAmount, _DevServerWebSocket_chunks, _DevServerWebSocket_queued, _DevServerWebSocket_fragmentOpcode, _DevServerWebSocket_fragments, _DevServerWebSocket_fragmentBytes, _DevServerWebSocket_closeSent, _DevServerWebSocket_closeTimer, _DevServerWebSocket_handlers, _DevServerWebSocket_setHandler, _DevServerWebSocket_onData, _DevServerWebSocket_parse, _DevServerWebSocket_pushFragment, _DevServerWebSocket_deliver, _DevServerWebSocket_handleControl, _DevServerWebSocket_sendClose, _DevServerWebSocket_fail, _DevServerWebSocket_finishClose, _DevServerWebSocket_emitError, _DevServerWebSocket_writeFrame, _DevServerWebSocket_peek, _DevServerWebSocket_take;
37
- import { AsyncLocalStorage } from "node:async_hooks";
38
1
  import { createHash } from "node:crypto";
2
+ import { AsyncLocalStorage } from "node:async_hooks";
3
+ //#region src/dev/websocket.ts
4
+ /**
5
+ * WebSocket support for the `neon dev` server.
6
+ *
7
+ * Node's `http.Server` emits `'upgrade'` — not `'request'` — for anything carrying
8
+ * `Connection: Upgrade`. With no `'upgrade'` listener registered, Node routes the
9
+ * handshake to the ordinary request handler, which answers `200 OK` on a connection
10
+ * the client is waiting to see a `101` on. That is why WebSockets did not work under
11
+ * `neon dev`: a function's `upgrade` export was never called, and the client saw a
12
+ * nonsense 200 rather than an error.
13
+ *
14
+ * This module provides both shapes the deployed runtime supports:
15
+ *
16
+ * 1. `export function upgrade(req, socket, head)` — handed the raw Node triple,
17
+ * exactly like production. The bundle owns the handshake and framing.
18
+ * 2. `upgradeWebSocket(request)` from `@neon/functions`, called inside `fetch` —
19
+ * reached only when there is no `upgrade` export, matching the runtime's
20
+ * precedence rule so local behaviour cannot diverge from deployed behaviour.
21
+ *
22
+ * The protocol implementation here mirrors the deployed runtime's. Duplicating it is
23
+ * deliberate for now: the runtime is a zero-dependency file baked into a microVM image
24
+ * and cannot import from npm, so sharing a module would mean new vendoring machinery.
25
+ * The shared contract is the bridge global and the observable behaviour, which the
26
+ * tests on both sides pin. permessage-deflate is not negotiated on either side.
27
+ */
39
28
  /** The bridge global `@neon/functions` reads. Must match the deployed runtime. */
40
29
  const WS_BRIDGE_KEY = Symbol.for("neon.websocket.bridge");
41
30
  /** Brand identifying the response the helper produced. Non-enumerable. */
42
31
  const UPGRADE_RECORD = Symbol.for("neon.websocket.upgradeRecord");
43
32
  /** RFC 6455 §1.3. */
44
33
  const WS_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
45
- const OP_CONTINUATION = 0x0;
46
- const OP_TEXT = 0x1;
47
- const OP_BINARY = 0x2;
48
- const OP_CLOSE = 0x8;
49
- const OP_PING = 0x9;
50
- const OP_PONG = 0xa;
34
+ const OP_CONTINUATION = 0;
35
+ const OP_TEXT = 1;
36
+ const OP_BINARY = 2;
37
+ const OP_CLOSE = 8;
38
+ const OP_PING = 9;
39
+ const OP_PONG = 10;
51
40
  const CONNECTING = 0;
52
41
  const OPEN = 1;
53
42
  const CLOSING = 2;
54
43
  const CLOSED = 3;
55
44
  /** Reassembly ceiling: a fragmented message buffers until FIN, so it must be bounded. */
56
- const MAX_MESSAGE_BYTES = 16 * 1024 * 1024;
45
+ const MAX_MESSAGE_BYTES = 16777216;
57
46
  /** Control frames carry at most 125 bytes and are never fragmented (§5.5). */
58
47
  const MAX_CONTROL_PAYLOAD = 125;
59
48
  /** A close reason must fit the 123 bytes left after the 2-byte code. */
60
49
  const MAX_CLOSE_REASON_BYTES = 123;
61
50
  /** How long to wait for the peer's answering close frame (§7.1.1 leaves this open). */
62
- const CLOSE_HANDSHAKE_TIMEOUT_MS = 5000;
51
+ const CLOSE_HANDSHAKE_TIMEOUT_MS = 5e3;
63
52
  const EMPTY = Buffer.alloc(0);
64
- class ShimErrorEvent extends Event {
65
- constructor(type, init = {}) {
66
- super(type);
67
- this.message = init.message ?? "";
68
- this.error = init.error;
69
- }
70
- }
71
- const ErrorEventCtor = typeof globalThis.ErrorEvent === "function"
72
- ? globalThis
73
- .ErrorEvent
74
- : ShimErrorEvent;
75
- class ShimCloseEvent extends Event {
76
- constructor(type, init = {}) {
77
- super(type);
78
- this.code = init.code ?? 0;
79
- this.reason = init.reason ?? "";
80
- this.wasClean = init.wasClean ?? false;
81
- }
82
- }
83
- export const CloseEventCtor = typeof globalThis.CloseEvent === "function"
84
- ? globalThis
85
- .CloseEvent
86
- : ShimCloseEvent;
87
- export const acceptKey = (key) => createHash("sha1")
88
- .update(key + WS_GUID)
89
- .digest("base64");
53
+ var ShimErrorEvent = class extends Event {
54
+ constructor(type, init = {}) {
55
+ super(type);
56
+ this.message = init.message ?? "";
57
+ this.error = init.error;
58
+ }
59
+ };
60
+ const ErrorEventCtor = typeof globalThis.ErrorEvent === "function" ? globalThis.ErrorEvent : ShimErrorEvent;
61
+ var ShimCloseEvent = class extends Event {
62
+ constructor(type, init = {}) {
63
+ super(type);
64
+ this.code = init.code ?? 0;
65
+ this.reason = init.reason ?? "";
66
+ this.wasClean = init.wasClean ?? false;
67
+ }
68
+ };
69
+ const CloseEventCtor = typeof globalThis.CloseEvent === "function" ? globalThis.CloseEvent : ShimCloseEvent;
70
+ const acceptKey = (key) => createHash("sha1").update(key + WS_GUID).digest("base64");
90
71
  const headerValue = (req, name) => {
91
- const value = req.headers[name];
92
- if (Array.isArray(value))
93
- return value[0] ?? "";
94
- return typeof value === "string" ? value : "";
72
+ const value = req.headers[name];
73
+ if (Array.isArray(value)) return value[0] ?? "";
74
+ return typeof value === "string" ? value : "";
95
75
  };
96
76
  /** Does this request carry a WebSocket handshake (rather than some other upgrade)? */
97
- export const isWebSocketHandshake = (req) => headerValue(req, "upgrade").toLowerCase() === "websocket" &&
98
- headerValue(req, "sec-websocket-key") !== "";
99
- // -- Request <-> socket association -----------------------------------------
77
+ const isWebSocketHandshake = (req) => headerValue(req, "upgrade").toLowerCase() === "websocket" && headerValue(req, "sec-websocket-key") !== "";
100
78
  /** Primary lookup: the exact Request object the dev server built. */
101
- const pendingByRequest = new WeakMap();
79
+ const pendingByRequest = /* @__PURE__ */ new WeakMap();
102
80
  /**
103
- * Fallback for adapters that rebuild the Request before the handler sees it —
104
- * Hono's Node adapter does, so the object identity we keyed on is lost.
105
- */
81
+ * Fallback for adapters that rebuild the Request before the handler sees it —
82
+ * Hono's Node adapter does, so the object identity we keyed on is lost.
83
+ */
106
84
  const upgradeStore = new AsyncLocalStorage();
107
85
  const registerUpgrade = (request, parts) => {
108
- const record = {
109
- req: parts.req,
110
- socket: parts.socket,
111
- head: parts.head.length ? Buffer.from(parts.head) : EMPTY,
112
- claimed: false,
113
- ws: null,
114
- protocol: "",
115
- accept: "",
116
- };
117
- pendingByRequest.set(request, record);
118
- return record;
86
+ const record = {
87
+ req: parts.req,
88
+ socket: parts.socket,
89
+ head: parts.head.length ? Buffer.from(parts.head) : EMPTY,
90
+ claimed: false,
91
+ ws: null,
92
+ protocol: "",
93
+ accept: ""
94
+ };
95
+ pendingByRequest.set(request, record);
96
+ return record;
119
97
  };
120
98
  const readUpgradeRecord = (value) => {
121
- if (!value || typeof value !== "object")
122
- return null;
123
- return (value[UPGRADE_RECORD] ??
124
- null);
99
+ if (!value || typeof value !== "object") return null;
100
+ return value[UPGRADE_RECORD] ?? null;
125
101
  };
126
- // -- The Response subclass ---------------------------------------------------
127
102
  /**
128
- * `new Response(null, { status: 101 })` throws — the fetch spec restricts constructed
129
- * responses to 200-599. A subclass can still report 101 from the getter while being a
130
- * genuine `instanceof Response`.
131
- *
132
- * The lie does not survive `clone()` or `new Response(res.body, res)`; both drop the
133
- * brand and the overridden getter. That is detected and failed loudly rather than
134
- * silently answering 200 on a socket awaiting a 101.
135
- */
136
- class UpgradeResponse extends Response {
137
- constructor(record) {
138
- super(null, { status: 200 });
139
- Object.defineProperty(this, UPGRADE_RECORD, {
140
- value: record,
141
- enumerable: false,
142
- configurable: false,
143
- writable: false,
144
- });
145
- }
146
- get status() {
147
- return 101;
148
- }
149
- get statusText() {
150
- return "Switching Protocols";
151
- }
152
- get ok() {
153
- return false;
154
- }
155
- }
156
- // -- The public helper -------------------------------------------------------
103
+ * `new Response(null, { status: 101 })` throws — the fetch spec restricts constructed
104
+ * responses to 200-599. A subclass can still report 101 from the getter while being a
105
+ * genuine `instanceof Response`.
106
+ *
107
+ * The lie does not survive `clone()` or `new Response(res.body, res)`; both drop the
108
+ * brand and the overridden getter. That is detected and failed loudly rather than
109
+ * silently answering 200 on a socket awaiting a 101.
110
+ */
111
+ var UpgradeResponse = class extends Response {
112
+ constructor(record) {
113
+ super(null, { status: 200 });
114
+ Object.defineProperty(this, UPGRADE_RECORD, {
115
+ value: record,
116
+ enumerable: false,
117
+ configurable: false,
118
+ writable: false
119
+ });
120
+ }
121
+ get status() {
122
+ return 101;
123
+ }
124
+ get statusText() {
125
+ return "Switching Protocols";
126
+ }
127
+ get ok() {
128
+ return false;
129
+ }
130
+ };
157
131
  /** The implementation behind `upgradeWebSocket()` in `@neon/functions`. */
158
- export const upgradeWebSocket = (request, options = {}) => {
159
- const record = pendingByRequest.get(request) ?? upgradeStore.getStore();
160
- if (!record) {
161
- throw new TypeError("upgradeWebSocket() found no connection to upgrade. It is only usable " +
162
- "inside a Neon Functions invocation, on a request carrying a WebSocket " +
163
- "handshake.");
164
- }
165
- if (record.claimed) {
166
- throw new TypeError("upgradeWebSocket() was already called for this request; a connection " +
167
- "can only be upgraded once.");
168
- }
169
- const req = record.req;
170
- const method = (req.method ?? "GET").toUpperCase();
171
- if (method !== "GET") {
172
- throw new TypeError(`upgradeWebSocket() requires a GET request, got ${method}.`);
173
- }
174
- const key = headerValue(req, "sec-websocket-key");
175
- if (headerValue(req, "upgrade").toLowerCase() !== "websocket" ||
176
- key === "") {
177
- throw new TypeError("upgradeWebSocket() requires a WebSocket handshake: the request must " +
178
- "carry `Upgrade: websocket` and `Sec-WebSocket-Key`.");
179
- }
180
- const version = headerValue(req, "sec-websocket-version");
181
- if (version !== "" && version !== "13") {
182
- throw new TypeError(`unsupported Sec-WebSocket-Version ${version}; only version 13 is supported.`);
183
- }
184
- // RFC 6455 §4.2.2: echo exactly one protocol, and only one the client offered.
185
- // Selecting an un-offered protocol is a server bug that surfaces as an opaque
186
- // client-side rejection, so fail here where the cause is obvious.
187
- let protocol = "";
188
- if (options.protocol != null) {
189
- if (typeof options.protocol !== "string") {
190
- throw new TypeError("options.protocol must be a string.");
191
- }
192
- const offered = headerValue(req, "sec-websocket-protocol")
193
- .split(",")
194
- .map((entry) => entry.trim())
195
- .filter((entry) => entry !== "");
196
- if (!offered.includes(options.protocol)) {
197
- throw new TypeError(`the client did not offer the subprotocol "${options.protocol}" ` +
198
- `(offered: ${offered.length ? offered.join(", ") : "none"}).`);
199
- }
200
- protocol = options.protocol;
201
- }
202
- record.claimed = true;
203
- record.protocol = protocol;
204
- record.accept = acceptKey(key);
205
- record.ws = new DevServerWebSocket({
206
- socket: record.socket,
207
- url: socketUrl(req),
208
- protocol,
209
- });
210
- return {
211
- socket: record.ws,
212
- response: new UpgradeResponse(record),
213
- };
132
+ const upgradeWebSocket = (request, options = {}) => {
133
+ const record = pendingByRequest.get(request) ?? upgradeStore.getStore();
134
+ if (!record) throw new TypeError("upgradeWebSocket() found no connection to upgrade. It is only usable inside a Neon Functions invocation, on a request carrying a WebSocket handshake.");
135
+ if (record.claimed) throw new TypeError("upgradeWebSocket() was already called for this request; a connection can only be upgraded once.");
136
+ const req = record.req;
137
+ const method = (req.method ?? "GET").toUpperCase();
138
+ if (method !== "GET") throw new TypeError(`upgradeWebSocket() requires a GET request, got ${method}.`);
139
+ const key = headerValue(req, "sec-websocket-key");
140
+ if (headerValue(req, "upgrade").toLowerCase() !== "websocket" || key === "") throw new TypeError("upgradeWebSocket() requires a WebSocket handshake: the request must carry `Upgrade: websocket` and `Sec-WebSocket-Key`.");
141
+ const version = headerValue(req, "sec-websocket-version");
142
+ if (version !== "" && version !== "13") throw new TypeError(`unsupported Sec-WebSocket-Version ${version}; only version 13 is supported.`);
143
+ let protocol = "";
144
+ if (options.protocol != null) {
145
+ if (typeof options.protocol !== "string") throw new TypeError("options.protocol must be a string.");
146
+ const offered = headerValue(req, "sec-websocket-protocol").split(",").map((entry) => entry.trim()).filter((entry) => entry !== "");
147
+ if (!offered.includes(options.protocol)) throw new TypeError(`the client did not offer the subprotocol "${options.protocol}" (offered: ${offered.length ? offered.join(", ") : "none"}).`);
148
+ protocol = options.protocol;
149
+ }
150
+ record.claimed = true;
151
+ record.protocol = protocol;
152
+ record.accept = acceptKey(key);
153
+ record.ws = new DevServerWebSocket({
154
+ socket: record.socket,
155
+ url: socketUrl(req),
156
+ protocol
157
+ });
158
+ return {
159
+ socket: record.ws,
160
+ response: new UpgradeResponse(record)
161
+ };
214
162
  };
215
163
  const socketUrl = (req) => {
216
- const host = headerValue(req, "host") || "localhost";
217
- const target = typeof req.url === "string" && req.url.startsWith("/") ? req.url : "/";
218
- return `ws://${host}${target}`;
164
+ return `ws://${headerValue(req, "host") || "localhost"}${typeof req.url === "string" && req.url.startsWith("/") ? req.url : "/"}`;
219
165
  };
220
166
  /** Publish the bridge so `@neon/functions` resolves under `neon dev`. */
221
- export const installWebSocketBridge = (target = globalThis) => {
222
- Object.defineProperty(target, WS_BRIDGE_KEY, {
223
- value: { version: 1, upgrade: upgradeWebSocket },
224
- enumerable: false,
225
- configurable: true,
226
- writable: true,
227
- });
167
+ const installWebSocketBridge = (target = globalThis) => {
168
+ Object.defineProperty(target, WS_BRIDGE_KEY, {
169
+ value: {
170
+ version: 1,
171
+ upgrade: upgradeWebSocket
172
+ },
173
+ enumerable: false,
174
+ configurable: true,
175
+ writable: true
176
+ });
228
177
  };
229
- // -- Handshake completion ----------------------------------------------------
230
178
  /** Headers the handshake owns; a customer value must not override them. */
231
- const RESERVED_HANDSHAKE_HEADERS = new Set([
232
- "connection",
233
- "upgrade",
234
- "sec-websocket-accept",
235
- "sec-websocket-protocol",
236
- "sec-websocket-extensions",
237
- "content-length",
238
- "content-type",
239
- "transfer-encoding",
179
+ const RESERVED_HANDSHAKE_HEADERS = /* @__PURE__ */ new Set([
180
+ "connection",
181
+ "upgrade",
182
+ "sec-websocket-accept",
183
+ "sec-websocket-protocol",
184
+ "sec-websocket-extensions",
185
+ "content-length",
186
+ "content-type",
187
+ "transfer-encoding"
240
188
  ]);
241
189
  const completeUpgrade = (record, response) => {
242
- const socket = record.socket;
243
- const ws = record.ws;
244
- if (!ws)
245
- return;
246
- // close() was called before the handler returned: never write the 101.
247
- if (ws.readyState === CLOSED) {
248
- try {
249
- socket.end();
250
- }
251
- catch {
252
- /* already gone */
253
- }
254
- return;
255
- }
256
- let head = "HTTP/1.1 101 Switching Protocols\r\n" +
257
- "upgrade: websocket\r\n" +
258
- "connection: Upgrade\r\n" +
259
- `sec-websocket-accept: ${record.accept}\r\n`;
260
- if (record.protocol) {
261
- head += `sec-websocket-protocol: ${record.protocol}\r\n`;
262
- }
263
- try {
264
- response.headers.forEach((value, name) => {
265
- const lower = name.toLowerCase();
266
- if (RESERVED_HANDSHAKE_HEADERS.has(lower))
267
- return;
268
- head += `${lower}: ${value}\r\n`;
269
- });
270
- }
271
- catch {
272
- // A response without usable headers is not a reason to drop the upgrade.
273
- }
274
- socket.write(`${head}\r\n`);
275
- // A live WebSocket is a long-lived idle-tolerant pipe: no inactivity timeout, and
276
- // no Nagle delay on small frames. `Duplex` does not declare either method, so
277
- // feature-detect (a test double may implement neither).
278
- try {
279
- const tunable = socket;
280
- tunable.setTimeout?.(0);
281
- tunable.setNoDelay?.(true);
282
- }
283
- catch {
284
- /* not a real socket */
285
- }
286
- ws.openWithHead(record.head);
190
+ const socket = record.socket;
191
+ const ws = record.ws;
192
+ if (!ws) return;
193
+ if (ws.readyState === CLOSED) {
194
+ try {
195
+ socket.end();
196
+ } catch {}
197
+ return;
198
+ }
199
+ let head = `HTTP/1.1 101 Switching Protocols\r
200
+ upgrade: websocket\r
201
+ connection: Upgrade\r
202
+ sec-websocket-accept: ${record.accept}\r\n`;
203
+ if (record.protocol) head += `sec-websocket-protocol: ${record.protocol}\r\n`;
204
+ try {
205
+ response.headers.forEach((value, name) => {
206
+ const lower = name.toLowerCase();
207
+ if (RESERVED_HANDSHAKE_HEADERS.has(lower)) return;
208
+ head += `${lower}: ${value}\r\n`;
209
+ });
210
+ } catch {}
211
+ socket.write(`${head}\r\n`);
212
+ try {
213
+ const tunable = socket;
214
+ tunable.setTimeout?.(0);
215
+ tunable.setNoDelay?.(true);
216
+ } catch {}
217
+ ws.openWithHead(record.head);
287
218
  };
288
- // -- Reject / relay helpers --------------------------------------------------
289
219
  const STATUS_TEXT = {
290
- 200: "OK",
291
- 400: "Bad Request",
292
- 401: "Unauthorized",
293
- 403: "Forbidden",
294
- 404: "Not Found",
295
- 405: "Method Not Allowed",
296
- 409: "Conflict",
297
- 426: "Upgrade Required",
298
- 429: "Too Many Requests",
299
- 500: "Internal Server Error",
300
- 501: "Not Implemented",
301
- 502: "Bad Gateway",
302
- 503: "Service Unavailable",
220
+ 200: "OK",
221
+ 400: "Bad Request",
222
+ 401: "Unauthorized",
223
+ 403: "Forbidden",
224
+ 404: "Not Found",
225
+ 405: "Method Not Allowed",
226
+ 409: "Conflict",
227
+ 426: "Upgrade Required",
228
+ 429: "Too Many Requests",
229
+ 500: "Internal Server Error",
230
+ 501: "Not Implemented",
231
+ 502: "Bad Gateway",
232
+ 503: "Service Unavailable"
303
233
  };
304
234
  /**
305
- * Answer an upgrade with a plain HTTP response and close. Node gives us a raw socket
306
- * for `'upgrade'` (no ServerResponse), so the bytes are written by hand. Always ends
307
- * the socket: an unanswered upgrade socket leaks and leaves the client hanging.
308
- */
309
- export const writeUpgradeRejection = (socket, status, reason, extraHeaders = {}) => {
310
- const body = `${reason}\n`;
311
- let head = `HTTP/1.1 ${status} ${STATUS_TEXT[status] ?? "Error"}\r\n` +
312
- "connection: close\r\n" +
313
- "content-type: text/plain\r\n" +
314
- `content-length: ${Buffer.byteLength(body)}\r\n`;
315
- for (const [name, value] of Object.entries(extraHeaders)) {
316
- head += `${name.toLowerCase()}: ${value}\r\n`;
317
- }
318
- try {
319
- socket.end(`${head}\r\n${body}`);
320
- }
321
- catch {
322
- try {
323
- socket.destroy();
324
- }
325
- catch {
326
- /* already destroyed */
327
- }
328
- }
235
+ * Answer an upgrade with a plain HTTP response and close. Node gives us a raw socket
236
+ * for `'upgrade'` (no ServerResponse), so the bytes are written by hand. Always ends
237
+ * the socket: an unanswered upgrade socket leaks and leaves the client hanging.
238
+ */
239
+ const writeUpgradeRejection = (socket, status, reason, extraHeaders = {}) => {
240
+ const body = `${reason}\n`;
241
+ let head = `HTTP/1.1 ${status} ${STATUS_TEXT[status] ?? "Error"}\r\nconnection: close\r
242
+ content-type: text/plain\r
243
+ content-length: ${Buffer.byteLength(body)}\r\n`;
244
+ for (const [name, value] of Object.entries(extraHeaders)) head += `${name.toLowerCase()}: ${value}\r\n`;
245
+ try {
246
+ socket.end(`${head}\r\n${body}`);
247
+ } catch {
248
+ try {
249
+ socket.destroy();
250
+ } catch {}
251
+ }
329
252
  };
330
253
  /** Headers the raw writer owns; a handler value must not contradict them. */
331
- const RESERVED_RESPONSE_HEADERS = new Set([
332
- "connection",
333
- "content-length",
334
- "transfer-encoding",
335
- "keep-alive",
336
- "upgrade",
254
+ const RESERVED_RESPONSE_HEADERS = /* @__PURE__ */ new Set([
255
+ "connection",
256
+ "content-length",
257
+ "transfer-encoding",
258
+ "keep-alive",
259
+ "upgrade"
337
260
  ]);
338
261
  /**
339
- * Write a `Response` the handler returned to decline the upgrade, verbatim: status,
340
- * headers and body. Declining a handshake with a 401/403/404 is how a function refuses
341
- * a connection, so the response has to reach the client instead of being replaced.
342
- */
262
+ * Write a `Response` the handler returned to decline the upgrade, verbatim: status,
263
+ * headers and body. Declining a handshake with a 401/403/404 is how a function refuses
264
+ * a connection, so the response has to reach the client instead of being replaced.
265
+ */
343
266
  const writeResponseToSocket = async (socket, response) => {
344
- const body = Buffer.from(await response.arrayBuffer());
345
- const statusText = response.statusText || (STATUS_TEXT[response.status] ?? "");
346
- let head = `HTTP/1.1 ${response.status} ${statusText}\r\n`;
347
- response.headers.forEach((value, name) => {
348
- if (RESERVED_RESPONSE_HEADERS.has(name.toLowerCase()))
349
- return;
350
- head += `${name.toLowerCase()}: ${value}\r\n`;
351
- });
352
- head += "connection: close\r\n";
353
- head += `content-length: ${body.length}\r\n\r\n`;
354
- socket.end(Buffer.concat([Buffer.from(head, "latin1"), body]));
267
+ const body = Buffer.from(await response.arrayBuffer());
268
+ const statusText = response.statusText || (STATUS_TEXT[response.status] ?? "");
269
+ let head = `HTTP/1.1 ${response.status} ${statusText}\r\n`;
270
+ response.headers.forEach((value, name) => {
271
+ if (RESERVED_RESPONSE_HEADERS.has(name.toLowerCase())) return;
272
+ head += `${name.toLowerCase()}: ${value}\r\n`;
273
+ });
274
+ head += "connection: close\r\n";
275
+ head += `content-length: ${body.length}\r\n\r\n`;
276
+ socket.end(Buffer.concat([Buffer.from(head, "latin1"), body]));
355
277
  };
356
278
  /** RFC 6455 §4.1: a 16-byte nonce, base64-encoded. */
357
- const isValidWebSocketKey = (key) => /^[A-Za-z0-9+/]{22}==$/.test(key) &&
358
- Buffer.from(key, "base64").length === 16;
279
+ const isValidWebSocketKey = (key) => /^[A-Za-z0-9+/]{22}==$/.test(key) && Buffer.from(key, "base64").length === 16;
359
280
  /**
360
- * Check the parts of the handshake the server owns before any user code runs, so a
361
- * malformed client request is answered as the client error it is rather than
362
- * surfacing later as a throw from `upgradeWebSocket()` (which the listener cannot
363
- * tell apart from a genuine handler bug, and would report as a 502).
364
- */
365
- export const validateHandshake = (req) => {
366
- const method = (req.method ?? "GET").toUpperCase();
367
- if (method !== "GET") {
368
- return {
369
- status: 405,
370
- reason: `a WebSocket handshake must use GET, got ${method}`,
371
- };
372
- }
373
- const version = headerValue(req, "sec-websocket-version");
374
- if (version !== "13") {
375
- return {
376
- status: 426,
377
- reason: `unsupported Sec-WebSocket-Version ${version === "" ? "(absent)" : version}; only version 13 is supported`,
378
- // §4.4 requires advertising what the server does support.
379
- headers: { "sec-websocket-version": "13" },
380
- };
381
- }
382
- if (!isValidWebSocketKey(headerValue(req, "sec-websocket-key"))) {
383
- return {
384
- status: 400,
385
- reason: "Sec-WebSocket-Key must be a base64-encoded 16-byte value",
386
- };
387
- }
388
- return null;
281
+ * Check the parts of the handshake the server owns before any user code runs, so a
282
+ * malformed client request is answered as the client error it is rather than
283
+ * surfacing later as a throw from `upgradeWebSocket()` (which the listener cannot
284
+ * tell apart from a genuine handler bug, and would report as a 502).
285
+ */
286
+ const validateHandshake = (req) => {
287
+ const method = (req.method ?? "GET").toUpperCase();
288
+ if (method !== "GET") return {
289
+ status: 405,
290
+ reason: `a WebSocket handshake must use GET, got ${method}`
291
+ };
292
+ const version = headerValue(req, "sec-websocket-version");
293
+ if (version !== "13") return {
294
+ status: 426,
295
+ reason: `unsupported Sec-WebSocket-Version ${version === "" ? "(absent)" : version}; only version 13 is supported`,
296
+ headers: { "sec-websocket-version": "13" }
297
+ };
298
+ if (!isValidWebSocketKey(headerValue(req, "sec-websocket-key"))) return {
299
+ status: 400,
300
+ reason: "Sec-WebSocket-Key must be a base64-encoded 16-byte value"
301
+ };
302
+ return null;
389
303
  };
390
304
  /**
391
- * Build the `'upgrade'` listener for the dev server.
392
- *
393
- * Precedence matches the deployed runtime exactly: a legacy `upgrade` export wins
394
- * unconditionally, and only in its absence is the upgrade routed through `fetch` so
395
- * `upgradeWebSocket()` can claim it. A function that does neither gets a clean 501
396
- * rather than the misleading 200 Node produces with no listener at all.
397
- */
398
- export const createUpgradeListener = ({ fetch: fetchHandler, upgrade, log = (message) => process.stderr.write(`${message}\n`), }) => {
399
- return (req, socket, head) => {
400
- void handleUpgrade(req, socket, head, {
401
- fetch: fetchHandler,
402
- upgrade,
403
- log,
404
- }).catch((err) => {
405
- const message = err instanceof Error ? (err.stack ?? err.message) : String(err);
406
- log(`WebSocket upgrade failed:\n${message}`);
407
- try {
408
- writeUpgradeRejection(socket, 500, "upgrade error");
409
- }
410
- catch {
411
- /* socket already gone */
412
- }
413
- });
414
- };
305
+ * Build the `'upgrade'` listener for the dev server.
306
+ *
307
+ * Precedence matches the deployed runtime exactly: a legacy `upgrade` export wins
308
+ * unconditionally, and only in its absence is the upgrade routed through `fetch` so
309
+ * `upgradeWebSocket()` can claim it. A function that does neither gets a clean 501
310
+ * rather than the misleading 200 Node produces with no listener at all.
311
+ */
312
+ const createUpgradeListener = ({ fetch: fetchHandler, upgrade, log = (message) => process.stderr.write(`${message}\n`) }) => {
313
+ return (req, socket, head) => {
314
+ handleUpgrade(req, socket, head, {
315
+ fetch: fetchHandler,
316
+ upgrade,
317
+ log
318
+ }).catch((err) => {
319
+ log(`WebSocket upgrade failed:\n${err instanceof Error ? err.stack ?? err.message : String(err)}`);
320
+ try {
321
+ writeUpgradeRejection(socket, 500, "upgrade error");
322
+ } catch {}
323
+ });
324
+ };
415
325
  };
416
- const handleUpgrade = async (req, socket, head, { fetch: fetchHandler, upgrade, log, }) => {
417
- // A non-WebSocket upgrade (h2c, or something bespoke) is not ours to answer.
418
- if (!isWebSocketHandshake(req) && !upgrade) {
419
- writeUpgradeRejection(socket, 426, "expected a websocket upgrade");
420
- return;
421
- }
422
- // The launched contract wins, so local precedence matches deployed precedence.
423
- // It owns its own handshake, so it is handed the request unvalidated.
424
- if (upgrade) {
425
- await upgrade(req, socket, head);
426
- return;
427
- }
428
- const rejection = validateHandshake(req);
429
- if (rejection) {
430
- writeUpgradeRejection(socket, rejection.status, rejection.reason, rejection.headers ?? {});
431
- return;
432
- }
433
- const request = toWebRequest(req);
434
- const record = registerUpgrade(request, { req, socket, head });
435
- let response;
436
- try {
437
- response = await upgradeStore.run(record, () => fetchHandler(request));
438
- }
439
- catch (err) {
440
- const message = err instanceof Error ? (err.stack ?? err.message) : String(err);
441
- log(`Request handler threw an error while upgrading:\n${message}`);
442
- abortClaimedUpgrade(record);
443
- writeUpgradeRejection(socket, 502, "handler error");
444
- return;
445
- }
446
- if (readUpgradeRecord(response) === record) {
447
- completeUpgrade(record, response);
448
- return;
449
- }
450
- if (record.claimed) {
451
- // upgradeWebSocket() ran, but its response never made it back intact.
452
- log("websocket_upgrade_response_lost: upgradeWebSocket() was called for this " +
453
- "request but the response returned from the handler is not the one it " +
454
- "produced. `Response.clone()` and rebuilding a response " +
455
- "(`new Response(res.body, res)`, as response-rewriting middleware does) " +
456
- "both discard the upgrade. Return the `response` from upgradeWebSocket() " +
457
- "unchanged.");
458
- abortClaimedUpgrade(record);
459
- writeUpgradeRejection(socket, 500, "websocket_upgrade_response_lost");
460
- return;
461
- }
462
- // The handler declined the upgrade and answered with an ordinary response.
463
- // Relay it verbatim: returning a 401/403/404 is how a function refuses a
464
- // handshake, and replacing it with a status of our own would throw away the
465
- // only answer the client was given.
466
- await writeResponseToSocket(socket, response);
326
+ const handleUpgrade = async (req, socket, head, { fetch: fetchHandler, upgrade, log }) => {
327
+ if (!isWebSocketHandshake(req) && !upgrade) {
328
+ writeUpgradeRejection(socket, 426, "expected a websocket upgrade");
329
+ return;
330
+ }
331
+ if (upgrade) {
332
+ await upgrade(req, socket, head);
333
+ return;
334
+ }
335
+ const rejection = validateHandshake(req);
336
+ if (rejection) {
337
+ writeUpgradeRejection(socket, rejection.status, rejection.reason, rejection.headers ?? {});
338
+ return;
339
+ }
340
+ const request = toWebRequest(req);
341
+ const record = registerUpgrade(request, {
342
+ req,
343
+ socket,
344
+ head
345
+ });
346
+ let response;
347
+ try {
348
+ response = await upgradeStore.run(record, () => fetchHandler(request));
349
+ } catch (err) {
350
+ log(`Request handler threw an error while upgrading:\n${err instanceof Error ? err.stack ?? err.message : String(err)}`);
351
+ abortClaimedUpgrade(record);
352
+ writeUpgradeRejection(socket, 502, "handler error");
353
+ return;
354
+ }
355
+ if (readUpgradeRecord(response) === record) {
356
+ completeUpgrade(record, response);
357
+ return;
358
+ }
359
+ if (record.claimed) {
360
+ log("websocket_upgrade_response_lost: upgradeWebSocket() was called for this request but the response returned from the handler is not the one it produced. `Response.clone()` and rebuilding a response (`new Response(res.body, res)`, as response-rewriting middleware does) both discard the upgrade. Return the `response` from upgradeWebSocket() unchanged.");
361
+ abortClaimedUpgrade(record);
362
+ writeUpgradeRejection(socket, 500, "websocket_upgrade_response_lost");
363
+ return;
364
+ }
365
+ await writeResponseToSocket(socket, response);
467
366
  };
468
367
  /**
469
- * The handler claimed the connection but we cannot honour it. Move the socket object
470
- * to CLOSED so a customer holding it sees a close event instead of one stuck at
471
- * CONNECTING — without touching the underlying socket, which still needs to carry an
472
- * HTTP error response.
473
- */
368
+ * The handler claimed the connection but we cannot honour it. Move the socket object
369
+ * to CLOSED so a customer holding it sees a close event instead of one stuck at
370
+ * CONNECTING — without touching the underlying socket, which still needs to carry an
371
+ * HTTP error response.
372
+ */
474
373
  const abortClaimedUpgrade = (record) => {
475
- try {
476
- record.ws?.abort(1011, "upgrade failed");
477
- }
478
- catch {
479
- /* best effort */
480
- }
374
+ try {
375
+ record.ws?.abort(1011, "upgrade failed");
376
+ } catch {}
481
377
  };
482
378
  /**
483
- * Build a Web `Request` from the Node upgrade. A handshake is a GET with no body, so
484
- * there is nothing to stream — which is just as well, since the socket has already been
485
- * detached from the HTTP parser.
486
- */
379
+ * Build a Web `Request` from the Node upgrade. A handshake is a GET with no body, so
380
+ * there is nothing to stream — which is just as well, since the socket has already been
381
+ * detached from the HTTP parser.
382
+ */
487
383
  const toWebRequest = (req) => {
488
- const host = headerValue(req, "host") || "localhost";
489
- const target = typeof req.url === "string" && req.url.startsWith("/") ? req.url : "/";
490
- const headers = new Headers();
491
- for (const [name, value] of Object.entries(req.headers)) {
492
- // HTTP/2 pseudo-headers are not valid Header names.
493
- if (name.startsWith(":"))
494
- continue;
495
- if (Array.isArray(value)) {
496
- for (const entry of value)
497
- headers.append(name, entry);
498
- }
499
- else if (value != null) {
500
- headers.set(name, value);
501
- }
502
- }
503
- return new Request(`http://${host}${target}`, {
504
- method: req.method ?? "GET",
505
- headers,
506
- });
384
+ const host = headerValue(req, "host") || "localhost";
385
+ const target = typeof req.url === "string" && req.url.startsWith("/") ? req.url : "/";
386
+ const headers = new Headers();
387
+ for (const [name, value] of Object.entries(req.headers)) {
388
+ if (name.startsWith(":")) continue;
389
+ if (Array.isArray(value)) for (const entry of value) headers.append(name, entry);
390
+ else if (value != null) headers.set(name, value);
391
+ }
392
+ return new Request(`http://${host}${target}`, {
393
+ method: req.method ?? "GET",
394
+ headers
395
+ });
507
396
  };
508
397
  /**
509
- * A WHATWG `WebSocket` over an already-upgraded Node socket.
510
- *
511
- * Two deliberate divergences from a browser, both matching the deployed runtime:
512
- * `binaryType` defaults to `"arraybuffer"` rather than `"blob"`, and `bufferedAmount`
513
- * is what we have written and not yet seen flushed, which approximates rather than
514
- * matches a browser's accounting.
515
- */
516
- class DevServerWebSocket extends EventTarget {
517
- constructor({ socket, url, protocol, }) {
518
- super();
519
- _DevServerWebSocket_instances.add(this);
520
- _DevServerWebSocket_socket.set(this, void 0);
521
- _DevServerWebSocket_protocol.set(this, void 0);
522
- _DevServerWebSocket_readyState.set(this, CONNECTING);
523
- _DevServerWebSocket_binaryType.set(this, "arraybuffer");
524
- _DevServerWebSocket_bufferedAmount.set(this, 0);
525
- _DevServerWebSocket_chunks.set(this, []);
526
- _DevServerWebSocket_queued.set(this, 0);
527
- _DevServerWebSocket_fragmentOpcode.set(this, 0);
528
- _DevServerWebSocket_fragments.set(this, []);
529
- _DevServerWebSocket_fragmentBytes.set(this, 0);
530
- _DevServerWebSocket_closeSent.set(this, false);
531
- _DevServerWebSocket_closeTimer.set(this, null);
532
- _DevServerWebSocket_handlers.set(this, {
533
- open: null,
534
- message: null,
535
- close: null,
536
- error: null,
537
- });
538
- __classPrivateFieldSet(this, _DevServerWebSocket_socket, socket, "f");
539
- this.url = url;
540
- __classPrivateFieldSet(this, _DevServerWebSocket_protocol, protocol, "f");
541
- }
542
- get CONNECTING() {
543
- return CONNECTING;
544
- }
545
- get OPEN() {
546
- return OPEN;
547
- }
548
- get CLOSING() {
549
- return CLOSING;
550
- }
551
- get CLOSED() {
552
- return CLOSED;
553
- }
554
- get protocol() {
555
- return __classPrivateFieldGet(this, _DevServerWebSocket_protocol, "f");
556
- }
557
- get extensions() {
558
- return "";
559
- }
560
- get readyState() {
561
- return __classPrivateFieldGet(this, _DevServerWebSocket_readyState, "f");
562
- }
563
- get bufferedAmount() {
564
- return __classPrivateFieldGet(this, _DevServerWebSocket_bufferedAmount, "f");
565
- }
566
- get binaryType() {
567
- return __classPrivateFieldGet(this, _DevServerWebSocket_binaryType, "f");
568
- }
569
- set binaryType(value) {
570
- if (value !== "arraybuffer" && value !== "blob") {
571
- throw new DOMException(`binaryType must be "arraybuffer" or "blob", got "${String(value)}"`, "SyntaxError");
572
- }
573
- __classPrivateFieldSet(this, _DevServerWebSocket_binaryType, value, "f");
574
- }
575
- get onopen() {
576
- return __classPrivateFieldGet(this, _DevServerWebSocket_handlers, "f").open;
577
- }
578
- set onopen(fn) {
579
- __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_setHandler).call(this, "open", fn);
580
- }
581
- get onmessage() {
582
- return __classPrivateFieldGet(this, _DevServerWebSocket_handlers, "f").message;
583
- }
584
- set onmessage(fn) {
585
- __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_setHandler).call(this, "message", fn);
586
- }
587
- get onclose() {
588
- return __classPrivateFieldGet(this, _DevServerWebSocket_handlers, "f").close;
589
- }
590
- set onclose(fn) {
591
- __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_setHandler).call(this, "close", fn);
592
- }
593
- get onerror() {
594
- return __classPrivateFieldGet(this, _DevServerWebSocket_handlers, "f").error;
595
- }
596
- set onerror(fn) {
597
- __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_setHandler).call(this, "error", fn);
598
- }
599
- send(data) {
600
- if (__classPrivateFieldGet(this, _DevServerWebSocket_readyState, "f") === CONNECTING) {
601
- throw new DOMException("cannot send() while the WebSocket is still CONNECTING; wait for the " +
602
- "open event (the socket opens once the handler returns the upgrade response)", "InvalidStateError");
603
- }
604
- // Per spec, sending on a closing/closed socket is counted and discarded.
605
- if (typeof data === "string") {
606
- const payload = Buffer.from(data, "utf8");
607
- if (__classPrivateFieldGet(this, _DevServerWebSocket_readyState, "f") !== OPEN) {
608
- __classPrivateFieldSet(this, _DevServerWebSocket_bufferedAmount, __classPrivateFieldGet(this, _DevServerWebSocket_bufferedAmount, "f") + payload.length, "f");
609
- return;
610
- }
611
- __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_writeFrame).call(this, OP_TEXT, payload);
612
- return;
613
- }
614
- if (data instanceof Blob) {
615
- // Blobs read asynchronously; chain on the read so ordering holds.
616
- const size = data.size;
617
- __classPrivateFieldSet(this, _DevServerWebSocket_bufferedAmount, __classPrivateFieldGet(this, _DevServerWebSocket_bufferedAmount, "f") + size, "f");
618
- data.arrayBuffer()
619
- .then((buf) => {
620
- __classPrivateFieldSet(this, _DevServerWebSocket_bufferedAmount, __classPrivateFieldGet(this, _DevServerWebSocket_bufferedAmount, "f") - size, "f");
621
- if (__classPrivateFieldGet(this, _DevServerWebSocket_readyState, "f") === OPEN) {
622
- __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_writeFrame).call(this, OP_BINARY, Buffer.from(buf));
623
- }
624
- })
625
- .catch(() => {
626
- __classPrivateFieldSet(this, _DevServerWebSocket_bufferedAmount, __classPrivateFieldGet(this, _DevServerWebSocket_bufferedAmount, "f") - size, "f");
627
- });
628
- return;
629
- }
630
- const payload = toBuffer(data);
631
- if (payload === null) {
632
- throw new TypeError("send() accepts a string, ArrayBuffer, TypedArray, DataView, or Blob");
633
- }
634
- if (__classPrivateFieldGet(this, _DevServerWebSocket_readyState, "f") !== OPEN) {
635
- __classPrivateFieldSet(this, _DevServerWebSocket_bufferedAmount, __classPrivateFieldGet(this, _DevServerWebSocket_bufferedAmount, "f") + payload.length, "f");
636
- return;
637
- }
638
- __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_writeFrame).call(this, OP_BINARY, payload);
639
- }
640
- close(code, reason) {
641
- if (code !== undefined && !isValidCloseCode(code)) {
642
- throw new DOMException(`close code ${code} is not allowed; use 1000 or a code in 3000-4999`, "InvalidAccessError");
643
- }
644
- let reasonBuf = EMPTY;
645
- if (reason !== undefined && reason !== null) {
646
- reasonBuf = Buffer.from(String(reason), "utf8");
647
- if (reasonBuf.length > MAX_CLOSE_REASON_BYTES) {
648
- throw new DOMException(`close reason must be at most ${MAX_CLOSE_REASON_BYTES} bytes of UTF-8`, "SyntaxError");
649
- }
650
- }
651
- if (__classPrivateFieldGet(this, _DevServerWebSocket_readyState, "f") === CLOSING || __classPrivateFieldGet(this, _DevServerWebSocket_readyState, "f") === CLOSED)
652
- return;
653
- if (__classPrivateFieldGet(this, _DevServerWebSocket_readyState, "f") === CONNECTING) {
654
- // Nothing to close yet — the handshake has not been written.
655
- this.abort(code ?? 1000, reasonBuf.toString("utf8"));
656
- return;
657
- }
658
- __classPrivateFieldSet(this, _DevServerWebSocket_readyState, CLOSING, "f");
659
- __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_sendClose).call(this, code ?? 1000, reasonBuf);
660
- __classPrivateFieldSet(this, _DevServerWebSocket_closeTimer, setTimeout(() => {
661
- // The peer never answered, so no close frame was received: §7.1.5 makes
662
- // that 1006, not the code we sent.
663
- __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_finishClose).call(this, 1006, "", false);
664
- }, CLOSE_HANDSHAKE_TIMEOUT_MS), "f");
665
- __classPrivateFieldGet(this, _DevServerWebSocket_closeTimer, "f").unref?.();
666
- }
667
- // -- dev-server internal ---------------------------------------------
668
- /** Called once the 101 is on the wire. */
669
- openWithHead(head) {
670
- if (__classPrivateFieldGet(this, _DevServerWebSocket_readyState, "f") !== CONNECTING)
671
- return;
672
- const socket = __classPrivateFieldGet(this, _DevServerWebSocket_socket, "f");
673
- socket.on("data", (chunk) => __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_onData).call(this, chunk));
674
- socket.on("error", (err) => {
675
- __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_emitError).call(this, err);
676
- __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_finishClose).call(this, 1006, "", false);
677
- });
678
- socket.on("close", () => __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_finishClose).call(this, 1006, "", false));
679
- socket.on("end", () => __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_finishClose).call(this, 1006, "", false));
680
- __classPrivateFieldSet(this, _DevServerWebSocket_readyState, OPEN, "f");
681
- this.dispatchEvent(new Event("open"));
682
- if (head.length)
683
- __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_onData).call(this, head);
684
- }
685
- /**
686
- * Give up before the handshake was written. Fires `close` so a caller holding the
687
- * socket is not left with something stuck at CONNECTING, but deliberately does not
688
- * touch the underlying socket: an HTTP error response still has to go out on it.
689
- */
690
- abort(code = 1006, reason = "") {
691
- if (__classPrivateFieldGet(this, _DevServerWebSocket_readyState, "f") === CLOSED)
692
- return;
693
- __classPrivateFieldSet(this, _DevServerWebSocket_readyState, CLOSED, "f");
694
- if (__classPrivateFieldGet(this, _DevServerWebSocket_closeTimer, "f")) {
695
- clearTimeout(__classPrivateFieldGet(this, _DevServerWebSocket_closeTimer, "f"));
696
- __classPrivateFieldSet(this, _DevServerWebSocket_closeTimer, null, "f");
697
- }
698
- this.dispatchEvent(new CloseEventCtor("close", { code, reason, wasClean: false }));
699
- }
700
- }
701
- _DevServerWebSocket_socket = new WeakMap(), _DevServerWebSocket_protocol = new WeakMap(), _DevServerWebSocket_readyState = new WeakMap(), _DevServerWebSocket_binaryType = new WeakMap(), _DevServerWebSocket_bufferedAmount = new WeakMap(), _DevServerWebSocket_chunks = new WeakMap(), _DevServerWebSocket_queued = new WeakMap(), _DevServerWebSocket_fragmentOpcode = new WeakMap(), _DevServerWebSocket_fragments = new WeakMap(), _DevServerWebSocket_fragmentBytes = new WeakMap(), _DevServerWebSocket_closeSent = new WeakMap(), _DevServerWebSocket_closeTimer = new WeakMap(), _DevServerWebSocket_handlers = new WeakMap(), _DevServerWebSocket_instances = new WeakSet(), _DevServerWebSocket_setHandler = function _DevServerWebSocket_setHandler(type, fn) {
702
- const previous = __classPrivateFieldGet(this, _DevServerWebSocket_handlers, "f")[type];
703
- if (previous)
704
- this.removeEventListener(type, previous);
705
- __classPrivateFieldGet(this, _DevServerWebSocket_handlers, "f")[type] = typeof fn === "function" ? fn : null;
706
- const next = __classPrivateFieldGet(this, _DevServerWebSocket_handlers, "f")[type];
707
- if (next)
708
- this.addEventListener(type, next);
709
- }, _DevServerWebSocket_onData = function _DevServerWebSocket_onData(chunk) {
710
- if (__classPrivateFieldGet(this, _DevServerWebSocket_readyState, "f") === CLOSED)
711
- return;
712
- __classPrivateFieldGet(this, _DevServerWebSocket_chunks, "f").push(chunk);
713
- __classPrivateFieldSet(this, _DevServerWebSocket_queued, __classPrivateFieldGet(this, _DevServerWebSocket_queued, "f") + chunk.length, "f");
714
- try {
715
- __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_parse).call(this);
716
- }
717
- catch (err) {
718
- __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_emitError).call(this, err);
719
- const code = err.wsCode ?? 1002;
720
- __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_fail).call(this, code, err instanceof Error ? err.message : String(err));
721
- }
722
- }, _DevServerWebSocket_parse = function _DevServerWebSocket_parse() {
723
- for (;;) {
724
- if (__classPrivateFieldGet(this, _DevServerWebSocket_queued, "f") < 2)
725
- return;
726
- const b0 = __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_peek).call(this, 0);
727
- const b1 = __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_peek).call(this, 1);
728
- const fin = (b0 & 0x80) !== 0;
729
- const rsv = b0 & 0x70;
730
- const opcode = b0 & 0x0f;
731
- const masked = (b1 & 0x80) !== 0;
732
- let length = b1 & 0x7f;
733
- let headerLen = 2;
734
- if (length === 126) {
735
- if (__classPrivateFieldGet(this, _DevServerWebSocket_queued, "f") < 4)
736
- return;
737
- length = (__classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_peek).call(this, 2) << 8) | __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_peek).call(this, 3);
738
- headerLen = 4;
739
- }
740
- else if (length === 127) {
741
- if (__classPrivateFieldGet(this, _DevServerWebSocket_queued, "f") < 10)
742
- return;
743
- let big = 0n;
744
- for (let i = 0; i < 8; i++)
745
- big = (big << 8n) | BigInt(__classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_peek).call(this, 2 + i));
746
- if (big > BigInt(MAX_MESSAGE_BYTES)) {
747
- throw protocolError(1009, "frame too large");
748
- }
749
- length = Number(big);
750
- headerLen = 10;
751
- }
752
- if (masked)
753
- headerLen += 4;
754
- if (__classPrivateFieldGet(this, _DevServerWebSocket_queued, "f") < headerLen + length)
755
- return;
756
- // Validate before consuming, so a rejected frame cannot desync the parser.
757
- if (rsv !== 0) {
758
- throw protocolError(1002, "reserved bits must be clear (no extensions were negotiated)");
759
- }
760
- if (!masked)
761
- throw protocolError(1002, "client frames must be masked");
762
- const isControl = (opcode & 0x08) !== 0;
763
- if (isControl) {
764
- if (!fin) {
765
- throw protocolError(1002, "control frames must not be fragmented");
766
- }
767
- if (length > MAX_CONTROL_PAYLOAD) {
768
- throw protocolError(1002, "control frame payload exceeds 125 bytes");
769
- }
770
- }
771
- if (opcode !== OP_CONTINUATION &&
772
- opcode !== OP_TEXT &&
773
- opcode !== OP_BINARY &&
774
- opcode !== OP_CLOSE &&
775
- opcode !== OP_PING &&
776
- opcode !== OP_PONG) {
777
- throw protocolError(1002, `reserved opcode 0x${opcode.toString(16)}`);
778
- }
779
- const header = __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_take).call(this, headerLen);
780
- const payload = __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_take).call(this, length);
781
- if (masked) {
782
- const mask = header.subarray(headerLen - 4, headerLen);
783
- for (let i = 0; i < payload.length; i++) {
784
- payload[i] =
785
- payload[i] ^ mask[i & 3];
786
- }
787
- }
788
- if (isControl) {
789
- __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_handleControl).call(this, opcode, payload);
790
- if (__classPrivateFieldGet(this, _DevServerWebSocket_readyState, "f") === CLOSED)
791
- return;
792
- continue;
793
- }
794
- if (opcode === OP_CONTINUATION) {
795
- if (__classPrivateFieldGet(this, _DevServerWebSocket_fragmentOpcode, "f") === 0) {
796
- throw protocolError(1002, "continuation frame without a start frame");
797
- }
798
- __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_pushFragment).call(this, payload);
799
- }
800
- else {
801
- if (__classPrivateFieldGet(this, _DevServerWebSocket_fragmentOpcode, "f") !== 0) {
802
- throw protocolError(1002, "new data frame while a fragmented message was open");
803
- }
804
- if (!fin) {
805
- __classPrivateFieldSet(this, _DevServerWebSocket_fragmentOpcode, opcode, "f");
806
- __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_pushFragment).call(this, payload);
807
- continue;
808
- }
809
- __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_deliver).call(this, opcode, payload);
810
- continue;
811
- }
812
- if (fin) {
813
- const messageOpcode = __classPrivateFieldGet(this, _DevServerWebSocket_fragmentOpcode, "f");
814
- const full = __classPrivateFieldGet(this, _DevServerWebSocket_fragments, "f").length === 1
815
- ? __classPrivateFieldGet(this, _DevServerWebSocket_fragments, "f")[0]
816
- : Buffer.concat(__classPrivateFieldGet(this, _DevServerWebSocket_fragments, "f"));
817
- __classPrivateFieldSet(this, _DevServerWebSocket_fragmentOpcode, 0, "f");
818
- __classPrivateFieldSet(this, _DevServerWebSocket_fragments, [], "f");
819
- __classPrivateFieldSet(this, _DevServerWebSocket_fragmentBytes, 0, "f");
820
- __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_deliver).call(this, messageOpcode, full);
821
- }
822
- }
823
- }, _DevServerWebSocket_pushFragment = function _DevServerWebSocket_pushFragment(payload) {
824
- __classPrivateFieldSet(this, _DevServerWebSocket_fragmentBytes, __classPrivateFieldGet(this, _DevServerWebSocket_fragmentBytes, "f") + payload.length, "f");
825
- if (__classPrivateFieldGet(this, _DevServerWebSocket_fragmentBytes, "f") > MAX_MESSAGE_BYTES) {
826
- throw protocolError(1009, `message exceeds the ${MAX_MESSAGE_BYTES} byte limit`);
827
- }
828
- // An empty continuation frame is legal and contributes nothing to the
829
- // message. Dropping it here keeps a flood of them from growing the fragment
830
- // list without ever moving `#fragmentBytes` toward the limit above.
831
- if (payload.length === 0)
832
- return;
833
- __classPrivateFieldGet(this, _DevServerWebSocket_fragments, "f").push(payload);
834
- }, _DevServerWebSocket_deliver = function _DevServerWebSocket_deliver(opcode, payload) {
835
- if (__classPrivateFieldGet(this, _DevServerWebSocket_readyState, "f") !== OPEN)
836
- return;
837
- let data;
838
- if (opcode === OP_TEXT) {
839
- try {
840
- data = new TextDecoder("utf-8", { fatal: true }).decode(payload);
841
- }
842
- catch {
843
- throw protocolError(1007, "text frame is not valid UTF-8");
844
- }
845
- }
846
- else if (__classPrivateFieldGet(this, _DevServerWebSocket_binaryType, "f") === "blob") {
847
- // Copy into a plain Uint8Array: a Node Buffer is not a valid BlobPart.
848
- data = new Blob([new Uint8Array(payload)]);
849
- }
850
- else {
851
- // A fresh ArrayBuffer, not a view onto our read buffer.
852
- data = payload.buffer.slice(payload.byteOffset, payload.byteOffset + payload.byteLength);
853
- }
854
- this.dispatchEvent(new MessageEvent("message", { data, origin: this.url }));
855
- }, _DevServerWebSocket_handleControl = function _DevServerWebSocket_handleControl(opcode, payload) {
856
- if (opcode === OP_PING) {
857
- if (__classPrivateFieldGet(this, _DevServerWebSocket_readyState, "f") === OPEN)
858
- __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_writeFrame).call(this, OP_PONG, payload);
859
- return;
860
- }
861
- if (opcode === OP_PONG)
862
- return;
863
- let code = 1005;
864
- let reason = "";
865
- if (payload.length === 1) {
866
- throw protocolError(1002, "close frame payload must be empty or at least 2 bytes");
867
- }
868
- if (payload.length >= 2) {
869
- code = (payload[0] << 8) | payload[1];
870
- if (!isEchoableCloseCode(code)) {
871
- throw protocolError(1002, `invalid close code ${code}`);
872
- }
873
- try {
874
- reason = new TextDecoder("utf-8", { fatal: true }).decode(payload.subarray(2));
875
- }
876
- catch {
877
- throw protocolError(1007, "close reason is not valid UTF-8");
878
- }
879
- }
880
- if (!__classPrivateFieldGet(this, _DevServerWebSocket_closeSent, "f")) {
881
- // Mirror the peer's code (§5.5.1). 1005 must never go on the wire.
882
- __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_sendClose).call(this, code === 1005 ? 1000 : code, EMPTY);
883
- }
884
- __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_finishClose).call(this, code, reason, true);
885
- }, _DevServerWebSocket_sendClose = function _DevServerWebSocket_sendClose(code, reasonBuf) {
886
- if (__classPrivateFieldGet(this, _DevServerWebSocket_closeSent, "f"))
887
- return;
888
- __classPrivateFieldSet(this, _DevServerWebSocket_closeSent, true, "f");
889
- const payload = Buffer.allocUnsafe(2 + reasonBuf.length);
890
- payload.writeUInt16BE(code, 0);
891
- if (reasonBuf.length)
892
- reasonBuf.copy(payload, 2);
893
- __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_writeFrame).call(this, OP_CLOSE, payload);
894
- }, _DevServerWebSocket_fail = function _DevServerWebSocket_fail(code, reason) {
895
- if (__classPrivateFieldGet(this, _DevServerWebSocket_readyState, "f") === OPEN) {
896
- const reasonBuf = Buffer.from(reason.slice(0, MAX_CLOSE_REASON_BYTES), "utf8");
897
- try {
898
- __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_sendClose).call(this, code, reasonBuf);
899
- }
900
- catch {
901
- /* socket already gone */
902
- }
903
- }
904
- __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_finishClose).call(this, code, reason, false);
905
- }, _DevServerWebSocket_finishClose = function _DevServerWebSocket_finishClose(code, reason, wasClean) {
906
- if (__classPrivateFieldGet(this, _DevServerWebSocket_readyState, "f") === CLOSED)
907
- return;
908
- __classPrivateFieldSet(this, _DevServerWebSocket_readyState, CLOSED, "f");
909
- if (__classPrivateFieldGet(this, _DevServerWebSocket_closeTimer, "f")) {
910
- clearTimeout(__classPrivateFieldGet(this, _DevServerWebSocket_closeTimer, "f"));
911
- __classPrivateFieldSet(this, _DevServerWebSocket_closeTimer, null, "f");
912
- }
913
- __classPrivateFieldSet(this, _DevServerWebSocket_chunks, [], "f");
914
- __classPrivateFieldSet(this, _DevServerWebSocket_queued, 0, "f");
915
- __classPrivateFieldSet(this, _DevServerWebSocket_fragments, [], "f");
916
- __classPrivateFieldSet(this, _DevServerWebSocket_fragmentBytes, 0, "f");
917
- try {
918
- __classPrivateFieldGet(this, _DevServerWebSocket_socket, "f").end();
919
- }
920
- catch {
921
- try {
922
- __classPrivateFieldGet(this, _DevServerWebSocket_socket, "f").destroy();
923
- }
924
- catch {
925
- /* already gone */
926
- }
927
- }
928
- this.dispatchEvent(new CloseEventCtor("close", { code, reason, wasClean }));
929
- }, _DevServerWebSocket_emitError = function _DevServerWebSocket_emitError(err) {
930
- const message = err instanceof Error ? err.message : String(err);
931
- this.dispatchEvent(new ErrorEventCtor("error", { message, error: err }));
932
- }, _DevServerWebSocket_writeFrame = function _DevServerWebSocket_writeFrame(opcode, payload) {
933
- const frame = encodeFrame(opcode, payload);
934
- __classPrivateFieldSet(this, _DevServerWebSocket_bufferedAmount, __classPrivateFieldGet(this, _DevServerWebSocket_bufferedAmount, "f") + frame.length, "f");
935
- try {
936
- __classPrivateFieldGet(this, _DevServerWebSocket_socket, "f").write(frame, () => {
937
- __classPrivateFieldSet(this, _DevServerWebSocket_bufferedAmount, __classPrivateFieldGet(this, _DevServerWebSocket_bufferedAmount, "f") - frame.length, "f");
938
- if (__classPrivateFieldGet(this, _DevServerWebSocket_bufferedAmount, "f") < 0)
939
- __classPrivateFieldSet(this, _DevServerWebSocket_bufferedAmount, 0, "f");
940
- });
941
- }
942
- catch (err) {
943
- __classPrivateFieldSet(this, _DevServerWebSocket_bufferedAmount, __classPrivateFieldGet(this, _DevServerWebSocket_bufferedAmount, "f") - frame.length, "f");
944
- __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_emitError).call(this, err);
945
- __classPrivateFieldGet(this, _DevServerWebSocket_instances, "m", _DevServerWebSocket_finishClose).call(this, 1006, "", false);
946
- }
947
- }, _DevServerWebSocket_peek = function _DevServerWebSocket_peek(index) {
948
- let remaining = index;
949
- for (const chunk of __classPrivateFieldGet(this, _DevServerWebSocket_chunks, "f")) {
950
- if (remaining < chunk.length)
951
- return chunk[remaining];
952
- remaining -= chunk.length;
953
- }
954
- return 0;
955
- }, _DevServerWebSocket_take = function _DevServerWebSocket_take(n) {
956
- if (n === 0)
957
- return EMPTY;
958
- __classPrivateFieldSet(this, _DevServerWebSocket_queued, __classPrivateFieldGet(this, _DevServerWebSocket_queued, "f") - n, "f");
959
- const first = __classPrivateFieldGet(this, _DevServerWebSocket_chunks, "f")[0];
960
- if (first.length === n)
961
- return __classPrivateFieldGet(this, _DevServerWebSocket_chunks, "f").shift();
962
- if (first.length > n) {
963
- __classPrivateFieldGet(this, _DevServerWebSocket_chunks, "f")[0] = first.subarray(n);
964
- // Copy: the caller unmasks in place and the remainder is still queued.
965
- return Buffer.from(first.subarray(0, n));
966
- }
967
- const out = Buffer.allocUnsafe(n);
968
- let offset = 0;
969
- while (offset < n) {
970
- const chunk = __classPrivateFieldGet(this, _DevServerWebSocket_chunks, "f")[0];
971
- const take = Math.min(chunk.length, n - offset);
972
- chunk.copy(out, offset, 0, take);
973
- offset += take;
974
- if (take === chunk.length)
975
- __classPrivateFieldGet(this, _DevServerWebSocket_chunks, "f").shift();
976
- else
977
- __classPrivateFieldGet(this, _DevServerWebSocket_chunks, "f")[0] = chunk.subarray(take);
978
- }
979
- return out;
398
+ * A WHATWG `WebSocket` over an already-upgraded Node socket.
399
+ *
400
+ * Two deliberate divergences from a browser, both matching the deployed runtime:
401
+ * `binaryType` defaults to `"arraybuffer"` rather than `"blob"`, and `bufferedAmount`
402
+ * is what we have written and not yet seen flushed, which approximates rather than
403
+ * matches a browser's accounting.
404
+ */
405
+ var DevServerWebSocket = class extends EventTarget {
406
+ static {
407
+ this.CONNECTING = CONNECTING;
408
+ }
409
+ static {
410
+ this.OPEN = OPEN;
411
+ }
412
+ static {
413
+ this.CLOSING = CLOSING;
414
+ }
415
+ static {
416
+ this.CLOSED = CLOSED;
417
+ }
418
+ #socket;
419
+ #protocol;
420
+ #readyState = CONNECTING;
421
+ #binaryType = "arraybuffer";
422
+ #bufferedAmount = 0;
423
+ #chunks = [];
424
+ #queued = 0;
425
+ #fragmentOpcode = 0;
426
+ #fragments = [];
427
+ #fragmentBytes = 0;
428
+ #closeSent = false;
429
+ #closeTimer = null;
430
+ #handlers = {
431
+ open: null,
432
+ message: null,
433
+ close: null,
434
+ error: null
435
+ };
436
+ constructor({ socket, url, protocol }) {
437
+ super();
438
+ this.#socket = socket;
439
+ this.url = url;
440
+ this.#protocol = protocol;
441
+ }
442
+ get CONNECTING() {
443
+ return CONNECTING;
444
+ }
445
+ get OPEN() {
446
+ return OPEN;
447
+ }
448
+ get CLOSING() {
449
+ return CLOSING;
450
+ }
451
+ get CLOSED() {
452
+ return CLOSED;
453
+ }
454
+ get protocol() {
455
+ return this.#protocol;
456
+ }
457
+ get extensions() {
458
+ return "";
459
+ }
460
+ get readyState() {
461
+ return this.#readyState;
462
+ }
463
+ get bufferedAmount() {
464
+ return this.#bufferedAmount;
465
+ }
466
+ get binaryType() {
467
+ return this.#binaryType;
468
+ }
469
+ set binaryType(value) {
470
+ if (value !== "arraybuffer" && value !== "blob") throw new DOMException(`binaryType must be "arraybuffer" or "blob", got "${String(value)}"`, "SyntaxError");
471
+ this.#binaryType = value;
472
+ }
473
+ get onopen() {
474
+ return this.#handlers.open;
475
+ }
476
+ set onopen(fn) {
477
+ this.#setHandler("open", fn);
478
+ }
479
+ get onmessage() {
480
+ return this.#handlers.message;
481
+ }
482
+ set onmessage(fn) {
483
+ this.#setHandler("message", fn);
484
+ }
485
+ get onclose() {
486
+ return this.#handlers.close;
487
+ }
488
+ set onclose(fn) {
489
+ this.#setHandler("close", fn);
490
+ }
491
+ get onerror() {
492
+ return this.#handlers.error;
493
+ }
494
+ set onerror(fn) {
495
+ this.#setHandler("error", fn);
496
+ }
497
+ #setHandler(type, fn) {
498
+ const previous = this.#handlers[type];
499
+ if (previous) this.removeEventListener(type, previous);
500
+ this.#handlers[type] = typeof fn === "function" ? fn : null;
501
+ const next = this.#handlers[type];
502
+ if (next) this.addEventListener(type, next);
503
+ }
504
+ send(data) {
505
+ if (this.#readyState === CONNECTING) throw new DOMException("cannot send() while the WebSocket is still CONNECTING; wait for the open event (the socket opens once the handler returns the upgrade response)", "InvalidStateError");
506
+ if (typeof data === "string") {
507
+ const payload = Buffer.from(data, "utf8");
508
+ if (this.#readyState !== OPEN) {
509
+ this.#bufferedAmount += payload.length;
510
+ return;
511
+ }
512
+ this.#writeFrame(OP_TEXT, payload);
513
+ return;
514
+ }
515
+ if (data instanceof Blob) {
516
+ const size = data.size;
517
+ this.#bufferedAmount += size;
518
+ data.arrayBuffer().then((buf) => {
519
+ this.#bufferedAmount -= size;
520
+ if (this.#readyState === OPEN) this.#writeFrame(OP_BINARY, Buffer.from(buf));
521
+ }).catch(() => {
522
+ this.#bufferedAmount -= size;
523
+ });
524
+ return;
525
+ }
526
+ const payload = toBuffer(data);
527
+ if (payload === null) throw new TypeError("send() accepts a string, ArrayBuffer, TypedArray, DataView, or Blob");
528
+ if (this.#readyState !== OPEN) {
529
+ this.#bufferedAmount += payload.length;
530
+ return;
531
+ }
532
+ this.#writeFrame(OP_BINARY, payload);
533
+ }
534
+ close(code, reason) {
535
+ if (code !== void 0 && !isValidCloseCode(code)) throw new DOMException(`close code ${code} is not allowed; use 1000 or a code in 3000-4999`, "InvalidAccessError");
536
+ let reasonBuf = EMPTY;
537
+ if (reason !== void 0 && reason !== null) {
538
+ reasonBuf = Buffer.from(String(reason), "utf8");
539
+ if (reasonBuf.length > MAX_CLOSE_REASON_BYTES) throw new DOMException(`close reason must be at most ${MAX_CLOSE_REASON_BYTES} bytes of UTF-8`, "SyntaxError");
540
+ }
541
+ if (this.#readyState === CLOSING || this.#readyState === CLOSED) return;
542
+ if (this.#readyState === CONNECTING) {
543
+ this.abort(code ?? 1e3, reasonBuf.toString("utf8"));
544
+ return;
545
+ }
546
+ this.#readyState = CLOSING;
547
+ this.#sendClose(code ?? 1e3, reasonBuf);
548
+ this.#closeTimer = setTimeout(() => {
549
+ this.#finishClose(1006, "", false);
550
+ }, CLOSE_HANDSHAKE_TIMEOUT_MS);
551
+ this.#closeTimer.unref?.();
552
+ }
553
+ /** Called once the 101 is on the wire. */
554
+ openWithHead(head) {
555
+ if (this.#readyState !== CONNECTING) return;
556
+ const socket = this.#socket;
557
+ socket.on("data", (chunk) => this.#onData(chunk));
558
+ socket.on("error", (err) => {
559
+ this.#emitError(err);
560
+ this.#finishClose(1006, "", false);
561
+ });
562
+ socket.on("close", () => this.#finishClose(1006, "", false));
563
+ socket.on("end", () => this.#finishClose(1006, "", false));
564
+ this.#readyState = OPEN;
565
+ this.dispatchEvent(new Event("open"));
566
+ if (head.length) this.#onData(head);
567
+ }
568
+ /**
569
+ * Give up before the handshake was written. Fires `close` so a caller holding the
570
+ * socket is not left with something stuck at CONNECTING, but deliberately does not
571
+ * touch the underlying socket: an HTTP error response still has to go out on it.
572
+ */
573
+ abort(code = 1006, reason = "") {
574
+ if (this.#readyState === CLOSED) return;
575
+ this.#readyState = CLOSED;
576
+ if (this.#closeTimer) {
577
+ clearTimeout(this.#closeTimer);
578
+ this.#closeTimer = null;
579
+ }
580
+ this.dispatchEvent(new CloseEventCtor("close", {
581
+ code,
582
+ reason,
583
+ wasClean: false
584
+ }));
585
+ }
586
+ #onData(chunk) {
587
+ if (this.#readyState === CLOSED) return;
588
+ this.#chunks.push(chunk);
589
+ this.#queued += chunk.length;
590
+ try {
591
+ this.#parse();
592
+ } catch (err) {
593
+ this.#emitError(err);
594
+ const code = err.wsCode ?? 1002;
595
+ this.#fail(code, err instanceof Error ? err.message : String(err));
596
+ }
597
+ }
598
+ #parse() {
599
+ for (;;) {
600
+ if (this.#queued < 2) return;
601
+ const b0 = this.#peek(0);
602
+ const b1 = this.#peek(1);
603
+ const fin = (b0 & 128) !== 0;
604
+ const rsv = b0 & 112;
605
+ const opcode = b0 & 15;
606
+ const masked = (b1 & 128) !== 0;
607
+ let length = b1 & 127;
608
+ let headerLen = 2;
609
+ if (length === 126) {
610
+ if (this.#queued < 4) return;
611
+ length = this.#peek(2) << 8 | this.#peek(3);
612
+ headerLen = 4;
613
+ } else if (length === 127) {
614
+ if (this.#queued < 10) return;
615
+ let big = 0n;
616
+ for (let i = 0; i < 8; i++) big = big << 8n | BigInt(this.#peek(2 + i));
617
+ if (big > BigInt(MAX_MESSAGE_BYTES)) throw protocolError(1009, "frame too large");
618
+ length = Number(big);
619
+ headerLen = 10;
620
+ }
621
+ if (masked) headerLen += 4;
622
+ if (this.#queued < headerLen + length) return;
623
+ if (rsv !== 0) throw protocolError(1002, "reserved bits must be clear (no extensions were negotiated)");
624
+ if (!masked) throw protocolError(1002, "client frames must be masked");
625
+ const isControl = (opcode & 8) !== 0;
626
+ if (isControl) {
627
+ if (!fin) throw protocolError(1002, "control frames must not be fragmented");
628
+ if (length > MAX_CONTROL_PAYLOAD) throw protocolError(1002, "control frame payload exceeds 125 bytes");
629
+ }
630
+ if (opcode !== OP_CONTINUATION && opcode !== OP_TEXT && opcode !== OP_BINARY && opcode !== OP_CLOSE && opcode !== OP_PING && opcode !== OP_PONG) throw protocolError(1002, `reserved opcode 0x${opcode.toString(16)}`);
631
+ const header = this.#take(headerLen);
632
+ const payload = this.#take(length);
633
+ if (masked) {
634
+ const mask = header.subarray(headerLen - 4, headerLen);
635
+ for (let i = 0; i < payload.length; i++) payload[i] = payload[i] ^ mask[i & 3];
636
+ }
637
+ if (isControl) {
638
+ this.#handleControl(opcode, payload);
639
+ if (this.#readyState === CLOSED) return;
640
+ continue;
641
+ }
642
+ if (opcode === OP_CONTINUATION) {
643
+ if (this.#fragmentOpcode === 0) throw protocolError(1002, "continuation frame without a start frame");
644
+ this.#pushFragment(payload);
645
+ } else {
646
+ if (this.#fragmentOpcode !== 0) throw protocolError(1002, "new data frame while a fragmented message was open");
647
+ if (!fin) {
648
+ this.#fragmentOpcode = opcode;
649
+ this.#pushFragment(payload);
650
+ continue;
651
+ }
652
+ this.#deliver(opcode, payload);
653
+ continue;
654
+ }
655
+ if (fin) {
656
+ const messageOpcode = this.#fragmentOpcode;
657
+ const full = this.#fragments.length === 1 ? this.#fragments[0] : Buffer.concat(this.#fragments);
658
+ this.#fragmentOpcode = 0;
659
+ this.#fragments = [];
660
+ this.#fragmentBytes = 0;
661
+ this.#deliver(messageOpcode, full);
662
+ }
663
+ }
664
+ }
665
+ #pushFragment(payload) {
666
+ this.#fragmentBytes += payload.length;
667
+ if (this.#fragmentBytes > MAX_MESSAGE_BYTES) throw protocolError(1009, `message exceeds the ${MAX_MESSAGE_BYTES} byte limit`);
668
+ if (payload.length === 0) return;
669
+ this.#fragments.push(payload);
670
+ }
671
+ #deliver(opcode, payload) {
672
+ if (this.#readyState !== OPEN) return;
673
+ let data;
674
+ if (opcode === OP_TEXT) try {
675
+ data = new TextDecoder("utf-8", { fatal: true }).decode(payload);
676
+ } catch {
677
+ throw protocolError(1007, "text frame is not valid UTF-8");
678
+ }
679
+ else if (this.#binaryType === "blob") data = new Blob([new Uint8Array(payload)]);
680
+ else data = payload.buffer.slice(payload.byteOffset, payload.byteOffset + payload.byteLength);
681
+ this.dispatchEvent(new MessageEvent("message", {
682
+ data,
683
+ origin: this.url
684
+ }));
685
+ }
686
+ #handleControl(opcode, payload) {
687
+ if (opcode === OP_PING) {
688
+ if (this.#readyState === OPEN) this.#writeFrame(OP_PONG, payload);
689
+ return;
690
+ }
691
+ if (opcode === OP_PONG) return;
692
+ let code = 1005;
693
+ let reason = "";
694
+ if (payload.length === 1) throw protocolError(1002, "close frame payload must be empty or at least 2 bytes");
695
+ if (payload.length >= 2) {
696
+ code = payload[0] << 8 | payload[1];
697
+ if (!isEchoableCloseCode(code)) throw protocolError(1002, `invalid close code ${code}`);
698
+ try {
699
+ reason = new TextDecoder("utf-8", { fatal: true }).decode(payload.subarray(2));
700
+ } catch {
701
+ throw protocolError(1007, "close reason is not valid UTF-8");
702
+ }
703
+ }
704
+ if (!this.#closeSent) this.#sendClose(code === 1005 ? 1e3 : code, EMPTY);
705
+ this.#finishClose(code, reason, true);
706
+ }
707
+ #sendClose(code, reasonBuf) {
708
+ if (this.#closeSent) return;
709
+ this.#closeSent = true;
710
+ const payload = Buffer.allocUnsafe(2 + reasonBuf.length);
711
+ payload.writeUInt16BE(code, 0);
712
+ if (reasonBuf.length) reasonBuf.copy(payload, 2);
713
+ this.#writeFrame(OP_CLOSE, payload);
714
+ }
715
+ #fail(code, reason) {
716
+ if (this.#readyState === OPEN) {
717
+ const reasonBuf = Buffer.from(reason.slice(0, MAX_CLOSE_REASON_BYTES), "utf8");
718
+ try {
719
+ this.#sendClose(code, reasonBuf);
720
+ } catch {}
721
+ }
722
+ this.#finishClose(code, reason, false);
723
+ }
724
+ #finishClose(code, reason, wasClean) {
725
+ if (this.#readyState === CLOSED) return;
726
+ this.#readyState = CLOSED;
727
+ if (this.#closeTimer) {
728
+ clearTimeout(this.#closeTimer);
729
+ this.#closeTimer = null;
730
+ }
731
+ this.#chunks = [];
732
+ this.#queued = 0;
733
+ this.#fragments = [];
734
+ this.#fragmentBytes = 0;
735
+ try {
736
+ this.#socket.end();
737
+ } catch {
738
+ try {
739
+ this.#socket.destroy();
740
+ } catch {}
741
+ }
742
+ this.dispatchEvent(new CloseEventCtor("close", {
743
+ code,
744
+ reason,
745
+ wasClean
746
+ }));
747
+ }
748
+ #emitError(err) {
749
+ const message = err instanceof Error ? err.message : String(err);
750
+ this.dispatchEvent(new ErrorEventCtor("error", {
751
+ message,
752
+ error: err
753
+ }));
754
+ }
755
+ #writeFrame(opcode, payload) {
756
+ const frame = encodeFrame(opcode, payload);
757
+ this.#bufferedAmount += frame.length;
758
+ try {
759
+ this.#socket.write(frame, () => {
760
+ this.#bufferedAmount -= frame.length;
761
+ if (this.#bufferedAmount < 0) this.#bufferedAmount = 0;
762
+ });
763
+ } catch (err) {
764
+ this.#bufferedAmount -= frame.length;
765
+ this.#emitError(err);
766
+ this.#finishClose(1006, "", false);
767
+ }
768
+ }
769
+ /** Read a byte without consuming it. */
770
+ #peek(index) {
771
+ let remaining = index;
772
+ for (const chunk of this.#chunks) {
773
+ if (remaining < chunk.length) return chunk[remaining];
774
+ remaining -= chunk.length;
775
+ }
776
+ return 0;
777
+ }
778
+ /** Remove `n` bytes from the queue and return them as one owned Buffer. */
779
+ #take(n) {
780
+ if (n === 0) return EMPTY;
781
+ this.#queued -= n;
782
+ const first = this.#chunks[0];
783
+ if (first.length === n) return this.#chunks.shift();
784
+ if (first.length > n) {
785
+ this.#chunks[0] = first.subarray(n);
786
+ return Buffer.from(first.subarray(0, n));
787
+ }
788
+ const out = Buffer.allocUnsafe(n);
789
+ let offset = 0;
790
+ while (offset < n) {
791
+ const chunk = this.#chunks[0];
792
+ const take = Math.min(chunk.length, n - offset);
793
+ chunk.copy(out, offset, 0, take);
794
+ offset += take;
795
+ if (take === chunk.length) this.#chunks.shift();
796
+ else this.#chunks[0] = chunk.subarray(take);
797
+ }
798
+ return out;
799
+ }
980
800
  };
981
- DevServerWebSocket.CONNECTING = CONNECTING;
982
- DevServerWebSocket.OPEN = OPEN;
983
- DevServerWebSocket.CLOSING = CLOSING;
984
- DevServerWebSocket.CLOSED = CLOSED;
985
801
  const encodeFrame = (opcode, payload) => {
986
- const len = payload.length;
987
- let header;
988
- if (len < 126) {
989
- header = Buffer.allocUnsafe(2);
990
- header[1] = len;
991
- }
992
- else if (len < 65536) {
993
- header = Buffer.allocUnsafe(4);
994
- header[1] = 126;
995
- header.writeUInt16BE(len, 2);
996
- }
997
- else {
998
- header = Buffer.allocUnsafe(10);
999
- header[1] = 127;
1000
- header.writeBigUInt64BE(BigInt(len), 2);
1001
- }
1002
- header[0] = 0x80 | opcode;
1003
- return Buffer.concat([header, payload]);
802
+ const len = payload.length;
803
+ let header;
804
+ if (len < 126) {
805
+ header = Buffer.allocUnsafe(2);
806
+ header[1] = len;
807
+ } else if (len < 65536) {
808
+ header = Buffer.allocUnsafe(4);
809
+ header[1] = 126;
810
+ header.writeUInt16BE(len, 2);
811
+ } else {
812
+ header = Buffer.allocUnsafe(10);
813
+ header[1] = 127;
814
+ header.writeBigUInt64BE(BigInt(len), 2);
815
+ }
816
+ header[0] = 128 | opcode;
817
+ return Buffer.concat([header, payload]);
1004
818
  };
1005
819
  const toBuffer = (data) => {
1006
- if (data instanceof ArrayBuffer)
1007
- return Buffer.from(new Uint8Array(data));
1008
- if (ArrayBuffer.isView(data)) {
1009
- return Buffer.from(new Uint8Array(data.buffer, data.byteOffset, data.byteLength));
1010
- }
1011
- return null;
820
+ if (data instanceof ArrayBuffer) return Buffer.from(new Uint8Array(data));
821
+ if (ArrayBuffer.isView(data)) return Buffer.from(new Uint8Array(data.buffer, data.byteOffset, data.byteLength));
822
+ return null;
1012
823
  };
1013
824
  /** Codes a server may send (§7.4): 1000, or the private 3000-4999 range. */
1014
- const isValidCloseCode = (code) => code === 1000 || (Number.isInteger(code) && code >= 3000 && code <= 4999);
825
+ const isValidCloseCode = (code) => code === 1e3 || Number.isInteger(code) && code >= 3e3 && code <= 4999;
1015
826
  /** Codes that may legally appear in a close frame we receive — wider than the above. */
1016
827
  const isEchoableCloseCode = (code) => {
1017
- if (!Number.isInteger(code))
1018
- return false;
1019
- if (code >= 3000 && code <= 4999)
1020
- return true;
1021
- return (code >= 1000 &&
1022
- code <= 1014 &&
1023
- code !== 1004 &&
1024
- code !== 1005 &&
1025
- code !== 1006);
828
+ if (!Number.isInteger(code)) return false;
829
+ if (code >= 3e3 && code <= 4999) return true;
830
+ return code >= 1e3 && code <= 1014 && code !== 1004 && code !== 1005 && code !== 1006;
1026
831
  };
1027
832
  const protocolError = (code, message) => {
1028
- const err = new Error(message);
1029
- err.wsCode = code;
1030
- return err;
833
+ const err = new Error(message);
834
+ err.wsCode = code;
835
+ return err;
1031
836
  };
837
+ //#endregion
838
+ export { CloseEventCtor, acceptKey, createUpgradeListener, installWebSocketBridge, isWebSocketHandshake, upgradeWebSocket, validateHandshake, writeUpgradeRejection };