@rivetkit/engine-runner 2.0.33 → 2.0.34-rc.2
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/mod.cjs +105 -347
- package/dist/mod.cjs.map +1 -1
- package/dist/mod.d.cts +4 -40
- package/dist/mod.d.ts +4 -40
- package/dist/mod.js +101 -343
- package/dist/mod.js.map +1 -1
- package/package.json +3 -2
- package/src/mod.ts +39 -16
- package/src/tunnel.ts +4 -4
- package/src/websocket-tunnel-adapter.ts +69 -435
|
@@ -1,56 +1,26 @@
|
|
|
1
|
-
// WebSocket-like adapter for tunneled connections
|
|
2
|
-
// Implements a subset of the WebSocket interface for compatibility with runner code
|
|
3
|
-
|
|
4
1
|
import type { Logger } from "pino";
|
|
5
|
-
import {
|
|
2
|
+
import { VirtualWebSocket, type UniversalWebSocket, type RivetMessageEvent } from "@rivetkit/virtual-websocket";
|
|
6
3
|
import type { Tunnel } from "./tunnel";
|
|
7
4
|
import { wrappingAddU16, wrappingLteU16, wrappingSubU16 } from "./utils";
|
|
8
5
|
|
|
9
6
|
export const HIBERNATABLE_SYMBOL = Symbol("hibernatable");
|
|
10
7
|
|
|
11
8
|
export class WebSocketTunnelAdapter {
|
|
12
|
-
|
|
13
|
-
#readyState: number = 0; // CONNECTING
|
|
14
|
-
#eventListeners: Map<string, Set<(event: any) => void>> = new Map();
|
|
15
|
-
#onopen: ((this: any, ev: any) => any) | null = null;
|
|
16
|
-
#onclose: ((this: any, ev: any) => any) | null = null;
|
|
17
|
-
#onerror: ((this: any, ev: any) => any) | null = null;
|
|
18
|
-
#onmessage: ((this: any, ev: any) => any) | null = null;
|
|
19
|
-
#bufferedAmount = 0;
|
|
9
|
+
#readyState: 0 | 1 | 2 | 3 = 0;
|
|
20
10
|
#binaryType: "nodebuffer" | "arraybuffer" | "blob" = "nodebuffer";
|
|
21
|
-
#
|
|
22
|
-
#protocol = "";
|
|
23
|
-
#url = "";
|
|
24
|
-
|
|
25
|
-
// mARK: - Internal State
|
|
11
|
+
#ws: VirtualWebSocket;
|
|
26
12
|
#tunnel: Tunnel;
|
|
27
13
|
#actorId: string;
|
|
28
14
|
#requestId: string;
|
|
29
15
|
#hibernatable: boolean;
|
|
30
16
|
#serverMessageIndex: number;
|
|
17
|
+
#sendCallback: (data: ArrayBuffer | string, isBinary: boolean) => void;
|
|
18
|
+
#closeCallback: (code?: number, reason?: string) => void;
|
|
31
19
|
|
|
32
20
|
get [HIBERNATABLE_SYMBOL](): boolean {
|
|
33
21
|
return this.#hibernatable;
|
|
34
22
|
}
|
|
35
23
|
|
|
36
|
-
/**
|
|
37
|
-
* Called when sending a message from this WebSocket.
|
|
38
|
-
*
|
|
39
|
-
* Used to send a tunnel message from Tunnel.
|
|
40
|
-
*/
|
|
41
|
-
#sendCallback: (data: ArrayBuffer | string, isBinary: boolean) => void;
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Called when closing this WebSocket.
|
|
45
|
-
*
|
|
46
|
-
* Used to send a tunnel message from Tunnel
|
|
47
|
-
*/
|
|
48
|
-
#closeCallback: (
|
|
49
|
-
code?: number,
|
|
50
|
-
reason?: string,
|
|
51
|
-
hibernate?: boolean,
|
|
52
|
-
) => void;
|
|
53
|
-
|
|
54
24
|
get #log(): Logger | undefined {
|
|
55
25
|
return this.#tunnel.log;
|
|
56
26
|
}
|
|
@@ -62,7 +32,6 @@ export class WebSocketTunnelAdapter {
|
|
|
62
32
|
serverMessageIndex: number,
|
|
63
33
|
hibernatable: boolean,
|
|
64
34
|
isRestoringHibernatable: boolean,
|
|
65
|
-
/** @experimental */
|
|
66
35
|
public readonly request: Request,
|
|
67
36
|
sendCallback: (data: ArrayBuffer | string, isBinary: boolean) => void,
|
|
68
37
|
closeCallback: (code?: number, reason?: string) => void,
|
|
@@ -75,40 +44,58 @@ export class WebSocketTunnelAdapter {
|
|
|
75
44
|
this.#sendCallback = sendCallback;
|
|
76
45
|
this.#closeCallback = closeCallback;
|
|
77
46
|
|
|
78
|
-
|
|
47
|
+
this.#ws = new VirtualWebSocket({
|
|
48
|
+
getReadyState: () => this.#readyState,
|
|
49
|
+
onSend: (data) => this.#handleSend(data),
|
|
50
|
+
onClose: (code, reason) => this.#close(code, reason, true),
|
|
51
|
+
onTerminate: () => this.#terminate(),
|
|
52
|
+
});
|
|
53
|
+
|
|
79
54
|
if (isRestoringHibernatable) {
|
|
80
55
|
this.#log?.debug({
|
|
81
56
|
msg: "setting WebSocket to OPEN state for restored connection",
|
|
82
57
|
actorId: this.#actorId,
|
|
83
58
|
requestId: this.#requestId,
|
|
84
|
-
hibernatable: this.#hibernatable,
|
|
85
59
|
});
|
|
86
|
-
this.#readyState = 1;
|
|
60
|
+
this.#readyState = 1;
|
|
87
61
|
}
|
|
88
62
|
}
|
|
89
63
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
return this.#bufferedAmount;
|
|
64
|
+
get websocket(): UniversalWebSocket {
|
|
65
|
+
return this.#ws;
|
|
93
66
|
}
|
|
94
67
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
68
|
+
#handleSend(data: string | ArrayBufferLike | Blob | ArrayBufferView): void {
|
|
69
|
+
let isBinary = false;
|
|
70
|
+
let messageData: string | ArrayBuffer;
|
|
100
71
|
|
|
101
|
-
|
|
72
|
+
if (typeof data === "string") {
|
|
73
|
+
messageData = data;
|
|
74
|
+
} else if (data instanceof ArrayBuffer) {
|
|
75
|
+
isBinary = true;
|
|
76
|
+
messageData = data;
|
|
77
|
+
} else if (ArrayBuffer.isView(data)) {
|
|
78
|
+
isBinary = true;
|
|
79
|
+
const view = data;
|
|
80
|
+
const buffer = view.buffer instanceof SharedArrayBuffer
|
|
81
|
+
? new Uint8Array(view.buffer, view.byteOffset, view.byteLength).slice().buffer
|
|
82
|
+
: view.buffer.slice(view.byteOffset, view.byteOffset + view.byteLength);
|
|
83
|
+
messageData = buffer as ArrayBuffer;
|
|
84
|
+
} else {
|
|
85
|
+
throw new Error("Unsupported data type");
|
|
86
|
+
}
|
|
102
87
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
rivetRequestId: requestId,
|
|
106
|
-
target: this,
|
|
107
|
-
};
|
|
88
|
+
this.#sendCallback(messageData, isBinary);
|
|
89
|
+
}
|
|
108
90
|
|
|
109
|
-
|
|
91
|
+
// Called by Tunnel when WebSocket is opened
|
|
92
|
+
_handleOpen(requestId: ArrayBuffer): void {
|
|
93
|
+
if (this.#readyState !== 0) return;
|
|
94
|
+
this.#readyState = 1;
|
|
95
|
+
this.#ws.dispatchEvent({ type: "open", rivetRequestId: requestId, target: this.#ws });
|
|
110
96
|
}
|
|
111
97
|
|
|
98
|
+
// Called by Tunnel when message is received
|
|
112
99
|
_handleMessage(
|
|
113
100
|
requestId: ArrayBuffer,
|
|
114
101
|
data: string | Uint8Array,
|
|
@@ -121,45 +108,28 @@ export class WebSocketTunnelAdapter {
|
|
|
121
108
|
requestId: this.#requestId,
|
|
122
109
|
actorId: this.#actorId,
|
|
123
110
|
currentReadyState: this.#readyState,
|
|
124
|
-
expectedReadyState: 1,
|
|
125
|
-
serverMessageIndex: serverMessageIndex,
|
|
126
|
-
hibernatable: this.#hibernatable,
|
|
127
111
|
});
|
|
128
112
|
return true;
|
|
129
113
|
}
|
|
130
114
|
|
|
131
|
-
// Validate message index
|
|
115
|
+
// Validate message index for hibernatable websockets
|
|
132
116
|
if (this.#hibernatable) {
|
|
133
117
|
const previousIndex = this.#serverMessageIndex;
|
|
134
118
|
|
|
135
|
-
// Ignore duplicate old messages
|
|
136
|
-
//
|
|
137
|
-
// This should only happen if something goes wrong
|
|
138
|
-
// between persisting the previous index and acking the
|
|
139
|
-
// message index to the gateway. If the ack is never
|
|
140
|
-
// received by the gateway (due to a crash or network
|
|
141
|
-
// issue), the gateway will resend all messages from
|
|
142
|
-
// the last ack on reconnect.
|
|
143
119
|
if (wrappingLteU16(serverMessageIndex, previousIndex)) {
|
|
144
120
|
this.#log?.info({
|
|
145
|
-
msg: "received duplicate hibernating websocket message
|
|
121
|
+
msg: "received duplicate hibernating websocket message",
|
|
146
122
|
requestId,
|
|
147
123
|
actorId: this.#actorId,
|
|
148
124
|
previousIndex,
|
|
149
|
-
expectedIndex: wrappingAddU16(previousIndex, 1),
|
|
150
125
|
receivedIndex: serverMessageIndex,
|
|
151
126
|
});
|
|
152
|
-
|
|
153
127
|
return true;
|
|
154
128
|
}
|
|
155
129
|
|
|
156
|
-
// Close message if skipped message in sequence
|
|
157
|
-
//
|
|
158
|
-
// There is no scenario where this should ever happen
|
|
159
130
|
const expectedIndex = wrappingAddU16(previousIndex, 1);
|
|
160
131
|
if (serverMessageIndex !== expectedIndex) {
|
|
161
132
|
const closeReason = "ws.message_index_skip";
|
|
162
|
-
|
|
163
133
|
this.#log?.warn({
|
|
164
134
|
msg: "hibernatable websocket message index out of sequence, closing connection",
|
|
165
135
|
requestId,
|
|
@@ -168,400 +138,64 @@ export class WebSocketTunnelAdapter {
|
|
|
168
138
|
expectedIndex,
|
|
169
139
|
receivedIndex: serverMessageIndex,
|
|
170
140
|
closeReason,
|
|
171
|
-
gap: wrappingSubU16(
|
|
172
|
-
wrappingSubU16(serverMessageIndex, previousIndex),
|
|
173
|
-
1,
|
|
174
|
-
),
|
|
141
|
+
gap: wrappingSubU16(wrappingSubU16(serverMessageIndex, previousIndex), 1),
|
|
175
142
|
});
|
|
176
|
-
|
|
177
|
-
// Close the WebSocket and skip processing
|
|
178
|
-
this.close(1008, closeReason);
|
|
179
|
-
|
|
143
|
+
this.#close(1008, closeReason, true);
|
|
180
144
|
return true;
|
|
181
145
|
}
|
|
182
146
|
|
|
183
|
-
// Update to the next index
|
|
184
147
|
this.#serverMessageIndex = serverMessageIndex;
|
|
185
148
|
}
|
|
186
149
|
|
|
187
|
-
//
|
|
188
|
-
let messageData: any;
|
|
189
|
-
if (isBinary) {
|
|
190
|
-
// Handle binary data based on binaryType
|
|
150
|
+
// Convert data based on binaryType
|
|
151
|
+
let messageData: any = data;
|
|
152
|
+
if (isBinary && data instanceof Uint8Array) {
|
|
191
153
|
if (this.#binaryType === "nodebuffer") {
|
|
192
|
-
|
|
193
|
-
messageData = Buffer.from(data as Uint8Array);
|
|
154
|
+
messageData = Buffer.from(data);
|
|
194
155
|
} else if (this.#binaryType === "arraybuffer") {
|
|
195
|
-
|
|
196
|
-
if (data instanceof Uint8Array) {
|
|
197
|
-
messageData = data.buffer.slice(
|
|
198
|
-
data.byteOffset,
|
|
199
|
-
data.byteOffset + data.byteLength,
|
|
200
|
-
);
|
|
201
|
-
} else {
|
|
202
|
-
messageData = data;
|
|
203
|
-
}
|
|
204
|
-
} else {
|
|
205
|
-
// Blob type - not commonly used in Node.js
|
|
206
|
-
throw new Error(
|
|
207
|
-
"Blob binaryType not supported in tunnel adapter",
|
|
208
|
-
);
|
|
156
|
+
messageData = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
209
157
|
}
|
|
210
|
-
} else {
|
|
211
|
-
messageData = data;
|
|
212
158
|
}
|
|
213
159
|
|
|
214
|
-
|
|
160
|
+
this.#ws.dispatchEvent({
|
|
215
161
|
type: "message",
|
|
216
162
|
data: messageData,
|
|
217
163
|
rivetRequestId: requestId,
|
|
218
164
|
rivetMessageIndex: serverMessageIndex,
|
|
219
|
-
target: this,
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
this.#fireEvent("message", event);
|
|
165
|
+
target: this.#ws,
|
|
166
|
+
} as RivetMessageEvent);
|
|
223
167
|
|
|
224
168
|
return false;
|
|
225
169
|
}
|
|
226
170
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
code
|
|
230
|
-
reason?: string,
|
|
231
|
-
): void {
|
|
232
|
-
this.#closeInner(code, reason, true);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
_handleError(error: Error): void {
|
|
236
|
-
const event = {
|
|
237
|
-
type: "error",
|
|
238
|
-
target: this,
|
|
239
|
-
error,
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
this.#fireEvent("error", event);
|
|
171
|
+
// Called by Tunnel when close is received
|
|
172
|
+
_handleClose(_requestId: ArrayBuffer, code?: number, reason?: string): void {
|
|
173
|
+
this.#close(code, reason, true);
|
|
243
174
|
}
|
|
244
175
|
|
|
176
|
+
// Close without sending close message to tunnel
|
|
245
177
|
_closeWithoutCallback(code?: number, reason?: string): void {
|
|
246
|
-
this.#
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
#fireEvent(type: string, event: any): void {
|
|
250
|
-
// Call all registered event listeners
|
|
251
|
-
const listeners = this.#eventListeners.get(type);
|
|
252
|
-
|
|
253
|
-
if (listeners && listeners.size > 0) {
|
|
254
|
-
for (const listener of listeners) {
|
|
255
|
-
try {
|
|
256
|
-
listener.call(this, event);
|
|
257
|
-
} catch (error) {
|
|
258
|
-
logger()?.error({
|
|
259
|
-
msg: "error in websocket event listener",
|
|
260
|
-
error,
|
|
261
|
-
type,
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// Call the onX property if set
|
|
268
|
-
switch (type) {
|
|
269
|
-
case "open":
|
|
270
|
-
if (this.#onopen) {
|
|
271
|
-
try {
|
|
272
|
-
this.#onopen.call(this, event);
|
|
273
|
-
} catch (error) {
|
|
274
|
-
logger()?.error({
|
|
275
|
-
msg: "error in onopen handler",
|
|
276
|
-
error,
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
break;
|
|
281
|
-
case "close":
|
|
282
|
-
if (this.#onclose) {
|
|
283
|
-
try {
|
|
284
|
-
this.#onclose.call(this, event);
|
|
285
|
-
} catch (error) {
|
|
286
|
-
logger()?.error({
|
|
287
|
-
msg: "error in onclose handler",
|
|
288
|
-
error,
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
break;
|
|
293
|
-
case "error":
|
|
294
|
-
if (this.#onerror) {
|
|
295
|
-
try {
|
|
296
|
-
this.#onerror.call(this, event);
|
|
297
|
-
} catch (error) {
|
|
298
|
-
logger()?.error({
|
|
299
|
-
msg: "error in onerror handler",
|
|
300
|
-
error,
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
break;
|
|
305
|
-
case "message":
|
|
306
|
-
if (this.#onmessage) {
|
|
307
|
-
try {
|
|
308
|
-
this.#onmessage.call(this, event);
|
|
309
|
-
} catch (error) {
|
|
310
|
-
logger()?.error({
|
|
311
|
-
msg: "error in onmessage handler",
|
|
312
|
-
error,
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
break;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
#closeInner(
|
|
321
|
-
code: number | undefined,
|
|
322
|
-
reason: string | undefined,
|
|
323
|
-
callback: boolean,
|
|
324
|
-
): void {
|
|
325
|
-
if (
|
|
326
|
-
this.#readyState === 2 || // CLOSING
|
|
327
|
-
this.#readyState === 3 // CLOSED
|
|
328
|
-
) {
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
this.#readyState = 2; // CLOSING
|
|
333
|
-
|
|
334
|
-
// Send close through tunnel
|
|
335
|
-
if (callback) {
|
|
336
|
-
this.#closeCallback(code, reason);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// Update state and fire event
|
|
340
|
-
this.#readyState = 3; // CLOSED
|
|
341
|
-
|
|
342
|
-
const closeEvent = {
|
|
343
|
-
wasClean: true,
|
|
344
|
-
code: code || 1000,
|
|
345
|
-
reason: reason || "",
|
|
346
|
-
type: "close",
|
|
347
|
-
target: this,
|
|
348
|
-
};
|
|
349
|
-
|
|
350
|
-
this.#fireEvent("close", closeEvent);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// MARK: - WebSocket Compatible API
|
|
354
|
-
get readyState(): number {
|
|
355
|
-
return this.#readyState;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
get binaryType(): string {
|
|
359
|
-
return this.#binaryType;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
set binaryType(value: string) {
|
|
363
|
-
if (
|
|
364
|
-
value === "nodebuffer" ||
|
|
365
|
-
value === "arraybuffer" ||
|
|
366
|
-
value === "blob"
|
|
367
|
-
) {
|
|
368
|
-
this.#binaryType = value;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
get extensions(): string {
|
|
373
|
-
return this.#extensions;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
get protocol(): string {
|
|
377
|
-
return this.#protocol;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
get url(): string {
|
|
381
|
-
return this.#url;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
get onopen(): ((this: any, ev: any) => any) | null {
|
|
385
|
-
return this.#onopen;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
set onopen(value: ((this: any, ev: any) => any) | null) {
|
|
389
|
-
this.#onopen = value;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
get onclose(): ((this: any, ev: any) => any) | null {
|
|
393
|
-
return this.#onclose;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
set onclose(value: ((this: any, ev: any) => any) | null) {
|
|
397
|
-
this.#onclose = value;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
get onerror(): ((this: any, ev: any) => any) | null {
|
|
401
|
-
return this.#onerror;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
set onerror(value: ((this: any, ev: any) => any) | null) {
|
|
405
|
-
this.#onerror = value;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
get onmessage(): ((this: any, ev: any) => any) | null {
|
|
409
|
-
return this.#onmessage;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
set onmessage(value: ((this: any, ev: any) => any) | null) {
|
|
413
|
-
this.#onmessage = value;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
send(data: string | ArrayBuffer | ArrayBufferView | Blob | Buffer): void {
|
|
417
|
-
// Handle different ready states
|
|
418
|
-
if (this.#readyState === 0) {
|
|
419
|
-
// CONNECTING
|
|
420
|
-
throw new DOMException(
|
|
421
|
-
"WebSocket is still in CONNECTING state",
|
|
422
|
-
"InvalidStateError",
|
|
423
|
-
);
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
if (this.#readyState === 2 || this.#readyState === 3) {
|
|
427
|
-
// CLOSING or CLOSED - silently ignore
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
let isBinary = false;
|
|
432
|
-
let messageData: string | ArrayBuffer;
|
|
433
|
-
|
|
434
|
-
if (typeof data === "string") {
|
|
435
|
-
messageData = data;
|
|
436
|
-
} else if (data instanceof ArrayBuffer) {
|
|
437
|
-
isBinary = true;
|
|
438
|
-
messageData = data;
|
|
439
|
-
} else if (ArrayBuffer.isView(data)) {
|
|
440
|
-
isBinary = true;
|
|
441
|
-
// Convert ArrayBufferView to ArrayBuffer
|
|
442
|
-
const view = data as ArrayBufferView;
|
|
443
|
-
// Check if it's a SharedArrayBuffer
|
|
444
|
-
if (view.buffer instanceof SharedArrayBuffer) {
|
|
445
|
-
// Copy SharedArrayBuffer to regular ArrayBuffer
|
|
446
|
-
const bytes = new Uint8Array(
|
|
447
|
-
view.buffer,
|
|
448
|
-
view.byteOffset,
|
|
449
|
-
view.byteLength,
|
|
450
|
-
);
|
|
451
|
-
messageData = bytes.buffer.slice(
|
|
452
|
-
bytes.byteOffset,
|
|
453
|
-
bytes.byteOffset + bytes.byteLength,
|
|
454
|
-
) as unknown as ArrayBuffer;
|
|
455
|
-
} else {
|
|
456
|
-
messageData = view.buffer.slice(
|
|
457
|
-
view.byteOffset,
|
|
458
|
-
view.byteOffset + view.byteLength,
|
|
459
|
-
) as ArrayBuffer;
|
|
460
|
-
}
|
|
461
|
-
} else if (data instanceof Blob) {
|
|
462
|
-
throw new Error("Blob sending not implemented in tunnel adapter");
|
|
463
|
-
} else if (typeof Buffer !== "undefined" && Buffer.isBuffer(data)) {
|
|
464
|
-
isBinary = true;
|
|
465
|
-
// Convert Buffer to ArrayBuffer
|
|
466
|
-
const buf = data as Buffer;
|
|
467
|
-
// Check if it's a SharedArrayBuffer
|
|
468
|
-
if (buf.buffer instanceof SharedArrayBuffer) {
|
|
469
|
-
// Copy SharedArrayBuffer to regular ArrayBuffer
|
|
470
|
-
const bytes = new Uint8Array(
|
|
471
|
-
buf.buffer,
|
|
472
|
-
buf.byteOffset,
|
|
473
|
-
buf.byteLength,
|
|
474
|
-
);
|
|
475
|
-
messageData = bytes.buffer.slice(
|
|
476
|
-
bytes.byteOffset,
|
|
477
|
-
bytes.byteOffset + bytes.byteLength,
|
|
478
|
-
) as unknown as ArrayBuffer;
|
|
479
|
-
} else {
|
|
480
|
-
messageData = buf.buffer.slice(
|
|
481
|
-
buf.byteOffset,
|
|
482
|
-
buf.byteOffset + buf.byteLength,
|
|
483
|
-
) as ArrayBuffer;
|
|
484
|
-
}
|
|
485
|
-
} else {
|
|
486
|
-
throw new Error("Invalid data type");
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
// Send through tunnel
|
|
490
|
-
this.#sendCallback(messageData, isBinary);
|
|
178
|
+
this.#close(code, reason, false);
|
|
491
179
|
}
|
|
492
180
|
|
|
181
|
+
// Public close method (used by tunnel.ts for stale websocket cleanup)
|
|
493
182
|
close(code?: number, reason?: string): void {
|
|
494
|
-
this.#
|
|
183
|
+
this.#close(code, reason, true);
|
|
495
184
|
}
|
|
496
185
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
listener: (event: any) => void,
|
|
500
|
-
options?: boolean | any,
|
|
501
|
-
): void {
|
|
502
|
-
if (typeof listener === "function") {
|
|
503
|
-
let listeners = this.#eventListeners.get(type);
|
|
504
|
-
if (!listeners) {
|
|
505
|
-
listeners = new Set();
|
|
506
|
-
this.#eventListeners.set(type, listeners);
|
|
507
|
-
}
|
|
508
|
-
listeners.add(listener);
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
removeEventListener(
|
|
513
|
-
type: string,
|
|
514
|
-
listener: (event: any) => void,
|
|
515
|
-
options?: boolean | any,
|
|
516
|
-
): void {
|
|
517
|
-
if (typeof listener === "function") {
|
|
518
|
-
const listeners = this.#eventListeners.get(type);
|
|
519
|
-
if (listeners) {
|
|
520
|
-
listeners.delete(listener);
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
dispatchEvent(event: any): boolean {
|
|
526
|
-
// TODO:
|
|
527
|
-
return true;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
static readonly CONNECTING = 0;
|
|
531
|
-
static readonly OPEN = 1;
|
|
532
|
-
static readonly CLOSING = 2;
|
|
533
|
-
static readonly CLOSED = 3;
|
|
186
|
+
#close(code: number | undefined, reason: string | undefined, sendCallback: boolean): void {
|
|
187
|
+
if (this.#readyState >= 2) return;
|
|
534
188
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
// Additional methods for compatibility
|
|
541
|
-
ping(data?: any, mask?: boolean, cb?: (err: Error) => void): void {
|
|
542
|
-
// Not implemented for tunnel - could be added if needed
|
|
543
|
-
if (cb) cb(new Error("Ping not supported in tunnel adapter"));
|
|
189
|
+
this.#readyState = 2;
|
|
190
|
+
if (sendCallback) this.#closeCallback(code, reason);
|
|
191
|
+
this.#readyState = 3;
|
|
192
|
+
this.#ws.triggerClose(code ?? 1000, reason ?? "");
|
|
544
193
|
}
|
|
545
194
|
|
|
546
|
-
|
|
547
|
-
// Not implemented for tunnel - could be added if needed
|
|
548
|
-
if (cb) cb(new Error("Pong not supported in tunnel adapter"));
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
/** @experimental */
|
|
552
|
-
terminate(): void {
|
|
195
|
+
#terminate(): void {
|
|
553
196
|
// Immediate close without close frame
|
|
554
|
-
this.#readyState = 3;
|
|
197
|
+
this.#readyState = 3;
|
|
555
198
|
this.#closeCallback(1006, "Abnormal Closure");
|
|
556
|
-
|
|
557
|
-
const event = {
|
|
558
|
-
wasClean: false,
|
|
559
|
-
code: 1006,
|
|
560
|
-
reason: "Abnormal Closure",
|
|
561
|
-
type: "close",
|
|
562
|
-
target: this,
|
|
563
|
-
};
|
|
564
|
-
|
|
565
|
-
this.#fireEvent("close", event);
|
|
199
|
+
this.#ws.triggerClose(1006, "Abnormal Closure", false);
|
|
566
200
|
}
|
|
567
201
|
}
|