@aigne/afs-session 1.11.0-beta.6

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/index.mjs ADDED
@@ -0,0 +1,1345 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import * as crypto from "node:crypto";
4
+
5
+ //#region src/protocol/types.ts
6
+ /**
7
+ * Protocol constants
8
+ */
9
+ const HEADER_SIZE = 9;
10
+ const MAX_PAYLOAD_SIZE = 16 * 1024 * 1024;
11
+ const MAX_JSON_DEPTH = 100;
12
+ const MAX_JSON_SIZE = 1 * 1024 * 1024;
13
+ const MAX_UINT32 = 4294967295;
14
+ /**
15
+ * Frame types
16
+ */
17
+ let FrameType = /* @__PURE__ */ function(FrameType) {
18
+ FrameType[FrameType["JSON"] = 1] = "JSON";
19
+ FrameType[FrameType["Binary"] = 2] = "Binary";
20
+ return FrameType;
21
+ }({});
22
+ /**
23
+ * Protocol errors
24
+ */
25
+ var ProtocolError = class extends Error {
26
+ constructor(code, message) {
27
+ super(message);
28
+ this.code = code;
29
+ this.name = "ProtocolError";
30
+ }
31
+ };
32
+ var PayloadTooLargeError = class extends ProtocolError {
33
+ constructor(size) {
34
+ super("payload_too_large", `Payload size ${size} exceeds maximum ${MAX_PAYLOAD_SIZE}`);
35
+ this.name = "PayloadTooLargeError";
36
+ }
37
+ };
38
+ var InvalidPayloadError = class extends ProtocolError {
39
+ constructor(message) {
40
+ super("invalid_payload", message);
41
+ this.name = "InvalidPayloadError";
42
+ }
43
+ };
44
+ var InvalidFrameTypeError = class extends ProtocolError {
45
+ constructor(type) {
46
+ super("invalid_frame_type", `Invalid frame type: 0x${type.toString(16).padStart(2, "0")}`);
47
+ this.name = "InvalidFrameTypeError";
48
+ }
49
+ };
50
+ var InvalidReqIdError = class extends ProtocolError {
51
+ constructor(reqId) {
52
+ super("invalid_req_id", `Invalid request ID: ${reqId} (must be 0-${MAX_UINT32})`);
53
+ this.name = "InvalidReqIdError";
54
+ }
55
+ };
56
+ var IncompleteFrameError = class extends ProtocolError {
57
+ constructor(message) {
58
+ super("incomplete_frame", message);
59
+ this.name = "IncompleteFrameError";
60
+ }
61
+ };
62
+
63
+ //#endregion
64
+ //#region src/protocol/frame.ts
65
+ /**
66
+ * Validates frame type.
67
+ */
68
+ function isValidFrameType(type) {
69
+ return type === FrameType.JSON || type === FrameType.Binary;
70
+ }
71
+ /**
72
+ * Encodes a frame with the given type, request ID, and payload.
73
+ *
74
+ * Frame structure (big-endian):
75
+ * - Length (4 bytes): payload length
76
+ * - Type (1 byte): frame type
77
+ * - ReqId (4 bytes): request ID
78
+ * - Payload (variable): actual data
79
+ *
80
+ * @param type Frame type (JSON or Binary)
81
+ * @param reqId Request ID (0 for events, >0 for request/response)
82
+ * @param payload The payload data
83
+ * @returns Encoded frame as Uint8Array
84
+ */
85
+ function encodeFrame(type, reqId, payload) {
86
+ if (payload == null) throw new InvalidPayloadError("Payload cannot be null or undefined");
87
+ if (payload.length > MAX_PAYLOAD_SIZE) throw new PayloadTooLargeError(payload.length);
88
+ if (!isValidFrameType(type)) throw new InvalidFrameTypeError(type);
89
+ if (reqId < 0 || reqId > MAX_UINT32 || !Number.isInteger(reqId)) throw new InvalidReqIdError(reqId);
90
+ const frame = new Uint8Array(HEADER_SIZE + payload.length);
91
+ const view = new DataView(frame.buffer, frame.byteOffset);
92
+ view.setUint32(0, payload.length);
93
+ view.setUint8(4, type);
94
+ view.setUint32(5, reqId);
95
+ frame.set(payload, HEADER_SIZE);
96
+ return frame;
97
+ }
98
+ /**
99
+ * Decodes a frame from a buffer.
100
+ *
101
+ * @param buffer The buffer containing the frame data
102
+ * @returns Object containing the decoded frame and any remaining bytes
103
+ * @throws IncompleteFrameError if buffer doesn't contain a complete frame
104
+ * @throws InvalidFrameTypeError if frame type is invalid
105
+ * @throws PayloadTooLargeError if payload length exceeds maximum
106
+ */
107
+ function decodeFrame(buffer) {
108
+ if (buffer.length < HEADER_SIZE) throw new IncompleteFrameError(`Buffer too small: ${buffer.length} bytes, need at least ${HEADER_SIZE}`);
109
+ const view = new DataView(buffer.buffer, buffer.byteOffset);
110
+ const length = view.getUint32(0);
111
+ const type = view.getUint8(4);
112
+ const reqId = view.getUint32(5);
113
+ if (length > MAX_PAYLOAD_SIZE) throw new PayloadTooLargeError(length);
114
+ if (!isValidFrameType(type)) throw new InvalidFrameTypeError(type);
115
+ const totalSize = HEADER_SIZE + length;
116
+ if (buffer.length < totalSize) throw new IncompleteFrameError(`Incomplete frame: have ${buffer.length} bytes, need ${totalSize}`);
117
+ const payload = buffer.slice(HEADER_SIZE, totalSize);
118
+ const remaining = buffer.slice(totalSize);
119
+ return {
120
+ frame: {
121
+ type,
122
+ reqId,
123
+ payload
124
+ },
125
+ remaining
126
+ };
127
+ }
128
+
129
+ //#endregion
130
+ //#region src/protocol/frame-decoder.ts
131
+ /**
132
+ * Error thrown when pending buffer exceeds maximum size
133
+ */
134
+ var BufferOverflowError = class extends ProtocolError {
135
+ constructor(size, maxSize) {
136
+ super("buffer_overflow", `Pending buffer size ${size} exceeds maximum ${maxSize}`);
137
+ this.name = "BufferOverflowError";
138
+ }
139
+ };
140
+ /**
141
+ * Streaming frame decoder that handles:
142
+ * - Partial frames across multiple pushes (拆包)
143
+ * - Multiple frames in single push (粘包)
144
+ * - Buffer overflow protection (security)
145
+ */
146
+ var FrameDecoder = class {
147
+ buffer = new Uint8Array(0);
148
+ maxPendingSize;
149
+ constructor(options) {
150
+ this.maxPendingSize = options?.maxPendingSize ?? MAX_PAYLOAD_SIZE + HEADER_SIZE;
151
+ }
152
+ /**
153
+ * Push data into the decoder and return any complete frames.
154
+ *
155
+ * @param data New data to append to buffer
156
+ * @returns Array of complete frames (may be empty)
157
+ * @throws InvalidFrameTypeError if frame type is invalid
158
+ * @throws PayloadTooLargeError if payload exceeds maximum
159
+ * @throws BufferOverflowError if pending buffer exceeds maximum
160
+ */
161
+ push(data) {
162
+ if (data.length === 0) return [];
163
+ const newBuffer = new Uint8Array(this.buffer.length + data.length);
164
+ newBuffer.set(this.buffer, 0);
165
+ newBuffer.set(data, this.buffer.length);
166
+ this.buffer = newBuffer;
167
+ if (this.buffer.length > this.maxPendingSize) throw new BufferOverflowError(this.buffer.length, this.maxPendingSize);
168
+ const frames = [];
169
+ while (this.buffer.length >= HEADER_SIZE) {
170
+ const payloadLength = new DataView(this.buffer.buffer, this.buffer.byteOffset).getUint32(0);
171
+ if (payloadLength > MAX_PAYLOAD_SIZE) throw new PayloadTooLargeError(payloadLength);
172
+ const totalFrameSize = HEADER_SIZE + payloadLength;
173
+ if (this.buffer.length < totalFrameSize) break;
174
+ const { frame, remaining } = decodeFrame(this.buffer);
175
+ frames.push(frame);
176
+ this.buffer = remaining;
177
+ }
178
+ return frames;
179
+ }
180
+ /**
181
+ * Reset the decoder state, discarding any buffered data.
182
+ */
183
+ reset() {
184
+ this.buffer = new Uint8Array(0);
185
+ }
186
+ /**
187
+ * Get the current pending buffer size.
188
+ */
189
+ get pendingSize() {
190
+ return this.buffer.length;
191
+ }
192
+ };
193
+
194
+ //#endregion
195
+ //#region src/protocol/message.ts
196
+ /**
197
+ * Error for invalid message structure
198
+ */
199
+ var InvalidMessageError = class extends ProtocolError {
200
+ constructor(message) {
201
+ super("invalid_message", message);
202
+ this.name = "InvalidMessageError";
203
+ }
204
+ };
205
+ /**
206
+ * Error for JSON parsing failures
207
+ */
208
+ var ParseError = class extends ProtocolError {
209
+ constructor(message) {
210
+ super("parse_error", `Parse error: ${message}`);
211
+ this.name = "ParseError";
212
+ }
213
+ };
214
+ /**
215
+ * Error for depth limit exceeded
216
+ */
217
+ var DepthLimitExceededError = class extends ProtocolError {
218
+ constructor() {
219
+ super("depth_limit_exceeded", `JSON depth limit exceeded (max ${MAX_JSON_DEPTH})`);
220
+ this.name = "DepthLimitExceededError";
221
+ }
222
+ };
223
+ /**
224
+ * Error for message too large
225
+ */
226
+ var MessageTooLargeError = class extends ProtocolError {
227
+ constructor(size) {
228
+ super("message_too_large", `Message too large: ${size} bytes (max ${MAX_JSON_SIZE})`);
229
+ this.name = "MessageTooLargeError";
230
+ }
231
+ };
232
+ /**
233
+ * Dangerous keys that should be filtered for security
234
+ */
235
+ const DANGEROUS_KEYS = new Set([
236
+ "__proto__",
237
+ "constructor",
238
+ "prototype"
239
+ ]);
240
+ /**
241
+ * Recursively filter dangerous keys from an object
242
+ */
243
+ function filterDangerousKeys(obj, depth = 0) {
244
+ if (depth > MAX_JSON_DEPTH) throw new DepthLimitExceededError();
245
+ if (obj === null || typeof obj !== "object") return obj;
246
+ if (Array.isArray(obj)) return obj.map((item) => filterDangerousKeys(item, depth + 1));
247
+ const filtered = {};
248
+ for (const [key, value] of Object.entries(obj)) if (!DANGEROUS_KEYS.has(key)) filtered[key] = filterDangerousKeys(value, depth + 1);
249
+ return filtered;
250
+ }
251
+ /**
252
+ * Calculate depth of a JSON object
253
+ */
254
+ function calculateDepth(obj, depth = 0) {
255
+ if (depth > MAX_JSON_DEPTH) return depth;
256
+ if (obj === null || typeof obj !== "object") return depth;
257
+ let maxDepth = depth;
258
+ if (Array.isArray(obj)) for (const item of obj) {
259
+ maxDepth = Math.max(maxDepth, calculateDepth(item, depth + 1));
260
+ if (maxDepth > MAX_JSON_DEPTH) return maxDepth;
261
+ }
262
+ else for (const value of Object.values(obj)) {
263
+ maxDepth = Math.max(maxDepth, calculateDepth(value, depth + 1));
264
+ if (maxDepth > MAX_JSON_DEPTH) return maxDepth;
265
+ }
266
+ return maxDepth;
267
+ }
268
+ /**
269
+ * Serialize a message to JSON bytes.
270
+ *
271
+ * @param message The message to serialize
272
+ * @returns UTF-8 encoded JSON bytes
273
+ * @throws InvalidMessageError if message is invalid
274
+ */
275
+ function serializeMessage(message) {
276
+ if (!message || typeof message !== "object" || !("type" in message)) throw new InvalidMessageError("Invalid message: missing type");
277
+ const { type } = message;
278
+ if (type === "request") {
279
+ const req = message;
280
+ if (!req.id) throw new InvalidMessageError("Invalid request: missing id");
281
+ if (!req.method) throw new InvalidMessageError("Invalid request: missing method");
282
+ }
283
+ const clean = {};
284
+ for (const [key, value] of Object.entries(message)) if (value !== void 0) clean[key] = value;
285
+ const json = JSON.stringify(clean);
286
+ return new TextEncoder().encode(json);
287
+ }
288
+ /**
289
+ * Deserialize JSON bytes to a message.
290
+ *
291
+ * @param data UTF-8 encoded JSON bytes
292
+ * @returns Parsed message
293
+ * @throws ParseError if JSON is invalid
294
+ * @throws InvalidMessageError if message structure is invalid
295
+ * @throws DepthLimitExceededError if JSON is too deeply nested
296
+ * @throws MessageTooLargeError if message exceeds size limit
297
+ */
298
+ function deserializeMessage(data) {
299
+ if (data.length > MAX_JSON_SIZE) throw new MessageTooLargeError(data.length);
300
+ let parsed;
301
+ try {
302
+ const json = new TextDecoder().decode(data);
303
+ parsed = JSON.parse(json);
304
+ } catch (e) {
305
+ throw new ParseError(e instanceof Error ? e.message : "Invalid JSON");
306
+ }
307
+ if (!parsed || typeof parsed !== "object") throw new InvalidMessageError("Invalid message: not an object");
308
+ if (calculateDepth(parsed) > MAX_JSON_DEPTH) throw new DepthLimitExceededError();
309
+ const filtered = filterDangerousKeys(parsed);
310
+ if (!("type" in filtered) || typeof filtered.type !== "string") throw new InvalidMessageError("Invalid message: missing type");
311
+ const { type } = filtered;
312
+ if (type !== "request" && type !== "response" && type !== "event") throw new InvalidMessageError(`Invalid message type: ${type}`);
313
+ return filtered;
314
+ }
315
+
316
+ //#endregion
317
+ //#region src/session/types.ts
318
+ /**
319
+ * Protocol version
320
+ */
321
+ const PROTOCOL_VERSION = "1.0";
322
+ /**
323
+ * Session error
324
+ */
325
+ var SessionError = class extends Error {
326
+ constructor(code, message) {
327
+ super(message);
328
+ this.code = code;
329
+ this.name = "SessionError";
330
+ }
331
+ };
332
+ /**
333
+ * Default options
334
+ */
335
+ const DEFAULT_SESSION_OPTIONS = {
336
+ maxSessions: 100,
337
+ requestTimeout: 3e4,
338
+ sessionTimeout: 3e4,
339
+ idleTimeout: 3e5,
340
+ heartbeatInterval: 3e4,
341
+ heartbeatTimeout: 9e4
342
+ };
343
+
344
+ //#endregion
345
+ //#region src/session/host.ts
346
+ /**
347
+ * Counter for generating unique session IDs
348
+ */
349
+ let sessionCounter = 0;
350
+ /**
351
+ * Validates namespace string
352
+ */
353
+ function isValidNamespace(namespace) {
354
+ if (namespace.length === 0 || namespace.length > 255) return false;
355
+ if (namespace.includes("..") || namespace.includes("/") || namespace.includes(":")) return false;
356
+ return true;
357
+ }
358
+ /**
359
+ * Generate session ID with uniqueness guarantee
360
+ */
361
+ function generateSessionId(namespace) {
362
+ sessionCounter++;
363
+ const random = Math.random().toString(36).substring(2, 8);
364
+ return `sess_${namespace}_${Date.now()}_${sessionCounter}_${random}`;
365
+ }
366
+ /**
367
+ * Session implementation
368
+ */
369
+ var SessionImpl = class {
370
+ id;
371
+ namespace;
372
+ name;
373
+ createdAt;
374
+ afs;
375
+ _state = "pending";
376
+ constructor(params) {
377
+ this.id = params.id || generateSessionId(params.namespace);
378
+ this.namespace = params.namespace;
379
+ this.name = params.name;
380
+ this.createdAt = /* @__PURE__ */ new Date();
381
+ this.afs = params.afs;
382
+ this._state = "attached";
383
+ }
384
+ get state() {
385
+ return this._state;
386
+ }
387
+ setState(state) {
388
+ this._state = state;
389
+ }
390
+ };
391
+ /**
392
+ * Stub AFS module for injected sessions.
393
+ * The real AFS operations are proxied back to the client.
394
+ */
395
+ var InjectedAFSModule = class {
396
+ name;
397
+ description;
398
+ constructor(name, description) {
399
+ this.name = name;
400
+ this.description = description;
401
+ }
402
+ };
403
+ /**
404
+ * Session host manages multiple client sessions (Observer side).
405
+ */
406
+ var SessionHost = class {
407
+ options;
408
+ connections = /* @__PURE__ */ new Map();
409
+ sessions = /* @__PURE__ */ new Map();
410
+ namespaces = /* @__PURE__ */ new Map();
411
+ sessionToConnection = /* @__PURE__ */ new Map();
412
+ disconnectedSessions = /* @__PURE__ */ new Map();
413
+ constructor(options = {}) {
414
+ this.options = {
415
+ ...DEFAULT_SESSION_OPTIONS,
416
+ ...options
417
+ };
418
+ }
419
+ /**
420
+ * Handle a new transport connection.
421
+ */
422
+ handleConnection(transport) {
423
+ const state = {
424
+ transport,
425
+ attached: false,
426
+ lastPong: Date.now()
427
+ };
428
+ this.connections.set(transport, state);
429
+ transport.on("frame", (frame) => {
430
+ this.handleFrame(state, frame).catch((err) => {
431
+ console.error("Error handling frame:", err);
432
+ });
433
+ });
434
+ transport.on("close", () => {
435
+ this.handleDisconnect(state);
436
+ });
437
+ transport.on("error", (err) => {
438
+ console.error("Transport error:", err);
439
+ });
440
+ }
441
+ /**
442
+ * Handle incoming frame from a connection.
443
+ */
444
+ async handleFrame(state, frame) {
445
+ if (frame.type !== FrameType.JSON) return;
446
+ try {
447
+ const message = deserializeMessage(frame.payload);
448
+ if (message.type === "event") {
449
+ if (message.event === "session.pong") state.lastPong = Date.now();
450
+ return;
451
+ }
452
+ if (message.type !== "request") return;
453
+ const request = message;
454
+ let response;
455
+ if (request.method === "session.attach") response = await this.handleAttach(state, request);
456
+ else if (request.method === "session.detach") response = await this.handleDetach(state, request);
457
+ else if (request.method.startsWith("afs.")) response = await this.handleAfsRequest(state, request);
458
+ else response = {
459
+ type: "response",
460
+ id: request.id,
461
+ error: {
462
+ code: "invalid_request",
463
+ message: `Unknown method: ${request.method}`
464
+ }
465
+ };
466
+ await this.sendResponse(state.transport, response);
467
+ } catch (err) {
468
+ console.error("Error processing frame:", err);
469
+ }
470
+ }
471
+ /**
472
+ * Handle session.attach request.
473
+ */
474
+ async handleAttach(state, request) {
475
+ if (state.attached) return {
476
+ type: "response",
477
+ id: request.id,
478
+ error: {
479
+ code: "invalid_request",
480
+ message: "Already attached to a session"
481
+ }
482
+ };
483
+ const params = request.params;
484
+ if (!params || !params.version) return {
485
+ type: "response",
486
+ id: request.id,
487
+ error: {
488
+ code: "invalid_request",
489
+ message: "Missing required parameter: version"
490
+ }
491
+ };
492
+ if (!params.namespace) return {
493
+ type: "response",
494
+ id: request.id,
495
+ error: {
496
+ code: "invalid_request",
497
+ message: "Missing required parameter: namespace"
498
+ }
499
+ };
500
+ if (params.version !== PROTOCOL_VERSION) return {
501
+ type: "response",
502
+ id: request.id,
503
+ error: {
504
+ code: "version_mismatch",
505
+ message: `Version mismatch: expected ${PROTOCOL_VERSION}, got ${params.version}`
506
+ }
507
+ };
508
+ if (!isValidNamespace(params.namespace)) return {
509
+ type: "response",
510
+ id: request.id,
511
+ error: {
512
+ code: "invalid_request",
513
+ message: "Invalid namespace"
514
+ }
515
+ };
516
+ if (params.sessionId) return this.handleReconnect(state, request, params);
517
+ if (this.sessions.size >= this.options.maxSessions) return {
518
+ type: "response",
519
+ id: request.id,
520
+ error: {
521
+ code: "invalid_request",
522
+ message: "Maximum sessions reached"
523
+ }
524
+ };
525
+ const existingSession = this.namespaces.get(params.namespace);
526
+ if (existingSession) if (params.replace) {
527
+ const oldConnectionState = this.sessionToConnection.get(existingSession.id);
528
+ if (oldConnectionState) {
529
+ await this.sendEvent(oldConnectionState.transport, {
530
+ type: "event",
531
+ event: "session.replaced",
532
+ data: {
533
+ sessionId: existingSession.id,
534
+ namespace: existingSession.namespace,
535
+ reason: "replaced by new connection"
536
+ }
537
+ });
538
+ if (oldConnectionState.heartbeatTimer) clearInterval(oldConnectionState.heartbeatTimer);
539
+ oldConnectionState.session = void 0;
540
+ oldConnectionState.attached = false;
541
+ this.sessionToConnection.delete(existingSession.id);
542
+ }
543
+ existingSession.setState("disconnected");
544
+ this.sessions.delete(existingSession.id);
545
+ this.namespaces.delete(params.namespace);
546
+ } else return {
547
+ type: "response",
548
+ id: request.id,
549
+ error: {
550
+ code: "namespace_conflict",
551
+ message: `Namespace '${params.namespace}' already exists`
552
+ }
553
+ };
554
+ const afs = new InjectedAFSModule(params.namespace, params.name);
555
+ const session = new SessionImpl({
556
+ namespace: params.namespace,
557
+ name: params.name,
558
+ afs
559
+ });
560
+ this.sessions.set(session.id, session);
561
+ this.namespaces.set(session.namespace, session);
562
+ this.sessionToConnection.set(session.id, state);
563
+ state.session = session;
564
+ state.attached = true;
565
+ this.startHeartbeat(state);
566
+ const result = {
567
+ sessionId: session.id,
568
+ namespace: session.namespace
569
+ };
570
+ return {
571
+ type: "response",
572
+ id: request.id,
573
+ result
574
+ };
575
+ }
576
+ /**
577
+ * Handle reconnect with existing session ID.
578
+ */
579
+ async handleReconnect(state, request, params) {
580
+ const sessionId = params.sessionId;
581
+ const namespace = params.namespace;
582
+ const disconnected = this.disconnectedSessions.get(sessionId);
583
+ if (disconnected) {
584
+ const session = disconnected.session;
585
+ if (session.namespace !== namespace) return {
586
+ type: "response",
587
+ id: request.id,
588
+ error: {
589
+ code: "invalid_session",
590
+ message: "Session ID does not match namespace"
591
+ }
592
+ };
593
+ clearTimeout(disconnected.timeoutId);
594
+ this.disconnectedSessions.delete(sessionId);
595
+ session.setState("attached");
596
+ this.sessions.set(session.id, session);
597
+ this.namespaces.set(session.namespace, session);
598
+ this.sessionToConnection.set(session.id, state);
599
+ state.session = session;
600
+ state.attached = true;
601
+ this.startHeartbeat(state);
602
+ return {
603
+ type: "response",
604
+ id: request.id,
605
+ result: {
606
+ sessionId: session.id,
607
+ namespace: session.namespace
608
+ }
609
+ };
610
+ }
611
+ const activeSession = this.sessions.get(sessionId);
612
+ if (activeSession) {
613
+ if (activeSession.namespace !== namespace) return {
614
+ type: "response",
615
+ id: request.id,
616
+ error: {
617
+ code: "invalid_session",
618
+ message: "Session ID does not match namespace"
619
+ }
620
+ };
621
+ return {
622
+ type: "response",
623
+ id: request.id,
624
+ error: {
625
+ code: "invalid_session",
626
+ message: "Session is already active"
627
+ }
628
+ };
629
+ }
630
+ return {
631
+ type: "response",
632
+ id: request.id,
633
+ error: {
634
+ code: "invalid_session",
635
+ message: "Session not found or expired"
636
+ }
637
+ };
638
+ }
639
+ /**
640
+ * Start heartbeat for a connection.
641
+ */
642
+ startHeartbeat(state) {
643
+ if (this.options.heartbeatInterval <= 0) return;
644
+ state.lastPong = Date.now();
645
+ state.heartbeatTimer = setInterval(() => {
646
+ if (Date.now() - state.lastPong > this.options.heartbeatTimeout) {
647
+ this.handleHeartbeatTimeout(state);
648
+ return;
649
+ }
650
+ this.sendEvent(state.transport, {
651
+ type: "event",
652
+ event: "session.ping",
653
+ data: { timestamp: Date.now() }
654
+ }).catch((err) => {
655
+ console.error("Error sending ping:", err);
656
+ });
657
+ }, this.options.heartbeatInterval);
658
+ }
659
+ /**
660
+ * Handle heartbeat timeout.
661
+ */
662
+ handleHeartbeatTimeout(state) {
663
+ if (state.heartbeatTimer) {
664
+ clearInterval(state.heartbeatTimer);
665
+ state.heartbeatTimer = void 0;
666
+ }
667
+ this.handleDisconnect(state);
668
+ state.transport.close().catch(() => {});
669
+ }
670
+ /**
671
+ * Handle session.detach request.
672
+ */
673
+ async handleDetach(state, request) {
674
+ if (!state.attached || !state.session) return {
675
+ type: "response",
676
+ id: request.id,
677
+ error: {
678
+ code: "invalid_session",
679
+ message: "Not attached to a session"
680
+ }
681
+ };
682
+ const session = state.session;
683
+ if (state.heartbeatTimer) {
684
+ clearInterval(state.heartbeatTimer);
685
+ state.heartbeatTimer = void 0;
686
+ }
687
+ session.setState("disconnected");
688
+ this.sessions.delete(session.id);
689
+ this.namespaces.delete(session.namespace);
690
+ this.sessionToConnection.delete(session.id);
691
+ state.session = void 0;
692
+ state.attached = false;
693
+ return {
694
+ type: "response",
695
+ id: request.id,
696
+ result: {}
697
+ };
698
+ }
699
+ /**
700
+ * Handle AFS request (passthrough to client).
701
+ */
702
+ async handleAfsRequest(state, request) {
703
+ if (!state.attached) return {
704
+ type: "response",
705
+ id: request.id,
706
+ error: {
707
+ code: "invalid_session",
708
+ message: "Must attach before sending AFS requests"
709
+ }
710
+ };
711
+ return {
712
+ type: "response",
713
+ id: request.id,
714
+ error: {
715
+ code: "not_implemented",
716
+ message: "AFS passthrough not yet implemented"
717
+ }
718
+ };
719
+ }
720
+ /**
721
+ * Handle transport disconnect.
722
+ */
723
+ handleDisconnect(state) {
724
+ if (state.heartbeatTimer) {
725
+ clearInterval(state.heartbeatTimer);
726
+ state.heartbeatTimer = void 0;
727
+ }
728
+ if (state.session) {
729
+ const session = state.session;
730
+ session.setState("disconnected");
731
+ this.sessions.delete(session.id);
732
+ this.namespaces.delete(session.namespace);
733
+ this.sessionToConnection.delete(session.id);
734
+ if (this.options.sessionTimeout > 0) {
735
+ const timeoutId = setTimeout(() => {
736
+ this.disconnectedSessions.delete(session.id);
737
+ }, this.options.sessionTimeout);
738
+ this.disconnectedSessions.set(session.id, {
739
+ session,
740
+ disconnectedAt: Date.now(),
741
+ timeoutId
742
+ });
743
+ }
744
+ }
745
+ this.connections.delete(state.transport);
746
+ }
747
+ /**
748
+ * Send response back to client.
749
+ */
750
+ async sendResponse(transport, response) {
751
+ const payload = serializeMessage(response);
752
+ await transport.send({
753
+ type: FrameType.JSON,
754
+ reqId: Number.parseInt(response.id, 10),
755
+ payload
756
+ });
757
+ }
758
+ /**
759
+ * Send event to client.
760
+ */
761
+ async sendEvent(transport, event) {
762
+ const payload = serializeMessage(event);
763
+ await transport.send({
764
+ type: FrameType.JSON,
765
+ reqId: 0,
766
+ payload
767
+ });
768
+ }
769
+ /**
770
+ * Get all active sessions.
771
+ */
772
+ getSessions() {
773
+ return Array.from(this.sessions.values());
774
+ }
775
+ /**
776
+ * Close the session host and all sessions.
777
+ */
778
+ async close() {
779
+ for (const state of this.connections.values()) if (state.heartbeatTimer) clearInterval(state.heartbeatTimer);
780
+ for (const disconnected of this.disconnectedSessions.values()) clearTimeout(disconnected.timeoutId);
781
+ for (const state of this.connections.values()) await state.transport.close();
782
+ this.connections.clear();
783
+ this.sessions.clear();
784
+ this.namespaces.clear();
785
+ this.sessionToConnection.clear();
786
+ this.disconnectedSessions.clear();
787
+ }
788
+ };
789
+
790
+ //#endregion
791
+ //#region src/transport/types.ts
792
+ /**
793
+ * Default socket path
794
+ */
795
+ const DEFAULT_SOCKET_PATH = "~/.afs/observer.sock";
796
+ /**
797
+ * Default WebSocket port
798
+ */
799
+ const DEFAULT_WS_PORT = 9999;
800
+
801
+ //#endregion
802
+ //#region src/transport/unix-socket.ts
803
+ /**
804
+ * Expand ~ to home directory
805
+ */
806
+ function expandPath(p) {
807
+ if (p.startsWith("~/")) {
808
+ const home = process.env.HOME || process.env.USERPROFILE || "/";
809
+ return path.join(home, p.slice(2));
810
+ }
811
+ return p;
812
+ }
813
+ /**
814
+ * Error for Unix socket operations
815
+ */
816
+ var UnixSocketError = class extends Error {
817
+ constructor(code, message) {
818
+ super(message);
819
+ this.code = code;
820
+ this.name = "UnixSocketError";
821
+ }
822
+ };
823
+ /**
824
+ * Unix socket transport implementation.
825
+ */
826
+ var UnixSocketTransport = class {
827
+ socket;
828
+ decoder = new FrameDecoder();
829
+ handlers = /* @__PURE__ */ new Map();
830
+ _connected = false;
831
+ constructor(socket) {
832
+ this.socket = socket;
833
+ this._connected = true;
834
+ this.setupHandlers();
835
+ }
836
+ setupHandlers() {
837
+ this.socket.on("data", (data) => {
838
+ try {
839
+ const frames = this.decoder.push(new Uint8Array(data));
840
+ for (const frame of frames) this.emit("frame", frame);
841
+ } catch (error) {
842
+ this.emit("error", error);
843
+ }
844
+ });
845
+ this.socket.on("close", () => {
846
+ this._connected = false;
847
+ this.emit("close");
848
+ });
849
+ this.socket.on("error", (error) => {
850
+ this.emit("error", error);
851
+ });
852
+ }
853
+ async send(frame) {
854
+ if (!this._connected) throw new UnixSocketError("not_connected", "Transport is not connected");
855
+ const encoded = encodeFrame(frame.type, frame.reqId, frame.payload);
856
+ return new Promise((resolve, reject) => {
857
+ this.socket.write(Buffer.from(encoded), (err) => {
858
+ if (err) reject(err);
859
+ else resolve();
860
+ });
861
+ });
862
+ }
863
+ async close() {
864
+ return new Promise((resolve) => {
865
+ if (!this._connected) {
866
+ resolve();
867
+ return;
868
+ }
869
+ this.socket.end(() => {
870
+ this._connected = false;
871
+ resolve();
872
+ });
873
+ });
874
+ }
875
+ on(event, handler) {
876
+ if (!this.handlers.has(event)) this.handlers.set(event, /* @__PURE__ */ new Set());
877
+ this.handlers.get(event).add(handler);
878
+ }
879
+ off(event, handler) {
880
+ this.handlers.get(event)?.delete(handler);
881
+ }
882
+ emit(event, ...args) {
883
+ const handlers = this.handlers.get(event);
884
+ if (handlers) for (const handler of handlers) handler(...args);
885
+ }
886
+ get connected() {
887
+ return this._connected;
888
+ }
889
+ };
890
+ /**
891
+ * Unix socket server implementation.
892
+ */
893
+ var UnixSocketServer = class {
894
+ server = null;
895
+ socketPath;
896
+ maxConnections;
897
+ connections = /* @__PURE__ */ new Set();
898
+ connectionHandler;
899
+ _listening = false;
900
+ constructor(options = {}) {
901
+ this.socketPath = expandPath(options.socketPath || "~/.afs/observer.sock");
902
+ this.maxConnections = options.maxConnections ?? 100;
903
+ }
904
+ async listen() {
905
+ try {
906
+ if (fs.lstatSync(this.socketPath).isSymbolicLink()) throw new UnixSocketError("symlink_not_allowed", `Socket path is a symlink: ${this.socketPath}`);
907
+ } catch (e) {
908
+ if (e.code !== "ENOENT") {
909
+ if (e instanceof UnixSocketError) throw e;
910
+ }
911
+ }
912
+ const dir = path.dirname(this.socketPath);
913
+ fs.mkdirSync(dir, { recursive: true });
914
+ try {
915
+ fs.unlinkSync(this.socketPath);
916
+ } catch (e) {
917
+ if (e.code !== "ENOENT") throw new UnixSocketError("remove_failed", `Failed to remove existing socket: ${e.message}`);
918
+ }
919
+ const net = await import("node:net");
920
+ return new Promise((resolve, reject) => {
921
+ this.server = net.createServer((socket) => {
922
+ if (this.connections.size >= this.maxConnections) {
923
+ socket.end();
924
+ socket.destroy();
925
+ return;
926
+ }
927
+ const transport = new UnixSocketTransport(socket);
928
+ this.connections.add(transport);
929
+ transport.on("close", () => {
930
+ this.connections.delete(transport);
931
+ });
932
+ this.connectionHandler?.(transport);
933
+ });
934
+ this.server.on("error", (err) => {
935
+ reject(err);
936
+ });
937
+ this.server.listen(this.socketPath, () => {
938
+ this._listening = true;
939
+ resolve();
940
+ });
941
+ });
942
+ }
943
+ async close() {
944
+ if (!this.server) return;
945
+ for (const conn of this.connections) await conn.close();
946
+ this.connections.clear();
947
+ return new Promise((resolve) => {
948
+ this.server.close(() => {
949
+ this._listening = false;
950
+ try {
951
+ fs.unlinkSync(this.socketPath);
952
+ } catch {}
953
+ resolve();
954
+ });
955
+ });
956
+ }
957
+ onConnection(handler) {
958
+ this.connectionHandler = handler;
959
+ }
960
+ get listening() {
961
+ return this._listening;
962
+ }
963
+ };
964
+ /**
965
+ * Unix socket client implementation.
966
+ */
967
+ var UnixSocketClient = class {
968
+ socket = null;
969
+ transport = null;
970
+ socketPath;
971
+ handlers = /* @__PURE__ */ new Map();
972
+ constructor(options = {}) {
973
+ this.socketPath = expandPath(options.socketPath || "~/.afs/observer.sock");
974
+ }
975
+ async connect() {
976
+ const net = await import("node:net");
977
+ return new Promise((resolve, reject) => {
978
+ this.socket = net.connect(this.socketPath, () => {
979
+ this.transport = new UnixSocketTransport(this.socket);
980
+ this.transport.on("frame", (frame) => this.emit("frame", frame));
981
+ this.transport.on("error", (error) => this.emit("error", error));
982
+ this.transport.on("close", () => this.emit("close"));
983
+ resolve();
984
+ });
985
+ this.socket.on("error", (err) => {
986
+ reject(err);
987
+ });
988
+ });
989
+ }
990
+ async send(frame) {
991
+ if (!this.transport) throw new UnixSocketError("not_connected", "Client is not connected");
992
+ return this.transport.send(frame);
993
+ }
994
+ async close() {
995
+ if (this.transport) await this.transport.close();
996
+ this.socket = null;
997
+ this.transport = null;
998
+ }
999
+ on(event, handler) {
1000
+ if (!this.handlers.has(event)) this.handlers.set(event, /* @__PURE__ */ new Set());
1001
+ this.handlers.get(event).add(handler);
1002
+ }
1003
+ off(event, handler) {
1004
+ this.handlers.get(event)?.delete(handler);
1005
+ }
1006
+ emit(event, ...args) {
1007
+ const handlers = this.handlers.get(event);
1008
+ if (handlers) for (const handler of handlers) handler(...args);
1009
+ }
1010
+ get connected() {
1011
+ return this.transport?.connected ?? false;
1012
+ }
1013
+ };
1014
+
1015
+ //#endregion
1016
+ //#region src/transport/websocket.ts
1017
+ /**
1018
+ * Error for WebSocket operations
1019
+ */
1020
+ var WebSocketError = class extends Error {
1021
+ constructor(code, message) {
1022
+ super(message);
1023
+ this.code = code;
1024
+ this.name = "WebSocketError";
1025
+ }
1026
+ };
1027
+ /**
1028
+ * Constant-time string comparison to prevent timing attacks
1029
+ */
1030
+ function secureCompare(a, b) {
1031
+ if (a.length !== b.length) {
1032
+ crypto.timingSafeEqual(Buffer.from(a), Buffer.from(a));
1033
+ return false;
1034
+ }
1035
+ return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b));
1036
+ }
1037
+ /**
1038
+ * Rate limiter for auth attempts
1039
+ */
1040
+ var AuthRateLimiter = class {
1041
+ attempts = /* @__PURE__ */ new Map();
1042
+ maxAttempts = 5;
1043
+ windowMs = 6e4;
1044
+ isRateLimited(ip) {
1045
+ const now = Date.now();
1046
+ const record = this.attempts.get(ip);
1047
+ if (!record || record.resetAt < now) return false;
1048
+ return record.count >= this.maxAttempts;
1049
+ }
1050
+ recordAttempt(ip) {
1051
+ const now = Date.now();
1052
+ const record = this.attempts.get(ip);
1053
+ if (!record || record.resetAt < now) this.attempts.set(ip, {
1054
+ count: 1,
1055
+ resetAt: now + this.windowMs
1056
+ });
1057
+ else record.count++;
1058
+ }
1059
+ recordSuccess(ip) {
1060
+ this.attempts.delete(ip);
1061
+ }
1062
+ };
1063
+ /**
1064
+ * WebSocket transport for server-side (Bun WebSocket).
1065
+ * Wraps Bun's ServerWebSocket to provide Transport interface.
1066
+ */
1067
+ var WebSocketTransport = class {
1068
+ ws;
1069
+ decoder = new FrameDecoder();
1070
+ handlers = /* @__PURE__ */ new Map();
1071
+ _connected = true;
1072
+ constructor(ws) {
1073
+ this.ws = ws;
1074
+ }
1075
+ /**
1076
+ * Called by server when message is received.
1077
+ */
1078
+ handleMessage(data) {
1079
+ try {
1080
+ const bytes = data instanceof ArrayBuffer ? new Uint8Array(data) : data;
1081
+ const frames = this.decoder.push(bytes);
1082
+ for (const frame of frames) this.emit("frame", frame);
1083
+ } catch (error) {
1084
+ this.emit("error", error);
1085
+ }
1086
+ }
1087
+ /**
1088
+ * Called by server when connection closes.
1089
+ */
1090
+ handleClose() {
1091
+ this._connected = false;
1092
+ this.emit("close");
1093
+ }
1094
+ /**
1095
+ * Called by server when error occurs.
1096
+ */
1097
+ handleError(error) {
1098
+ this.emit("error", error);
1099
+ }
1100
+ async send(frame) {
1101
+ if (!this._connected) throw new WebSocketError("not_connected", "Transport is not connected");
1102
+ const encoded = encodeFrame(frame.type, frame.reqId, frame.payload);
1103
+ this.ws.sendBinary(encoded);
1104
+ }
1105
+ async close() {
1106
+ if (this._connected) {
1107
+ this.ws.close();
1108
+ this._connected = false;
1109
+ }
1110
+ }
1111
+ on(event, handler) {
1112
+ if (!this.handlers.has(event)) this.handlers.set(event, /* @__PURE__ */ new Set());
1113
+ this.handlers.get(event).add(handler);
1114
+ }
1115
+ off(event, handler) {
1116
+ this.handlers.get(event)?.delete(handler);
1117
+ }
1118
+ emit(event, ...args) {
1119
+ const handlers = this.handlers.get(event);
1120
+ if (handlers) for (const handler of handlers) handler(...args);
1121
+ }
1122
+ get connected() {
1123
+ return this._connected;
1124
+ }
1125
+ };
1126
+ /**
1127
+ * WebSocket server implementation using Bun.serve.
1128
+ */
1129
+ var WebSocketServer = class {
1130
+ server = null;
1131
+ host;
1132
+ port;
1133
+ authToken;
1134
+ maxConnections;
1135
+ connections = /* @__PURE__ */ new Set();
1136
+ wsToTransport = /* @__PURE__ */ new Map();
1137
+ connectionHandler;
1138
+ _listening = false;
1139
+ rateLimiter = new AuthRateLimiter();
1140
+ constructor(options = {}) {
1141
+ this.host = options.host || "localhost";
1142
+ this.port = options.port || 9999;
1143
+ this.authToken = options.authToken;
1144
+ this.maxConnections = options.maxConnections ?? 100;
1145
+ }
1146
+ async listen() {
1147
+ const self = this;
1148
+ this.server = Bun.serve({
1149
+ hostname: this.host,
1150
+ port: this.port,
1151
+ fetch(req, server) {
1152
+ const ip = server.requestIP(req)?.address || "unknown";
1153
+ if (self.authToken && self.rateLimiter.isRateLimited(ip)) return new Response("Too many auth attempts", { status: 429 });
1154
+ if (self.authToken) {
1155
+ const queryToken = new URL(req.url).searchParams.get("token");
1156
+ const authHeader = req.headers.get("Authorization");
1157
+ const providedToken = (authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null) || queryToken;
1158
+ if (!providedToken) {
1159
+ self.rateLimiter.recordAttempt(ip);
1160
+ return new Response("Authentication required", { status: 401 });
1161
+ }
1162
+ if (!secureCompare(providedToken, self.authToken)) {
1163
+ self.rateLimiter.recordAttempt(ip);
1164
+ return new Response("Authentication failed", { status: 403 });
1165
+ }
1166
+ self.rateLimiter.recordSuccess(ip);
1167
+ }
1168
+ if (self.connections.size >= self.maxConnections) return new Response("Too many connections", { status: 503 });
1169
+ if (!server.upgrade(req, { data: { ip } })) return new Response("WebSocket upgrade failed", { status: 400 });
1170
+ },
1171
+ websocket: {
1172
+ open(ws) {
1173
+ const transport = new WebSocketTransport(ws);
1174
+ self.connections.add(transport);
1175
+ self.wsToTransport.set(ws, transport);
1176
+ transport.on("close", () => {
1177
+ self.connections.delete(transport);
1178
+ self.wsToTransport.delete(ws);
1179
+ });
1180
+ self.connectionHandler?.(transport);
1181
+ },
1182
+ message(ws, message) {
1183
+ const transport = self.wsToTransport.get(ws);
1184
+ if (transport) {
1185
+ const data = typeof message === "string" ? new TextEncoder().encode(message) : message instanceof ArrayBuffer ? new Uint8Array(message) : new Uint8Array(message.buffer);
1186
+ transport.handleMessage(data);
1187
+ }
1188
+ },
1189
+ close(ws) {
1190
+ const transport = self.wsToTransport.get(ws);
1191
+ if (transport) {
1192
+ transport.handleClose();
1193
+ self.connections.delete(transport);
1194
+ self.wsToTransport.delete(ws);
1195
+ }
1196
+ },
1197
+ perMessageDeflate: false
1198
+ }
1199
+ });
1200
+ this._listening = true;
1201
+ }
1202
+ async close() {
1203
+ for (const conn of this.connections) await conn.close();
1204
+ this.connections.clear();
1205
+ this.wsToTransport.clear();
1206
+ if (this.server) {
1207
+ this.server.stop();
1208
+ this._listening = false;
1209
+ }
1210
+ }
1211
+ onConnection(handler) {
1212
+ this.connectionHandler = handler;
1213
+ }
1214
+ get listening() {
1215
+ return this._listening;
1216
+ }
1217
+ get address() {
1218
+ return `ws://${this.host}:${this.port}`;
1219
+ }
1220
+ };
1221
+ /**
1222
+ * WebSocket client transport (uses browser WebSocket API).
1223
+ */
1224
+ var ClientWebSocketTransport = class {
1225
+ ws;
1226
+ decoder = new FrameDecoder();
1227
+ handlers = /* @__PURE__ */ new Map();
1228
+ _connected = false;
1229
+ constructor(ws) {
1230
+ this.ws = ws;
1231
+ this._connected = ws.readyState === WebSocket.OPEN;
1232
+ this.setupHandlers();
1233
+ }
1234
+ setupHandlers() {
1235
+ this.ws.onmessage = (event) => {
1236
+ try {
1237
+ const data = event.data instanceof ArrayBuffer ? new Uint8Array(event.data) : typeof event.data === "string" ? new TextEncoder().encode(event.data) : new Uint8Array(event.data);
1238
+ const frames = this.decoder.push(data);
1239
+ for (const frame of frames) this.emit("frame", frame);
1240
+ } catch (error) {
1241
+ this.emit("error", error);
1242
+ }
1243
+ };
1244
+ this.ws.onclose = () => {
1245
+ this._connected = false;
1246
+ this.emit("close");
1247
+ };
1248
+ this.ws.onerror = () => {
1249
+ this.emit("error", /* @__PURE__ */ new Error("WebSocket error"));
1250
+ };
1251
+ this.ws.onopen = () => {
1252
+ this._connected = true;
1253
+ };
1254
+ }
1255
+ async send(frame) {
1256
+ if (!this._connected) throw new WebSocketError("not_connected", "Transport is not connected");
1257
+ const encoded = encodeFrame(frame.type, frame.reqId, frame.payload);
1258
+ this.ws.send(encoded);
1259
+ }
1260
+ async close() {
1261
+ if (this._connected) {
1262
+ this.ws.close();
1263
+ this._connected = false;
1264
+ }
1265
+ }
1266
+ on(event, handler) {
1267
+ if (!this.handlers.has(event)) this.handlers.set(event, /* @__PURE__ */ new Set());
1268
+ this.handlers.get(event).add(handler);
1269
+ }
1270
+ off(event, handler) {
1271
+ this.handlers.get(event)?.delete(handler);
1272
+ }
1273
+ emit(event, ...args) {
1274
+ const handlers = this.handlers.get(event);
1275
+ if (handlers) for (const handler of handlers) handler(...args);
1276
+ }
1277
+ get connected() {
1278
+ return this._connected;
1279
+ }
1280
+ };
1281
+ /**
1282
+ * WebSocket client implementation.
1283
+ */
1284
+ var WebSocketClient = class {
1285
+ ws = null;
1286
+ transport = null;
1287
+ host;
1288
+ port;
1289
+ authToken;
1290
+ handlers = /* @__PURE__ */ new Map();
1291
+ constructor(options = {}) {
1292
+ this.host = options.host || "localhost";
1293
+ this.port = options.port || 9999;
1294
+ this.authToken = options.authToken;
1295
+ }
1296
+ async connect() {
1297
+ return new Promise((resolve, reject) => {
1298
+ let url = `ws://${this.host}:${this.port}`;
1299
+ if (this.authToken) url += `?token=${encodeURIComponent(this.authToken)}`;
1300
+ this.ws = new WebSocket(url);
1301
+ this.ws.binaryType = "arraybuffer";
1302
+ this.ws.onopen = () => {
1303
+ this.transport = new ClientWebSocketTransport(this.ws);
1304
+ this.transport.on("frame", (frame) => this.emit("frame", frame));
1305
+ this.transport.on("error", (error) => this.emit("error", error));
1306
+ this.transport.on("close", () => this.emit("close"));
1307
+ resolve();
1308
+ };
1309
+ this.ws.onerror = () => {
1310
+ reject(new WebSocketError("connection_failed", "Failed to connect"));
1311
+ };
1312
+ this.ws.onclose = (event) => {
1313
+ if (!this.transport) if (event.code === 1002 || event.code === 1008) reject(new WebSocketError("auth_failed", "Authentication failed"));
1314
+ else reject(new WebSocketError("connection_failed", "Connection closed"));
1315
+ };
1316
+ });
1317
+ }
1318
+ async send(frame) {
1319
+ if (!this.transport) throw new WebSocketError("not_connected", "Client is not connected");
1320
+ return this.transport.send(frame);
1321
+ }
1322
+ async close() {
1323
+ if (this.transport) await this.transport.close();
1324
+ this.ws = null;
1325
+ this.transport = null;
1326
+ }
1327
+ on(event, handler) {
1328
+ if (!this.handlers.has(event)) this.handlers.set(event, /* @__PURE__ */ new Set());
1329
+ this.handlers.get(event).add(handler);
1330
+ }
1331
+ off(event, handler) {
1332
+ this.handlers.get(event)?.delete(handler);
1333
+ }
1334
+ emit(event, ...args) {
1335
+ const handlers = this.handlers.get(event);
1336
+ if (handlers) for (const handler of handlers) handler(...args);
1337
+ }
1338
+ get connected() {
1339
+ return this.transport?.connected ?? false;
1340
+ }
1341
+ };
1342
+
1343
+ //#endregion
1344
+ export { BufferOverflowError, DEFAULT_SESSION_OPTIONS, DEFAULT_SOCKET_PATH, DEFAULT_WS_PORT, DepthLimitExceededError, FrameDecoder, FrameType, HEADER_SIZE, IncompleteFrameError, InvalidFrameTypeError, InvalidMessageError, InvalidPayloadError, InvalidReqIdError, MAX_JSON_DEPTH, MAX_JSON_SIZE, MAX_PAYLOAD_SIZE, MAX_UINT32, MessageTooLargeError, PROTOCOL_VERSION, ParseError, PayloadTooLargeError, ProtocolError, SessionError, SessionHost, UnixSocketClient, UnixSocketError, UnixSocketServer, UnixSocketTransport, WebSocketClient, WebSocketError, WebSocketServer, WebSocketTransport, decodeFrame, deserializeMessage, encodeFrame, serializeMessage };
1345
+ //# sourceMappingURL=index.mjs.map