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.
- package/dist/adapters/hosting/agentcore.js +112 -1
- package/dist/adapters/hosting/agentcore.js.map +1 -1
- package/dist/esm/adapters/hosting/agentcore.d.ts +50 -1
- package/dist/esm/adapters/hosting/agentcore.js +110 -0
- package/dist/esm/adapters/hosting/agentcore.js.map +1 -1
- package/dist/esm/hosting/errors.d.ts +44 -2
- package/dist/esm/hosting/errors.js +63 -0
- package/dist/esm/hosting/errors.js.map +1 -1
- package/dist/esm/hosting/headers.d.ts +16 -0
- package/dist/esm/hosting/headers.js +24 -0
- package/dist/esm/hosting/headers.js.map +1 -0
- package/dist/esm/hosting/httpHost.d.ts +70 -4
- package/dist/esm/hosting/httpHost.js +211 -47
- package/dist/esm/hosting/httpHost.js.map +1 -1
- package/dist/esm/hosting/index.d.ts +23 -8
- package/dist/esm/hosting/index.js +21 -6
- package/dist/esm/hosting/index.js.map +1 -1
- package/dist/esm/hosting/nodeHost.d.ts +23 -0
- package/dist/esm/hosting/nodeHost.js +21 -1
- package/dist/esm/hosting/nodeHost.js.map +1 -1
- package/dist/esm/hosting/types.d.ts +212 -6
- package/dist/esm/hosting/types.js +7 -5
- package/dist/esm/hosting/types.js.map +1 -1
- package/dist/esm/hosting/webSocketConversation.d.ts +101 -0
- package/dist/esm/hosting/webSocketConversation.js +341 -0
- package/dist/esm/hosting/webSocketConversation.js.map +1 -0
- package/dist/esm/hosting/webSocketFrames.d.ts +164 -0
- package/dist/esm/hosting/webSocketFrames.js +284 -0
- package/dist/esm/hosting/webSocketFrames.js.map +1 -0
- package/dist/esm/hosting-providers.d.ts +7 -2
- package/dist/esm/hosting-providers.js +7 -2
- package/dist/esm/hosting-providers.js.map +1 -1
- package/dist/hosting/errors.js +66 -1
- package/dist/hosting/errors.js.map +1 -1
- package/dist/hosting/headers.js +28 -0
- package/dist/hosting/headers.js.map +1 -0
- package/dist/hosting/httpHost.js +211 -47
- package/dist/hosting/httpHost.js.map +1 -1
- package/dist/hosting/index.js +23 -6
- package/dist/hosting/index.js.map +1 -1
- package/dist/hosting/nodeHost.js +20 -0
- package/dist/hosting/nodeHost.js.map +1 -1
- package/dist/hosting/types.js +7 -5
- package/dist/hosting/types.js.map +1 -1
- package/dist/hosting/webSocketConversation.js +345 -0
- package/dist/hosting/webSocketConversation.js.map +1 -0
- package/dist/hosting/webSocketFrames.js +297 -0
- package/dist/hosting/webSocketFrames.js.map +1 -0
- package/dist/hosting-providers.js +8 -2
- package/dist/hosting-providers.js.map +1 -1
- package/dist/types/adapters/hosting/agentcore.d.ts +50 -1
- package/dist/types/adapters/hosting/agentcore.d.ts.map +1 -1
- package/dist/types/hosting/errors.d.ts +44 -2
- package/dist/types/hosting/errors.d.ts.map +1 -1
- package/dist/types/hosting/headers.d.ts +17 -0
- package/dist/types/hosting/headers.d.ts.map +1 -0
- package/dist/types/hosting/httpHost.d.ts +70 -4
- package/dist/types/hosting/httpHost.d.ts.map +1 -1
- package/dist/types/hosting/index.d.ts +23 -8
- package/dist/types/hosting/index.d.ts.map +1 -1
- package/dist/types/hosting/nodeHost.d.ts +23 -0
- package/dist/types/hosting/nodeHost.d.ts.map +1 -1
- package/dist/types/hosting/types.d.ts +212 -6
- package/dist/types/hosting/types.d.ts.map +1 -1
- package/dist/types/hosting/webSocketConversation.d.ts +102 -0
- package/dist/types/hosting/webSocketConversation.d.ts.map +1 -0
- package/dist/types/hosting/webSocketFrames.d.ts +165 -0
- package/dist/types/hosting/webSocketFrames.d.ts.map +1 -0
- package/dist/types/hosting-providers.d.ts +7 -2
- package/dist/types/hosting-providers.d.ts.map +1 -1
- 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
|