neon 2.44.0 → 2.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dev/runtime.js +45 -1
- package/dist/dev/websocket.js +1031 -0
- package/package.json +4 -4
package/dist/dev/runtime.js
CHANGED
|
@@ -2,6 +2,7 @@ import { createServer } from "node:http";
|
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { pathToFileURL } from "node:url";
|
|
4
4
|
import { getRequestListener } from "@hono/node-server";
|
|
5
|
+
import { createUpgradeListener, installWebSocketBridge, } from "./websocket.js";
|
|
5
6
|
const isFunction = (value) => typeof value === "function";
|
|
6
7
|
const hasFetchMethod = (value) => typeof value === "object" &&
|
|
7
8
|
value !== null &&
|
|
@@ -27,6 +28,30 @@ export const resolveFetchHandler = (mod) => {
|
|
|
27
28
|
" export default { fetch(req) { /* ... */ } }\n" +
|
|
28
29
|
" export default function (req) { /* ... */ }");
|
|
29
30
|
};
|
|
31
|
+
const hasUpgradeMethod = (value) => typeof value === "object" &&
|
|
32
|
+
value !== null &&
|
|
33
|
+
"upgrade" in value &&
|
|
34
|
+
typeof value.upgrade === "function";
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the user's optional WebSocket entrypoint: a named `export function upgrade`,
|
|
37
|
+
* or an `upgrade` method on the default export. `undefined` when the module has
|
|
38
|
+
* neither, which is the common case — a function without one either uses
|
|
39
|
+
* `upgradeWebSocket()` inside `fetch` or serves no WebSockets at all.
|
|
40
|
+
*
|
|
41
|
+
* Resolution order matches the deployed runtime exactly (named export first, then the
|
|
42
|
+
* default-export method), so a module that resolves one way locally cannot resolve the
|
|
43
|
+
* other way once deployed.
|
|
44
|
+
*/
|
|
45
|
+
export const resolveUpgradeHandler = (mod) => {
|
|
46
|
+
if (typeof mod.upgrade === "function")
|
|
47
|
+
return mod.upgrade;
|
|
48
|
+
const defaultExport = mod.default;
|
|
49
|
+
if (hasUpgradeMethod(defaultExport)) {
|
|
50
|
+
const target = defaultExport;
|
|
51
|
+
return (req, socket, head) => target.upgrade(req, socket, head);
|
|
52
|
+
}
|
|
53
|
+
return undefined;
|
|
54
|
+
};
|
|
30
55
|
/**
|
|
31
56
|
* Wrap a fetch handler so user errors become a 500 response (with the message
|
|
32
57
|
* in the body during dev) instead of crashing the child process.
|
|
@@ -87,11 +112,30 @@ const listen = (server, port, hostname) => new Promise((resolveListen, rejectLis
|
|
|
87
112
|
export const startRuntime = async ({ source, port, hostname, }) => {
|
|
88
113
|
const absoluteSource = resolve(process.cwd(), source);
|
|
89
114
|
const mod = (await import(pathToFileURL(absoluteSource).href));
|
|
90
|
-
const
|
|
115
|
+
const fetchHandler = resolveFetchHandler(mod);
|
|
116
|
+
const handler = withErrorBoundary(fetchHandler);
|
|
117
|
+
// Publish the bridge `upgradeWebSocket()` reads before the user module can serve a
|
|
118
|
+
// request, so the helper resolves locally exactly as it does when deployed.
|
|
119
|
+
installWebSocketBridge();
|
|
91
120
|
const listener = getRequestListener(handler, { hostname });
|
|
92
121
|
const server = createServer((incoming, outgoing) => {
|
|
93
122
|
void listener(incoming, outgoing);
|
|
94
123
|
});
|
|
124
|
+
// Node emits 'upgrade' rather than 'request' for a WebSocket handshake. Without
|
|
125
|
+
// this listener Node hands the handshake to the ordinary request handler, which
|
|
126
|
+
// answers 200 on a connection the client expects to be a 101 — so a function's
|
|
127
|
+
// WebSocket code silently never ran under `neon dev`.
|
|
128
|
+
//
|
|
129
|
+
// The upgrade path takes the RAW handler, not the error-boundary-wrapped one. The
|
|
130
|
+
// boundary turns a throw into a 500 Response, which on this path is
|
|
131
|
+
// indistinguishable from a handler that deliberately declined the upgrade — so a
|
|
132
|
+
// crashing handler would answer 501 ("no WebSocket support") instead of surfacing
|
|
133
|
+
// the error. The upgrade listener has its own equivalent boundary, and reports a
|
|
134
|
+
// throw as the 502 the deployed runtime returns.
|
|
135
|
+
server.on("upgrade", createUpgradeListener({
|
|
136
|
+
fetch: fetchHandler,
|
|
137
|
+
upgrade: resolveUpgradeHandler(mod),
|
|
138
|
+
}));
|
|
95
139
|
const boundPort = await bindPort(server, port, hostname);
|
|
96
140
|
process.stdout.write(`neon-dev:ready ${boundPort}\n`);
|
|
97
141
|
return boundPort;
|
|
@@ -0,0 +1,1031 @@
|
|
|
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
|
+
import { createHash } from "node:crypto";
|
|
39
|
+
/** The bridge global `@neon/functions` reads. Must match the deployed runtime. */
|
|
40
|
+
const WS_BRIDGE_KEY = Symbol.for("neon.websocket.bridge");
|
|
41
|
+
/** Brand identifying the response the helper produced. Non-enumerable. */
|
|
42
|
+
const UPGRADE_RECORD = Symbol.for("neon.websocket.upgradeRecord");
|
|
43
|
+
/** RFC 6455 §1.3. */
|
|
44
|
+
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;
|
|
51
|
+
const CONNECTING = 0;
|
|
52
|
+
const OPEN = 1;
|
|
53
|
+
const CLOSING = 2;
|
|
54
|
+
const CLOSED = 3;
|
|
55
|
+
/** Reassembly ceiling: a fragmented message buffers until FIN, so it must be bounded. */
|
|
56
|
+
const MAX_MESSAGE_BYTES = 16 * 1024 * 1024;
|
|
57
|
+
/** Control frames carry at most 125 bytes and are never fragmented (§5.5). */
|
|
58
|
+
const MAX_CONTROL_PAYLOAD = 125;
|
|
59
|
+
/** A close reason must fit the 123 bytes left after the 2-byte code. */
|
|
60
|
+
const MAX_CLOSE_REASON_BYTES = 123;
|
|
61
|
+
/** How long to wait for the peer's answering close frame (§7.1.1 leaves this open). */
|
|
62
|
+
const CLOSE_HANDSHAKE_TIMEOUT_MS = 5000;
|
|
63
|
+
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");
|
|
90
|
+
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 : "";
|
|
95
|
+
};
|
|
96
|
+
/** 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 -----------------------------------------
|
|
100
|
+
/** Primary lookup: the exact Request object the dev server built. */
|
|
101
|
+
const pendingByRequest = new WeakMap();
|
|
102
|
+
/**
|
|
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
|
+
*/
|
|
106
|
+
const upgradeStore = new AsyncLocalStorage();
|
|
107
|
+
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;
|
|
119
|
+
};
|
|
120
|
+
const readUpgradeRecord = (value) => {
|
|
121
|
+
if (!value || typeof value !== "object")
|
|
122
|
+
return null;
|
|
123
|
+
return (value[UPGRADE_RECORD] ??
|
|
124
|
+
null);
|
|
125
|
+
};
|
|
126
|
+
// -- The Response subclass ---------------------------------------------------
|
|
127
|
+
/**
|
|
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 -------------------------------------------------------
|
|
157
|
+
/** 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
|
+
};
|
|
214
|
+
};
|
|
215
|
+
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}`;
|
|
219
|
+
};
|
|
220
|
+
/** 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
|
+
});
|
|
228
|
+
};
|
|
229
|
+
// -- Handshake completion ----------------------------------------------------
|
|
230
|
+
/** 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",
|
|
240
|
+
]);
|
|
241
|
+
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);
|
|
287
|
+
};
|
|
288
|
+
// -- Reject / relay helpers --------------------------------------------------
|
|
289
|
+
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",
|
|
303
|
+
};
|
|
304
|
+
/**
|
|
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
|
+
}
|
|
329
|
+
};
|
|
330
|
+
/** 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",
|
|
337
|
+
]);
|
|
338
|
+
/**
|
|
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
|
+
*/
|
|
343
|
+
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]));
|
|
355
|
+
};
|
|
356
|
+
/** 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;
|
|
359
|
+
/**
|
|
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;
|
|
389
|
+
};
|
|
390
|
+
/**
|
|
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
|
+
};
|
|
415
|
+
};
|
|
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);
|
|
467
|
+
};
|
|
468
|
+
/**
|
|
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
|
+
*/
|
|
474
|
+
const abortClaimedUpgrade = (record) => {
|
|
475
|
+
try {
|
|
476
|
+
record.ws?.abort(1011, "upgrade failed");
|
|
477
|
+
}
|
|
478
|
+
catch {
|
|
479
|
+
/* best effort */
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
/**
|
|
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
|
+
*/
|
|
487
|
+
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
|
+
});
|
|
507
|
+
};
|
|
508
|
+
/**
|
|
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;
|
|
980
|
+
};
|
|
981
|
+
DevServerWebSocket.CONNECTING = CONNECTING;
|
|
982
|
+
DevServerWebSocket.OPEN = OPEN;
|
|
983
|
+
DevServerWebSocket.CLOSING = CLOSING;
|
|
984
|
+
DevServerWebSocket.CLOSED = CLOSED;
|
|
985
|
+
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]);
|
|
1004
|
+
};
|
|
1005
|
+
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;
|
|
1012
|
+
};
|
|
1013
|
+
/** 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);
|
|
1015
|
+
/** Codes that may legally appear in a close frame we receive — wider than the above. */
|
|
1016
|
+
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);
|
|
1026
|
+
};
|
|
1027
|
+
const protocolError = (code, message) => {
|
|
1028
|
+
const err = new Error(message);
|
|
1029
|
+
err.wsCode = code;
|
|
1030
|
+
return err;
|
|
1031
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neon",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.45.0",
|
|
4
4
|
"description": "CLI tool for Neon, the cloud backend primitives built around Lakebase Postgres",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"neon",
|
|
@@ -55,11 +55,11 @@
|
|
|
55
55
|
"which": "3.0.1",
|
|
56
56
|
"yaml": "^2.9.0",
|
|
57
57
|
"yargs": "17.7.2",
|
|
58
|
-
"@neon/config-runtime": "0.12.4",
|
|
59
|
-
"@neon/env": "0.14.0",
|
|
60
58
|
"@neon/sdk": "1.5.0",
|
|
59
|
+
"@neon/config": "0.14.0",
|
|
60
|
+
"@neon/config-runtime": "0.12.4",
|
|
61
61
|
"neon-init": "0.20.7",
|
|
62
|
-
"@neon/
|
|
62
|
+
"@neon/env": "0.14.0"
|
|
63
63
|
},
|
|
64
64
|
"optionalDependencies": {
|
|
65
65
|
"esbuild": "0.28.1"
|