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