agentfootprint 7.24.0 → 7.25.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 (71) hide show
  1. package/dist/adapters/hosting/agentcore.js +112 -1
  2. package/dist/adapters/hosting/agentcore.js.map +1 -1
  3. package/dist/esm/adapters/hosting/agentcore.d.ts +50 -1
  4. package/dist/esm/adapters/hosting/agentcore.js +110 -0
  5. package/dist/esm/adapters/hosting/agentcore.js.map +1 -1
  6. package/dist/esm/hosting/errors.d.ts +44 -2
  7. package/dist/esm/hosting/errors.js +63 -0
  8. package/dist/esm/hosting/errors.js.map +1 -1
  9. package/dist/esm/hosting/headers.d.ts +16 -0
  10. package/dist/esm/hosting/headers.js +24 -0
  11. package/dist/esm/hosting/headers.js.map +1 -0
  12. package/dist/esm/hosting/httpHost.d.ts +70 -4
  13. package/dist/esm/hosting/httpHost.js +211 -47
  14. package/dist/esm/hosting/httpHost.js.map +1 -1
  15. package/dist/esm/hosting/index.d.ts +23 -8
  16. package/dist/esm/hosting/index.js +21 -6
  17. package/dist/esm/hosting/index.js.map +1 -1
  18. package/dist/esm/hosting/nodeHost.d.ts +23 -0
  19. package/dist/esm/hosting/nodeHost.js +21 -1
  20. package/dist/esm/hosting/nodeHost.js.map +1 -1
  21. package/dist/esm/hosting/types.d.ts +212 -6
  22. package/dist/esm/hosting/types.js +7 -5
  23. package/dist/esm/hosting/types.js.map +1 -1
  24. package/dist/esm/hosting/webSocketConversation.d.ts +101 -0
  25. package/dist/esm/hosting/webSocketConversation.js +341 -0
  26. package/dist/esm/hosting/webSocketConversation.js.map +1 -0
  27. package/dist/esm/hosting/webSocketFrames.d.ts +164 -0
  28. package/dist/esm/hosting/webSocketFrames.js +284 -0
  29. package/dist/esm/hosting/webSocketFrames.js.map +1 -0
  30. package/dist/esm/hosting-providers.d.ts +7 -2
  31. package/dist/esm/hosting-providers.js +7 -2
  32. package/dist/esm/hosting-providers.js.map +1 -1
  33. package/dist/hosting/errors.js +66 -1
  34. package/dist/hosting/errors.js.map +1 -1
  35. package/dist/hosting/headers.js +28 -0
  36. package/dist/hosting/headers.js.map +1 -0
  37. package/dist/hosting/httpHost.js +211 -47
  38. package/dist/hosting/httpHost.js.map +1 -1
  39. package/dist/hosting/index.js +23 -6
  40. package/dist/hosting/index.js.map +1 -1
  41. package/dist/hosting/nodeHost.js +20 -0
  42. package/dist/hosting/nodeHost.js.map +1 -1
  43. package/dist/hosting/types.js +7 -5
  44. package/dist/hosting/types.js.map +1 -1
  45. package/dist/hosting/webSocketConversation.js +345 -0
  46. package/dist/hosting/webSocketConversation.js.map +1 -0
  47. package/dist/hosting/webSocketFrames.js +297 -0
  48. package/dist/hosting/webSocketFrames.js.map +1 -0
  49. package/dist/hosting-providers.js +8 -2
  50. package/dist/hosting-providers.js.map +1 -1
  51. package/dist/types/adapters/hosting/agentcore.d.ts +50 -1
  52. package/dist/types/adapters/hosting/agentcore.d.ts.map +1 -1
  53. package/dist/types/hosting/errors.d.ts +44 -2
  54. package/dist/types/hosting/errors.d.ts.map +1 -1
  55. package/dist/types/hosting/headers.d.ts +17 -0
  56. package/dist/types/hosting/headers.d.ts.map +1 -0
  57. package/dist/types/hosting/httpHost.d.ts +70 -4
  58. package/dist/types/hosting/httpHost.d.ts.map +1 -1
  59. package/dist/types/hosting/index.d.ts +23 -8
  60. package/dist/types/hosting/index.d.ts.map +1 -1
  61. package/dist/types/hosting/nodeHost.d.ts +23 -0
  62. package/dist/types/hosting/nodeHost.d.ts.map +1 -1
  63. package/dist/types/hosting/types.d.ts +212 -6
  64. package/dist/types/hosting/types.d.ts.map +1 -1
  65. package/dist/types/hosting/webSocketConversation.d.ts +102 -0
  66. package/dist/types/hosting/webSocketConversation.d.ts.map +1 -0
  67. package/dist/types/hosting/webSocketFrames.d.ts +165 -0
  68. package/dist/types/hosting/webSocketFrames.d.ts.map +1 -0
  69. package/dist/types/hosting-providers.d.ts +7 -2
  70. package/dist/types/hosting-providers.d.ts.map +1 -1
  71. package/package.json +1 -1
@@ -0,0 +1,345 @@
1
+ "use strict";
2
+ /**
3
+ * hosting/webSocketConversation — one upgraded socket, presented as a
4
+ * {@link HostConversation}.
5
+ *
6
+ * This is the whole adapter side of the conversation port: it takes the
7
+ * `'upgrade'` event a `node:http` server hands it, answers the handshake, and
8
+ * turns the frames flowing over the socket into the six-member port a handler
9
+ * sees. Everything protocol-shaped lives in `webSocketFrames.ts`; everything
10
+ * port-shaped lives in `types.ts`; this file is the join.
11
+ *
12
+ * ── The laws it keeps, all of them pinned by tests ───────────────────────────
13
+ * - **A path this door does not own is not touched.** `node:http` calls EVERY
14
+ * `'upgrade'` listener for every upgrade, exactly as it calls every
15
+ * `'request'` listener — so a caller's own protocol lives beside this one on
16
+ * the same socket. Whether an unclaimed path gets an answer is the caller's
17
+ * business on a server they own, and this door's only on a server it owns.
18
+ * - **Frames that arrive before the handler subscribes are held, up to a
19
+ * declared bound.** An `async` handler that awaits before calling `onFrame`
20
+ * would otherwise lose the far side's opening frame. The bound is in BYTES
21
+ * and overflow ends the conversation with a stated reason, because an
22
+ * unbounded queue somebody else fills is a way to kill this process, and an
23
+ * undeclared ceiling is the exact thing the declared-ceilings rule exists to
24
+ * forbid.
25
+ * - **`close()` ends every live conversation before the socket is released.**
26
+ * Measured, not assumed: an upgraded socket keeps `server.close()` waiting
27
+ * forever, so a door that let go of its conversations would hang the whole
28
+ * shutdown.
29
+ * - **The port's frame is the whole message.** A message the transport
30
+ * delivered in fragments counts against `maxFrameBytes` in total, so
31
+ * fragmentation cannot be used to walk around the ceiling.
32
+ *
33
+ * Pattern: Adapter. Role: outer ring, one transport.
34
+ */
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.conversationDoor = void 0;
37
+ const errors_js_1 = require("./errors.js");
38
+ const headers_js_1 = require("./headers.js");
39
+ const webSocketFrames_js_1 = require("./webSocketFrames.js");
40
+ /** How long a politely-closed socket has to say goodbye before it is dropped. */
41
+ const CLOSE_GRACE_MS = 1_000;
42
+ function conversationDoor(options) {
43
+ const live = new Set();
44
+ return {
45
+ get liveCount() {
46
+ return live.size;
47
+ },
48
+ handleUpgrade(request, socket) {
49
+ const url = request.url ?? '';
50
+ if (url.split('?')[0] !== options.path)
51
+ return false;
52
+ // From here the upgrade is ours, and every exit answers it.
53
+ socket.on('error', () => undefined);
54
+ if (!options.accepting()) {
55
+ refuse(socket, 503, `the '${options.hostName}' host is closed and is not taking new conversations`);
56
+ return true;
57
+ }
58
+ const headers = (0, headers_js_1.lowerCasedHeaders)(request.headers);
59
+ const key = headers['sec-websocket-key'];
60
+ const upgrade = (headers.upgrade ?? '').toLowerCase();
61
+ if (upgrade !== 'websocket' || headers['sec-websocket-version'] !== '13' || !key) {
62
+ refuse(socket, 400, `the '${options.hostName}' host serves version-13 WebSocket conversations on ` +
63
+ `${options.path}, and this upgrade did not offer one`);
64
+ return true;
65
+ }
66
+ const facts = {
67
+ headers,
68
+ query: new URLSearchParams(url.split('?')[1] ?? ''),
69
+ };
70
+ const read = options.readConversation?.(facts) ?? {};
71
+ socket.write((0, webSocketFrames_js_1.handshakeResponse)(key, read.protocol));
72
+ const conversation = openConversation(socket, {
73
+ hostName: options.hostName,
74
+ limits: options.limits,
75
+ ...(read.sessionId !== undefined && { sessionId: read.sessionId }),
76
+ headers: { ...headers, ...read.headers },
77
+ });
78
+ live.add(conversation);
79
+ void conversation.ended.finally(() => live.delete(conversation));
80
+ // A handler that throws ends THAT conversation and nothing else: one
81
+ // caller's bad frame is not an outage for everyone else on this door.
82
+ void (async () => {
83
+ try {
84
+ await options.handler(conversation.port);
85
+ }
86
+ catch (err) {
87
+ conversation.end(`the conversation handler threw: ${asMessage(err)}`);
88
+ }
89
+ })();
90
+ return true;
91
+ },
92
+ async closeAll(reason) {
93
+ const ending = [...live];
94
+ for (const conversation of ending)
95
+ conversation.end(reason);
96
+ // Waited on, not fired and forgotten: an upgraded socket that is still
97
+ // open keeps `server.close()` waiting forever, so "the door is closed"
98
+ // has to mean the sockets are actually gone.
99
+ await Promise.allSettled(ending.map((conversation) => conversation.ended));
100
+ },
101
+ };
102
+ }
103
+ exports.conversationDoor = conversationDoor;
104
+ /** Answer an upgrade we will not carry, in the one language a pre-101 socket speaks. */
105
+ function refuse(socket, status, message) {
106
+ const text = `[hosting] ${message}.`;
107
+ const reason = status === 503 ? 'Service Unavailable' : 'Bad Request';
108
+ socket.end(`HTTP/1.1 ${status} ${reason}\r\n` +
109
+ `content-type: text/plain\r\n` +
110
+ `content-length: ${Buffer.byteLength(text)}\r\n` +
111
+ `connection: close\r\n\r\n${text}`);
112
+ }
113
+ /**
114
+ * One socket, presented as a conversation.
115
+ *
116
+ * Written as a closure rather than a class because everything here is one
117
+ * socket's state and none of it is anybody else's business — there is nothing
118
+ * to subclass and nothing to inject.
119
+ */
120
+ function openConversation(socket, options) {
121
+ const { hostName, limits, sessionId } = options;
122
+ const maxFrameBytes = limits.maxFrameBytes;
123
+ const maxPendingBytes = limits.maxPendingBytes;
124
+ const reader = new webSocketFrames_js_1.FrameReader(maxFrameBytes);
125
+ const frameSubscribers = new Set();
126
+ const closeSubscribers = new Set();
127
+ /** Frames the far side sent before anybody was listening. Bounded (see below). */
128
+ let pending = [];
129
+ let pendingBytes = 0;
130
+ /** The message being assembled out of fragments, if any. */
131
+ let assembling = [];
132
+ let assemblingBytes = 0;
133
+ let assemblingText = false;
134
+ let ending;
135
+ let resolveEnded;
136
+ const ended = new Promise((resolve) => {
137
+ resolveEnded = resolve;
138
+ });
139
+ function finish(close, wireCode, wireReason) {
140
+ if (ending !== undefined)
141
+ return;
142
+ ending = close;
143
+ if (wireCode !== undefined && socket.writable) {
144
+ socket.write((0, webSocketFrames_js_1.encodeClose)(wireCode, wireReason ?? close.reason));
145
+ }
146
+ // end() flushes what is queued and then sends FIN — the polite half of the
147
+ // close. The timer is the impolite half, for a peer that never answers.
148
+ socket.end();
149
+ const grace = setTimeout(() => socket.destroy(), CLOSE_GRACE_MS);
150
+ if (typeof grace.unref === 'function')
151
+ grace.unref();
152
+ socket.once('close', () => clearTimeout(grace));
153
+ const subscribers = [...closeSubscribers];
154
+ closeSubscribers.clear();
155
+ for (const cb of subscribers) {
156
+ try {
157
+ cb(close);
158
+ }
159
+ catch {
160
+ // A close subscriber that throws has nothing left to be told; the
161
+ // conversation is already over and there is nothing to protect.
162
+ }
163
+ }
164
+ resolveEnded();
165
+ }
166
+ function deliver(frame) {
167
+ if (ending !== undefined)
168
+ return;
169
+ if (frameSubscribers.size === 0) {
170
+ pendingBytes += Buffer.byteLength(frame, 'utf8');
171
+ if (maxPendingBytes !== undefined && pendingBytes > maxPendingBytes) {
172
+ pending = [];
173
+ finish({
174
+ by: 'host',
175
+ reason: `${pendingBytes} bytes arrived before onFrame(...) was subscribed, past the ` +
176
+ `declared maxPendingBytes of ${maxPendingBytes}`,
177
+ }, webSocketFrames_js_1.CLOSE_CODE.tooBig);
178
+ return;
179
+ }
180
+ pending.push(frame);
181
+ return;
182
+ }
183
+ for (const cb of [...frameSubscribers]) {
184
+ try {
185
+ cb(frame);
186
+ }
187
+ catch (err) {
188
+ // Feeding more frames to a consumer whose parser already threw is how a
189
+ // channel keeps looking healthy while nothing on it is being read.
190
+ finish({ by: 'host', reason: `a frame subscriber threw: ${asMessage(err)}` });
191
+ return;
192
+ }
193
+ }
194
+ }
195
+ function handleFrame(frame) {
196
+ if (ending !== undefined)
197
+ return;
198
+ if ((0, webSocketFrames_js_1.isControlFrame)(frame.opcode)) {
199
+ if (frame.opcode === webSocketFrames_js_1.OPCODE.ping) {
200
+ // Answered because the protocol says so, and because a peer's liveness
201
+ // check is not the same thing as this port inventing a heartbeat.
202
+ if (socket.writable)
203
+ socket.write((0, webSocketFrames_js_1.encodeFrame)(webSocketFrames_js_1.OPCODE.pong, frame.payload));
204
+ return;
205
+ }
206
+ if (frame.opcode === webSocketFrames_js_1.OPCODE.pong)
207
+ return;
208
+ const said = (0, webSocketFrames_js_1.decodeClose)(frame.payload);
209
+ finish({ by: 'far-side', ...(said.reason !== undefined && { reason: said.reason }) }, said.code === webSocketFrames_js_1.CLOSE_CODE.noStatus ? webSocketFrames_js_1.CLOSE_CODE.normal : said.code, said.reason);
210
+ return;
211
+ }
212
+ if (frame.opcode === webSocketFrames_js_1.OPCODE.binary) {
213
+ // Refused by name rather than silently stringified: this port carries
214
+ // text, and binary is a capability nobody has shown evidence for yet.
215
+ finish({
216
+ by: 'host',
217
+ reason: 'a binary frame arrived, and this port carries text frames only — binary is a ' +
218
+ 'capability that will be minted when a consumer needs it, not guessed at now',
219
+ }, webSocketFrames_js_1.CLOSE_CODE.unsupportedData);
220
+ return;
221
+ }
222
+ if (frame.opcode === webSocketFrames_js_1.OPCODE.text) {
223
+ if (assemblingText) {
224
+ protocolFailure(new webSocketFrames_js_1.FrameProtocolError(webSocketFrames_js_1.CLOSE_CODE.protocolError, 'a new message started before the previous fragmented one finished'));
225
+ return;
226
+ }
227
+ assemblingText = true;
228
+ }
229
+ else if (!assemblingText) {
230
+ protocolFailure(new webSocketFrames_js_1.FrameProtocolError(webSocketFrames_js_1.CLOSE_CODE.protocolError, 'a continuation frame arrived with no message to continue'));
231
+ return;
232
+ }
233
+ assemblingBytes += frame.payload.length;
234
+ if (maxFrameBytes !== undefined && assemblingBytes > maxFrameBytes) {
235
+ // The ceiling is on the MESSAGE, so a peer cannot fragment its way past it.
236
+ protocolFailure(new webSocketFrames_js_1.FrameProtocolError(webSocketFrames_js_1.CLOSE_CODE.tooBig, `a fragmented message reached ${assemblingBytes} bytes, past the declared ` +
237
+ `maxFrameBytes of ${maxFrameBytes}`));
238
+ return;
239
+ }
240
+ assembling.push(frame.payload);
241
+ if (!frame.fin)
242
+ return;
243
+ const whole = assembling.length === 1 ? assembling[0] : Buffer.concat(assembling);
244
+ assembling = [];
245
+ assemblingBytes = 0;
246
+ assemblingText = false;
247
+ let text;
248
+ try {
249
+ text = (0, webSocketFrames_js_1.decodeText)(whole);
250
+ }
251
+ catch (err) {
252
+ protocolFailure(err);
253
+ return;
254
+ }
255
+ deliver(text);
256
+ }
257
+ function protocolFailure(err) {
258
+ const code = err instanceof webSocketFrames_js_1.FrameProtocolError ? err.closeCode : webSocketFrames_js_1.CLOSE_CODE.protocolError;
259
+ finish({ by: 'far-side', reason: asMessage(err) }, code, asMessage(err));
260
+ }
261
+ socket.on('data', (chunk) => {
262
+ if (ending !== undefined)
263
+ return;
264
+ let frames;
265
+ try {
266
+ frames = reader.push(chunk);
267
+ }
268
+ catch (err) {
269
+ protocolFailure(err);
270
+ return;
271
+ }
272
+ for (const frame of frames)
273
+ handleFrame(frame);
274
+ });
275
+ socket.on('error', (err) => {
276
+ finish({ by: 'transport', reason: err.message });
277
+ socket.destroy();
278
+ });
279
+ // BOTH halves, and `'end'` is the one that matters. An upgraded socket is
280
+ // half-open: when the far side vanishes, node delivers `'end'` and then waits
281
+ // for THIS side to end too — no `'error'`, and no `'close'` until we act. A
282
+ // door listening only for `'close'` would never fire onClose for a caller
283
+ // that walked away, and would hold a socket open that keeps every shutdown
284
+ // sharing this port waiting. Measured, not assumed.
285
+ socket.on('end', () => {
286
+ finish({ by: 'transport', reason: 'the connection ended without a close frame' });
287
+ });
288
+ socket.on('close', () => {
289
+ // No close frame from either side: the connection went away rather than
290
+ // ended, and those are different facts to whoever is watching.
291
+ finish({ by: 'transport', reason: 'the connection closed without a close frame' });
292
+ });
293
+ const port = {
294
+ ...(sessionId !== undefined && { sessionId }),
295
+ headers: options.headers,
296
+ send(frame) {
297
+ if (ending !== undefined)
298
+ throw new errors_js_1.ConversationClosedError(hostName, sessionId);
299
+ const bytes = Buffer.byteLength(frame, 'utf8');
300
+ if (maxFrameBytes !== undefined && bytes > maxFrameBytes) {
301
+ throw new errors_js_1.FrameTooLargeError(hostName, bytes, maxFrameBytes);
302
+ }
303
+ socket.write((0, webSocketFrames_js_1.encodeText)(frame));
304
+ },
305
+ onFrame(cb) {
306
+ frameSubscribers.add(cb);
307
+ if (pending.length > 0) {
308
+ const held = pending;
309
+ pending = [];
310
+ pendingBytes = 0;
311
+ for (const frame of held)
312
+ cb(frame);
313
+ }
314
+ return () => {
315
+ frameSubscribers.delete(cb);
316
+ };
317
+ },
318
+ onClose(cb) {
319
+ // Already over? Say so now. A subscriber that arrives late is asking the
320
+ // same question as one that arrived early, and "never" is a wrong answer.
321
+ if (ending !== undefined) {
322
+ cb(ending);
323
+ return () => undefined;
324
+ }
325
+ closeSubscribers.add(cb);
326
+ return () => {
327
+ closeSubscribers.delete(cb);
328
+ };
329
+ },
330
+ close(reason) {
331
+ finish({ by: 'host', ...(reason !== undefined && { reason }) }, webSocketFrames_js_1.CLOSE_CODE.normal, reason);
332
+ },
333
+ };
334
+ return {
335
+ port,
336
+ ended,
337
+ end(reason) {
338
+ finish({ by: 'host', reason }, webSocketFrames_js_1.CLOSE_CODE.goingAway, reason);
339
+ },
340
+ };
341
+ }
342
+ function asMessage(err) {
343
+ return err instanceof Error ? err.message : String(err);
344
+ }
345
+ //# sourceMappingURL=webSocketConversation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webSocketConversation.js","sourceRoot":"","sources":["../../src/hosting/webSocketConversation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,297 @@
1
+ "use strict";
2
+ /**
3
+ * hosting/webSocketFrames — the frame codec the conversation door speaks, and
4
+ * nothing else.
5
+ *
6
+ * Pure bytes in, pure bytes out: no socket, no session, no handler, no port
7
+ * vocabulary. That is what makes it testable the only way protocol code is
8
+ * worth testing — against the byte sequences the specification itself
9
+ * publishes, rather than against a client we also wrote.
10
+ *
11
+ * ── Why this file exists instead of a dependency ─────────────────────────────
12
+ * The alternative was an optional peer dependency carrying a complete
13
+ * implementation. It was rejected for a reason that is about honesty rather
14
+ * than about lines of code: `capabilities` is declared at CONSTRUCTION and is
15
+ * static thereafter, so a host whose conversation door only works when an
16
+ * optional package happens to be installed can only either claim
17
+ * `'conversation'` and then refuse to do it — the library declaring a promise
18
+ * it cannot keep, which is the one thing the capability union forbids — or
19
+ * probe `node_modules` and let feature detection depend on install state, so a
20
+ * deployment that forgot the dependency silently gets a host that quietly does
21
+ * not do conversations. The shipped adapter honours what it declares, always,
22
+ * with nothing to install. That was worth these bytes.
23
+ *
24
+ * ── What is implemented, exactly ─────────────────────────────────────────────
25
+ * The server half of RFC 6455 for a text channel: the handshake, text frames,
26
+ * continuation frames, ping/pong, and close. Payloads are unmasked on the way
27
+ * in (client frames MUST be masked) and written unmasked on the way out (server
28
+ * frames MUST NOT be), which is the asymmetry the RFC requires.
29
+ *
30
+ * ── What is NOT, and how far the verification goes ───────────────────────────
31
+ * No extensions and no compression: `permessage-deflate` is negotiated, and
32
+ * this door never negotiates it, so the RSV bits must always be zero and a peer
33
+ * that sets one is refused. No binary frames — the port carries text, and
34
+ * binary is a capability nobody has minted evidence for. No client role.
35
+ *
36
+ * Verification is: the byte vectors published in RFC 6455 §5.7, which were
37
+ * authored by the specification and not by this repository, plus a live
38
+ * exchange against the platform's own WebSocket client where the runtime has
39
+ * one, plus the conversation conformance suite over a real socket. It is **not**
40
+ * run against the Autobahn test suite, and this file does not claim more
41
+ * coverage than the tests beside it actually execute.
42
+ *
43
+ * Pattern: pure codec. Role: innermost ring — imports nothing from this package.
44
+ */
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.decodeText = exports.FrameReader = exports.isControlFrame = exports.decodeClose = exports.encodeClose = exports.encodeText = exports.encodeFrame = exports.handshakeResponse = exports.acceptKey = exports.FrameProtocolError = exports.CLOSE_CODE = exports.OPCODE = void 0;
47
+ const node_crypto_1 = require("node:crypto");
48
+ /** The frame kinds this door speaks. Numbers, because the wire uses numbers. */
49
+ exports.OPCODE = {
50
+ continuation: 0x0,
51
+ text: 0x1,
52
+ binary: 0x2,
53
+ close: 0x8,
54
+ ping: 0x9,
55
+ pong: 0xa,
56
+ };
57
+ /**
58
+ * Close codes this door produces. The RFC's own numbers, named — a bare `1009`
59
+ * in a branch is a fact nobody can check without opening the specification.
60
+ */
61
+ exports.CLOSE_CODE = {
62
+ /** Ordinary end: the work is done. */
63
+ normal: 1000,
64
+ /** The host is shutting down. */
65
+ goingAway: 1001,
66
+ /** The peer broke the protocol. */
67
+ protocolError: 1002,
68
+ /** Data this endpoint cannot accept — a binary frame, here. */
69
+ unsupportedData: 1003,
70
+ /** Reserved: never sent on the wire, used only as "we were not told". */
71
+ noStatus: 1005,
72
+ /** Text that is not valid UTF-8. */
73
+ invalidPayload: 1007,
74
+ /** Past a declared ceiling. */
75
+ tooBig: 1009,
76
+ };
77
+ /**
78
+ * A peer that broke the protocol, carrying the close code the RFC names for
79
+ * that breakage — so the caller does not have to re-derive which number means
80
+ * what at the point it has to write one.
81
+ */
82
+ class FrameProtocolError extends Error {
83
+ closeCode;
84
+ constructor(closeCode, message) {
85
+ super(message);
86
+ this.name = 'FrameProtocolError';
87
+ this.closeCode = closeCode;
88
+ }
89
+ }
90
+ exports.FrameProtocolError = FrameProtocolError;
91
+ /** The magic string RFC 6455 §1.3 appends before hashing. Not ours to change. */
92
+ const HANDSHAKE_GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
93
+ /**
94
+ * The `Sec-WebSocket-Accept` value for a client's `Sec-WebSocket-Key`:
95
+ * base64(sha1(key + GUID)). Deterministic, and pinned against the RFC's own
96
+ * worked example.
97
+ */
98
+ function acceptKey(clientKey) {
99
+ return (0, node_crypto_1.createHash)('sha1')
100
+ .update(clientKey + HANDSHAKE_GUID)
101
+ .digest('base64');
102
+ }
103
+ exports.acceptKey = acceptKey;
104
+ /**
105
+ * The 101 response, as bytes.
106
+ *
107
+ * `protocol` is echoed only when the caller selected one. Echoing a subprotocol
108
+ * the client did not offer would make the client fail the connection, so
109
+ * choosing one is the wire's job (it read the offer) and not this function's.
110
+ */
111
+ function handshakeResponse(clientKey, protocol) {
112
+ const lines = [
113
+ 'HTTP/1.1 101 Switching Protocols',
114
+ 'Upgrade: websocket',
115
+ 'Connection: Upgrade',
116
+ `Sec-WebSocket-Accept: ${acceptKey(clientKey)}`,
117
+ ];
118
+ if (protocol !== undefined && protocol.length > 0) {
119
+ lines.push(`Sec-WebSocket-Protocol: ${protocol}`);
120
+ }
121
+ return Buffer.from(`${lines.join('\r\n')}\r\n\r\n`, 'utf8');
122
+ }
123
+ exports.handshakeResponse = handshakeResponse;
124
+ /**
125
+ * One frame, ready to write. **Never masked**: a server that masks its frames
126
+ * is a server the client closes on, per RFC 6455 §5.1.
127
+ */
128
+ function encodeFrame(opcode, payload, fin = true) {
129
+ const length = payload.length;
130
+ let header;
131
+ if (length < 126) {
132
+ header = Buffer.alloc(2);
133
+ header[1] = length;
134
+ }
135
+ else if (length < 0x10000) {
136
+ header = Buffer.alloc(4);
137
+ header[1] = 126;
138
+ header.writeUInt16BE(length, 2);
139
+ }
140
+ else {
141
+ header = Buffer.alloc(10);
142
+ header[1] = 127;
143
+ // Two 32-bit halves rather than a BigInt write: the high half is always
144
+ // zero for anything this door will ever send, and saying so in bytes is
145
+ // clearer than converting through a type nothing else here uses.
146
+ header.writeUInt32BE(0, 2);
147
+ header.writeUInt32BE(length, 6);
148
+ }
149
+ header[0] = (fin ? 0x80 : 0x00) | opcode;
150
+ return Buffer.concat([header, payload]);
151
+ }
152
+ exports.encodeFrame = encodeFrame;
153
+ /** A text frame carrying one whole message. */
154
+ function encodeText(text) {
155
+ return encodeFrame(exports.OPCODE.text, Buffer.from(text, 'utf8'));
156
+ }
157
+ exports.encodeText = encodeText;
158
+ /**
159
+ * A close frame carrying a code and, when there is one, a reason.
160
+ *
161
+ * The reason is truncated to 123 bytes because a control frame's payload may
162
+ * not exceed 125 and the code takes two of them. Truncating a REASON is safe in
163
+ * a way truncating a message never is: it is diagnostic prose about an ending
164
+ * that has already been decided.
165
+ */
166
+ function encodeClose(code, reason) {
167
+ const text = reason === undefined ? Buffer.alloc(0) : Buffer.from(reason, 'utf8');
168
+ const trimmed = text.length > 123 ? text.subarray(0, 123) : text;
169
+ const payload = Buffer.alloc(2 + trimmed.length);
170
+ payload.writeUInt16BE(code, 0);
171
+ trimmed.copy(payload, 2);
172
+ return encodeFrame(exports.OPCODE.close, payload);
173
+ }
174
+ exports.encodeClose = encodeClose;
175
+ /** Read a close frame's payload. An empty payload is "no status", never an error. */
176
+ function decodeClose(payload) {
177
+ if (payload.length < 2)
178
+ return { code: exports.CLOSE_CODE.noStatus };
179
+ const code = payload.readUInt16BE(0);
180
+ const reason = payload.length > 2 ? payload.subarray(2).toString('utf8') : undefined;
181
+ return { code, ...(reason !== undefined && reason.length > 0 && { reason }) };
182
+ }
183
+ exports.decodeClose = decodeClose;
184
+ /** Is this one of the three control opcodes? */
185
+ function isControlFrame(opcode) {
186
+ return (opcode & 0x8) !== 0;
187
+ }
188
+ exports.isControlFrame = isControlFrame;
189
+ /**
190
+ * Turns a stream of TCP chunks into whole frames.
191
+ *
192
+ * Stateful by necessity — a frame arrives in as many pieces as the network
193
+ * feels like, and two frames arrive in one piece just as often. Every branch
194
+ * that gives up does so by throwing a {@link FrameProtocolError} carrying the
195
+ * code to close with, so the layer above never has to invent one.
196
+ */
197
+ class FrameReader {
198
+ buffered = Buffer.alloc(0);
199
+ maxFrameBytes;
200
+ /**
201
+ * @param maxFrameBytes The declared ceiling, when there is one. Checked
202
+ * against the length in the HEADER — before the payload is buffered, so an
203
+ * announced 4GB frame costs nothing to refuse.
204
+ */
205
+ constructor(maxFrameBytes) {
206
+ this.maxFrameBytes = maxFrameBytes;
207
+ }
208
+ /** Feed one chunk; get back every frame that completed. */
209
+ push(chunk) {
210
+ this.buffered = this.buffered.length === 0 ? chunk : Buffer.concat([this.buffered, chunk]);
211
+ const frames = [];
212
+ for (;;) {
213
+ const frame = this.readOne();
214
+ if (frame === undefined)
215
+ return frames;
216
+ frames.push(frame);
217
+ }
218
+ }
219
+ /** One frame, or `undefined` when the bytes for a whole one are not here yet. */
220
+ readOne() {
221
+ const buffer = this.buffered;
222
+ if (buffer.length < 2)
223
+ return undefined;
224
+ const first = buffer[0];
225
+ const second = buffer[1];
226
+ const fin = (first & 0x80) !== 0;
227
+ const reserved = first & 0x70;
228
+ const opcode = first & 0x0f;
229
+ const masked = (second & 0x80) !== 0;
230
+ let length = second & 0x7f;
231
+ let offset = 2;
232
+ if (reserved !== 0) {
233
+ // RSV bits mean an extension, and this door negotiates none — so a peer
234
+ // that set one is describing a frame we would have to guess at.
235
+ throw new FrameProtocolError(exports.CLOSE_CODE.protocolError, 'a reserved bit is set, and this door negotiated no extension that would define it');
236
+ }
237
+ if (!masked) {
238
+ // RFC 6455 §5.1: every client frame is masked, and a server that accepts
239
+ // an unmasked one is a server that can be fed frames by a naive proxy.
240
+ throw new FrameProtocolError(exports.CLOSE_CODE.protocolError, 'a client frame arrived unmasked, which the protocol does not allow');
241
+ }
242
+ if (isControlFrame(opcode) && (length > 125 || !fin)) {
243
+ throw new FrameProtocolError(exports.CLOSE_CODE.protocolError, 'a control frame was fragmented or longer than 125 bytes');
244
+ }
245
+ if (length === 126) {
246
+ if (buffer.length < offset + 2)
247
+ return undefined;
248
+ length = buffer.readUInt16BE(offset);
249
+ offset += 2;
250
+ }
251
+ else if (length === 127) {
252
+ if (buffer.length < offset + 8)
253
+ return undefined;
254
+ const high = buffer.readUInt32BE(offset);
255
+ const low = buffer.readUInt32BE(offset + 4);
256
+ if (high !== 0) {
257
+ throw new FrameProtocolError(exports.CLOSE_CODE.tooBig, 'a frame announced a length above 4 GiB, which no ceiling here permits');
258
+ }
259
+ length = low;
260
+ offset += 8;
261
+ }
262
+ if (this.maxFrameBytes !== undefined && length > this.maxFrameBytes) {
263
+ // Refused on the ANNOUNCED length, before a byte of payload is kept: a
264
+ // ceiling that only notices after buffering is not a ceiling.
265
+ throw new FrameProtocolError(exports.CLOSE_CODE.tooBig, `a frame announced ${length} bytes, past the declared maxFrameBytes of ${this.maxFrameBytes}`);
266
+ }
267
+ const maskOffset = offset;
268
+ offset += 4;
269
+ if (buffer.length < offset + length)
270
+ return undefined;
271
+ const payload = Buffer.allocUnsafe(length);
272
+ buffer.copy(payload, 0, offset, offset + length);
273
+ for (let i = 0; i < length; i++)
274
+ payload[i] ^= buffer[maskOffset + (i & 3)];
275
+ this.buffered = buffer.subarray(offset + length);
276
+ return { fin, opcode, payload };
277
+ }
278
+ }
279
+ exports.FrameReader = FrameReader;
280
+ /**
281
+ * Decode a whole message's bytes as UTF-8, refusing anything that is not.
282
+ *
283
+ * Strict on purpose (RFC 6455 §8.1): a text frame that is not valid UTF-8 is a
284
+ * protocol violation, and the lenient alternative silently replaces the bad
285
+ * bytes with `U+FFFD` — handing the consumer a string the far side never sent
286
+ * and no way to know it happened.
287
+ */
288
+ function decodeText(payload) {
289
+ try {
290
+ return new TextDecoder('utf-8', { fatal: true }).decode(payload);
291
+ }
292
+ catch {
293
+ throw new FrameProtocolError(exports.CLOSE_CODE.invalidPayload, 'a text frame carried bytes that are not valid UTF-8');
294
+ }
295
+ }
296
+ exports.decodeText = decodeText;
297
+ //# sourceMappingURL=webSocketFrames.js.map