cloudforge-agent 0.1.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.
@@ -0,0 +1,695 @@
1
+ /**
2
+ * CloudForge Agent WebSocket Manager
3
+ * Handles connection to CloudForge server
4
+ */
5
+ import { io } from 'socket.io-client';
6
+ import * as os from 'os';
7
+ import chalk from 'chalk';
8
+ import { getSystemInfo, VERSION } from './config.js';
9
+ export class WebSocketManager {
10
+ socket = null;
11
+ config;
12
+ terminalManager;
13
+ fileManager;
14
+ gitManager;
15
+ reconnectAttempts = 0;
16
+ heartbeatTimer = null;
17
+ isConnected = false;
18
+ constructor(config, terminalManager, fileManager, gitManager) {
19
+ this.config = config;
20
+ this.terminalManager = terminalManager;
21
+ this.fileManager = fileManager;
22
+ this.gitManager = gitManager;
23
+ }
24
+ /**
25
+ * Connect to CloudForge server
26
+ */
27
+ async connect() {
28
+ return new Promise((resolve, reject) => {
29
+ const socketUrl = `${this.config.serverUrl}/agent`;
30
+ this.socket = io(socketUrl, {
31
+ auth: {
32
+ token: this.config.token,
33
+ },
34
+ transports: ['websocket', 'polling'],
35
+ reconnection: true,
36
+ reconnectionAttempts: Infinity,
37
+ reconnectionDelay: this.config.reconnectDelay,
38
+ reconnectionDelayMax: this.config.maxReconnectDelay,
39
+ timeout: 20000,
40
+ });
41
+ // Connection events
42
+ this.socket.on('connect', () => {
43
+ this.isConnected = true;
44
+ this.reconnectAttempts = 0;
45
+ console.log(chalk.green('Connected to CloudForge!'));
46
+ // Send system info
47
+ this.sendSystemInfo();
48
+ // Start heartbeat
49
+ this.startHeartbeat();
50
+ resolve();
51
+ });
52
+ this.socket.on('connect_error', (err) => {
53
+ if (this.reconnectAttempts === 0) {
54
+ console.error(chalk.red('Connection failed:'), err.message);
55
+ }
56
+ this.reconnectAttempts++;
57
+ if (this.reconnectAttempts >= 5 && !this.isConnected) {
58
+ reject(new Error(`Failed to connect after ${this.reconnectAttempts} attempts: ${err.message}`));
59
+ }
60
+ });
61
+ this.socket.on('disconnect', (reason) => {
62
+ this.isConnected = false;
63
+ this.stopHeartbeat();
64
+ console.log(chalk.yellow('Disconnected:'), reason);
65
+ if (reason === 'io server disconnect') {
66
+ // Server disconnected us, try to reconnect
67
+ console.log(chalk.yellow('Server disconnected. Reconnecting...'));
68
+ this.socket?.connect();
69
+ }
70
+ });
71
+ this.socket.on('reconnect', (attemptNumber) => {
72
+ console.log(chalk.green(`Reconnected after ${attemptNumber} attempts`));
73
+ this.sendSystemInfo();
74
+ this.startHeartbeat();
75
+ });
76
+ this.socket.on('reconnect_attempt', (attemptNumber) => {
77
+ if (this.config.debug) {
78
+ console.log(chalk.gray(`Reconnect attempt ${attemptNumber}...`));
79
+ }
80
+ });
81
+ this.socket.on('error', (err) => {
82
+ console.error(chalk.red('Socket error:'), err);
83
+ });
84
+ // Terminal events from server
85
+ this.setupTerminalHandlers();
86
+ // File events from server
87
+ this.setupFileHandlers();
88
+ // Git events from server
89
+ this.setupGitHandlers();
90
+ });
91
+ }
92
+ /**
93
+ * Setup terminal event handlers
94
+ */
95
+ setupTerminalHandlers() {
96
+ if (!this.socket)
97
+ return;
98
+ // Spawn terminal request
99
+ this.socket.on('terminal:spawn', (msg) => {
100
+ if (this.config.debug) {
101
+ console.log(chalk.gray(`Terminal spawn request: ${msg.sessionId}`));
102
+ }
103
+ // Resolve cwd: use provided cwd, or fall back to homeDir
104
+ let cwd = this.config.homeDir;
105
+ if (msg.cwd) {
106
+ let expanded = msg.cwd;
107
+ if (expanded === '~' || expanded.startsWith('~/')) {
108
+ expanded = expanded.replace('~', os.homedir());
109
+ }
110
+ cwd = expanded;
111
+ }
112
+ try {
113
+ const session = this.terminalManager.spawn(msg.sessionId, msg.shell || this.config.shell, msg.cols || 80, msg.rows || 24, cwd);
114
+ // Forward output to server
115
+ session.onData((data) => {
116
+ this.socket?.emit('terminal:output', {
117
+ type: 'terminal:output',
118
+ sessionId: msg.sessionId,
119
+ data,
120
+ });
121
+ });
122
+ // Forward exit to server
123
+ session.onExit((exitCode) => {
124
+ this.socket?.emit('terminal:closed', {
125
+ type: 'terminal:closed',
126
+ sessionId: msg.sessionId,
127
+ exitCode,
128
+ });
129
+ this.terminalManager.remove(msg.sessionId);
130
+ });
131
+ }
132
+ catch (err) {
133
+ console.error(chalk.red('Terminal spawn error:'), err);
134
+ this.socket?.emit('terminal:error', {
135
+ type: 'terminal:error',
136
+ sessionId: msg.sessionId,
137
+ error: err instanceof Error ? err.message : String(err),
138
+ });
139
+ }
140
+ });
141
+ // Terminal input from server
142
+ this.socket.on('terminal:input', (msg) => {
143
+ if (this.config.debug) {
144
+ console.log(chalk.gray(`Terminal input: sessionId=${msg.sessionId}, data length=${msg.data.length}`));
145
+ }
146
+ const session = this.terminalManager.get(msg.sessionId);
147
+ if (session) {
148
+ session.write(msg.data);
149
+ }
150
+ else {
151
+ console.warn(chalk.yellow(`No terminal session found for: ${msg.sessionId}`));
152
+ console.warn(chalk.yellow(`Available sessions: ${this.terminalManager.sessionIds.join(', ')}`));
153
+ }
154
+ });
155
+ // Terminal resize from server
156
+ this.socket.on('terminal:resize', (msg) => {
157
+ const session = this.terminalManager.get(msg.sessionId);
158
+ if (session) {
159
+ session.resize(msg.cols, msg.rows);
160
+ }
161
+ });
162
+ // Terminal kill from server
163
+ this.socket.on('terminal:kill', (msg) => {
164
+ this.terminalManager.kill(msg.sessionId);
165
+ });
166
+ // Get terminal cwd
167
+ this.socket.on('terminal:getcwd', async (msg) => {
168
+ const cwd = await this.terminalManager.getCwd(msg.sessionId);
169
+ if (cwd) {
170
+ this.socket?.emit('terminal:cwd', {
171
+ type: 'terminal:cwd',
172
+ sessionId: msg.sessionId,
173
+ cwd,
174
+ });
175
+ }
176
+ });
177
+ }
178
+ /**
179
+ * Setup file event handlers
180
+ */
181
+ setupFileHandlers() {
182
+ if (!this.socket)
183
+ return;
184
+ // List directory
185
+ this.socket.on('files:list', async (msg) => {
186
+ if (this.config.debug) {
187
+ console.log(chalk.gray(`Files list request: ${msg.path}`));
188
+ }
189
+ try {
190
+ const entries = await this.fileManager.list(msg.path);
191
+ this.socket?.emit('files:list:response', {
192
+ type: 'files:list:response',
193
+ requestId: msg.requestId,
194
+ path: msg.path,
195
+ entries,
196
+ success: true,
197
+ });
198
+ }
199
+ catch (err) {
200
+ console.error(chalk.red('Files list error:'), err);
201
+ this.socket?.emit('files:list:response', {
202
+ type: 'files:list:response',
203
+ requestId: msg.requestId,
204
+ path: msg.path,
205
+ entries: [],
206
+ success: false,
207
+ error: err instanceof Error ? err.message : String(err),
208
+ });
209
+ }
210
+ });
211
+ // Read file
212
+ this.socket.on('file:read', async (msg) => {
213
+ if (this.config.debug) {
214
+ console.log(chalk.gray(`File read request: ${msg.path}`));
215
+ }
216
+ try {
217
+ const result = await this.fileManager.read(msg.path);
218
+ this.socket?.emit('file:read:response', {
219
+ type: 'file:read:response',
220
+ requestId: msg.requestId,
221
+ ...result,
222
+ success: true,
223
+ });
224
+ }
225
+ catch (err) {
226
+ console.error(chalk.red('File read error:'), err);
227
+ this.socket?.emit('file:read:response', {
228
+ type: 'file:read:response',
229
+ requestId: msg.requestId,
230
+ path: msg.path,
231
+ content: '',
232
+ encoding: 'utf8',
233
+ size: 0,
234
+ isBinary: false,
235
+ success: false,
236
+ error: err instanceof Error ? err.message : String(err),
237
+ });
238
+ }
239
+ });
240
+ // Write file
241
+ this.socket.on('file:write', async (msg) => {
242
+ if (this.config.debug) {
243
+ console.log(chalk.gray(`File write request: ${msg.path}`));
244
+ }
245
+ try {
246
+ const result = await this.fileManager.write(msg.path, msg.content, msg.encoding || 'utf8');
247
+ this.socket?.emit('file:write:response', {
248
+ type: 'file:write:response',
249
+ requestId: msg.requestId,
250
+ ...result,
251
+ });
252
+ }
253
+ catch (err) {
254
+ console.error(chalk.red('File write error:'), err);
255
+ this.socket?.emit('file:write:response', {
256
+ type: 'file:write:response',
257
+ requestId: msg.requestId,
258
+ path: msg.path,
259
+ success: false,
260
+ bytesWritten: 0,
261
+ error: err instanceof Error ? err.message : String(err),
262
+ });
263
+ }
264
+ });
265
+ // Delete file/directory
266
+ this.socket.on('file:delete', async (msg) => {
267
+ if (this.config.debug) {
268
+ console.log(chalk.gray(`File delete request: ${msg.path}`));
269
+ }
270
+ try {
271
+ const result = await this.fileManager.delete(msg.path, msg.recursive);
272
+ this.socket?.emit('file:delete:response', {
273
+ type: 'file:delete:response',
274
+ requestId: msg.requestId,
275
+ ...result,
276
+ });
277
+ }
278
+ catch (err) {
279
+ console.error(chalk.red('File delete error:'), err);
280
+ this.socket?.emit('file:delete:response', {
281
+ type: 'file:delete:response',
282
+ requestId: msg.requestId,
283
+ path: msg.path,
284
+ success: false,
285
+ error: err instanceof Error ? err.message : String(err),
286
+ });
287
+ }
288
+ });
289
+ // Create directory
290
+ this.socket.on('file:mkdir', async (msg) => {
291
+ if (this.config.debug) {
292
+ console.log(chalk.gray(`Mkdir request: ${msg.path}`));
293
+ }
294
+ try {
295
+ const result = await this.fileManager.mkdir(msg.path, msg.recursive);
296
+ this.socket?.emit('file:mkdir:response', {
297
+ type: 'file:mkdir:response',
298
+ requestId: msg.requestId,
299
+ ...result,
300
+ });
301
+ }
302
+ catch (err) {
303
+ console.error(chalk.red('Mkdir error:'), err);
304
+ this.socket?.emit('file:mkdir:response', {
305
+ type: 'file:mkdir:response',
306
+ requestId: msg.requestId,
307
+ path: msg.path,
308
+ success: false,
309
+ error: err instanceof Error ? err.message : String(err),
310
+ });
311
+ }
312
+ });
313
+ // Rename/move file
314
+ this.socket.on('file:rename', async (msg) => {
315
+ if (this.config.debug) {
316
+ console.log(chalk.gray(`Rename request: ${msg.fromPath} -> ${msg.toPath}`));
317
+ }
318
+ try {
319
+ const result = await this.fileManager.rename(msg.fromPath, msg.toPath);
320
+ this.socket?.emit('file:rename:response', {
321
+ type: 'file:rename:response',
322
+ requestId: msg.requestId,
323
+ fromPath: msg.fromPath,
324
+ ...result,
325
+ });
326
+ }
327
+ catch (err) {
328
+ console.error(chalk.red('Rename error:'), err);
329
+ this.socket?.emit('file:rename:response', {
330
+ type: 'file:rename:response',
331
+ requestId: msg.requestId,
332
+ fromPath: msg.fromPath,
333
+ path: msg.toPath,
334
+ success: false,
335
+ error: err instanceof Error ? err.message : String(err),
336
+ });
337
+ }
338
+ });
339
+ // Stat file/directory
340
+ this.socket.on('file:stat', async (msg) => {
341
+ if (this.config.debug) {
342
+ console.log(chalk.gray(`Stat request: ${msg.path}`));
343
+ }
344
+ try {
345
+ const entry = await this.fileManager.stat(msg.path);
346
+ this.socket?.emit('file:stat:response', {
347
+ type: 'file:stat:response',
348
+ requestId: msg.requestId,
349
+ path: msg.path,
350
+ entry,
351
+ success: entry !== null,
352
+ error: entry === null ? 'File not found' : undefined,
353
+ });
354
+ }
355
+ catch (err) {
356
+ console.error(chalk.red('Stat error:'), err);
357
+ this.socket?.emit('file:stat:response', {
358
+ type: 'file:stat:response',
359
+ requestId: msg.requestId,
360
+ path: msg.path,
361
+ entry: null,
362
+ success: false,
363
+ error: err instanceof Error ? err.message : String(err),
364
+ });
365
+ }
366
+ });
367
+ }
368
+ /**
369
+ * Resolve git cwd - handles ~ expansion and field name compatibility
370
+ */
371
+ resolveGitCwd(msg) {
372
+ let cwd = msg.cwd || msg.path || os.homedir();
373
+ if (cwd === '~' || cwd.startsWith('~/')) {
374
+ cwd = cwd.replace('~', os.homedir());
375
+ }
376
+ return cwd;
377
+ }
378
+ /**
379
+ * Setup Git event handlers
380
+ */
381
+ setupGitHandlers() {
382
+ if (!this.socket)
383
+ return;
384
+ // Git status
385
+ this.socket.on('git:status', async (msg) => {
386
+ const cwd = this.resolveGitCwd(msg);
387
+ if (this.config.debug) {
388
+ console.log(chalk.gray(`Git status request: ${cwd}`));
389
+ }
390
+ try {
391
+ const status = await this.gitManager.status(cwd);
392
+ this.socket?.emit('git:status:response', {
393
+ type: 'git:status:response',
394
+ requestId: msg.requestId,
395
+ status,
396
+ success: true,
397
+ });
398
+ }
399
+ catch (err) {
400
+ console.error(chalk.red('Git status error:'), err);
401
+ this.socket?.emit('git:status:response', {
402
+ type: 'git:status:response',
403
+ requestId: msg.requestId,
404
+ status: null,
405
+ success: false,
406
+ error: err instanceof Error ? err.message : String(err),
407
+ });
408
+ }
409
+ });
410
+ // Git add (stage files)
411
+ this.socket.on('git:add', async (msg) => {
412
+ const cwd = this.resolveGitCwd(msg);
413
+ if (this.config.debug) {
414
+ console.log(chalk.gray(`Git add request: ${msg.files.join(', ')}`));
415
+ }
416
+ try {
417
+ const result = await this.gitManager.add(cwd, msg.files);
418
+ this.socket?.emit('git:add:response', {
419
+ type: 'git:add:response',
420
+ requestId: msg.requestId,
421
+ ...result,
422
+ });
423
+ }
424
+ catch (err) {
425
+ console.error(chalk.red('Git add error:'), err);
426
+ this.socket?.emit('git:add:response', {
427
+ type: 'git:add:response',
428
+ requestId: msg.requestId,
429
+ success: false,
430
+ error: err instanceof Error ? err.message : String(err),
431
+ });
432
+ }
433
+ });
434
+ // Git reset (unstage files)
435
+ this.socket.on('git:reset', async (msg) => {
436
+ const cwd = this.resolveGitCwd(msg);
437
+ if (this.config.debug) {
438
+ console.log(chalk.gray(`Git reset request: ${msg.files.join(', ')}`));
439
+ }
440
+ try {
441
+ const result = await this.gitManager.reset(cwd, msg.files);
442
+ this.socket?.emit('git:reset:response', {
443
+ type: 'git:reset:response',
444
+ requestId: msg.requestId,
445
+ ...result,
446
+ });
447
+ }
448
+ catch (err) {
449
+ console.error(chalk.red('Git reset error:'), err);
450
+ this.socket?.emit('git:reset:response', {
451
+ type: 'git:reset:response',
452
+ requestId: msg.requestId,
453
+ success: false,
454
+ error: err instanceof Error ? err.message : String(err),
455
+ });
456
+ }
457
+ });
458
+ // Git commit
459
+ this.socket.on('git:commit', async (msg) => {
460
+ const cwd = this.resolveGitCwd(msg);
461
+ if (this.config.debug) {
462
+ console.log(chalk.gray(`Git commit request: ${msg.message}`));
463
+ }
464
+ try {
465
+ const result = await this.gitManager.commit(cwd, msg.message);
466
+ this.socket?.emit('git:commit:response', {
467
+ type: 'git:commit:response',
468
+ requestId: msg.requestId,
469
+ ...result,
470
+ });
471
+ }
472
+ catch (err) {
473
+ console.error(chalk.red('Git commit error:'), err);
474
+ this.socket?.emit('git:commit:response', {
475
+ type: 'git:commit:response',
476
+ requestId: msg.requestId,
477
+ success: false,
478
+ error: err instanceof Error ? err.message : String(err),
479
+ });
480
+ }
481
+ });
482
+ // Git pull
483
+ this.socket.on('git:pull', async (msg) => {
484
+ const cwd = this.resolveGitCwd(msg);
485
+ if (this.config.debug) {
486
+ console.log(chalk.gray('Git pull request'));
487
+ }
488
+ try {
489
+ const result = await this.gitManager.pull(cwd);
490
+ this.socket?.emit('git:pull:response', {
491
+ type: 'git:pull:response',
492
+ requestId: msg.requestId,
493
+ ...result,
494
+ });
495
+ }
496
+ catch (err) {
497
+ console.error(chalk.red('Git pull error:'), err);
498
+ this.socket?.emit('git:pull:response', {
499
+ type: 'git:pull:response',
500
+ requestId: msg.requestId,
501
+ success: false,
502
+ error: err instanceof Error ? err.message : String(err),
503
+ });
504
+ }
505
+ });
506
+ // Git push
507
+ this.socket.on('git:push', async (msg) => {
508
+ const cwd = this.resolveGitCwd(msg);
509
+ if (this.config.debug) {
510
+ console.log(chalk.gray('Git push request'));
511
+ }
512
+ try {
513
+ const result = await this.gitManager.push(cwd);
514
+ this.socket?.emit('git:push:response', {
515
+ type: 'git:push:response',
516
+ requestId: msg.requestId,
517
+ ...result,
518
+ });
519
+ }
520
+ catch (err) {
521
+ console.error(chalk.red('Git push error:'), err);
522
+ this.socket?.emit('git:push:response', {
523
+ type: 'git:push:response',
524
+ requestId: msg.requestId,
525
+ success: false,
526
+ error: err instanceof Error ? err.message : String(err),
527
+ });
528
+ }
529
+ });
530
+ // Git diff
531
+ this.socket.on('git:diff', async (msg) => {
532
+ const cwd = this.resolveGitCwd(msg);
533
+ if (this.config.debug) {
534
+ console.log(chalk.gray(`Git diff request: ${msg.file || 'all'}`));
535
+ }
536
+ try {
537
+ const diff = await this.gitManager.diff(cwd, msg.file, msg.staged);
538
+ this.socket?.emit('git:diff:response', {
539
+ type: 'git:diff:response',
540
+ requestId: msg.requestId,
541
+ diff,
542
+ success: true,
543
+ });
544
+ }
545
+ catch (err) {
546
+ console.error(chalk.red('Git diff error:'), err);
547
+ this.socket?.emit('git:diff:response', {
548
+ type: 'git:diff:response',
549
+ requestId: msg.requestId,
550
+ diff: null,
551
+ success: false,
552
+ error: err instanceof Error ? err.message : String(err),
553
+ });
554
+ }
555
+ });
556
+ // Git log
557
+ this.socket.on('git:log', async (msg) => {
558
+ const cwd = this.resolveGitCwd(msg);
559
+ if (this.config.debug) {
560
+ console.log(chalk.gray(`Git log request: limit=${msg.limit || 20}`));
561
+ }
562
+ try {
563
+ const logs = await this.gitManager.log(cwd, msg.limit);
564
+ this.socket?.emit('git:log:response', {
565
+ type: 'git:log:response',
566
+ requestId: msg.requestId,
567
+ logs,
568
+ success: true,
569
+ });
570
+ }
571
+ catch (err) {
572
+ console.error(chalk.red('Git log error:'), err);
573
+ this.socket?.emit('git:log:response', {
574
+ type: 'git:log:response',
575
+ requestId: msg.requestId,
576
+ logs: [],
577
+ success: false,
578
+ error: err instanceof Error ? err.message : String(err),
579
+ });
580
+ }
581
+ });
582
+ // Git branches
583
+ this.socket.on('git:branches', async (msg) => {
584
+ const cwd = this.resolveGitCwd(msg);
585
+ if (this.config.debug) {
586
+ console.log(chalk.gray('Git branches request'));
587
+ }
588
+ try {
589
+ const result = await this.gitManager.branches(cwd);
590
+ this.socket?.emit('git:branches:response', {
591
+ type: 'git:branches:response',
592
+ requestId: msg.requestId,
593
+ ...result,
594
+ success: true,
595
+ });
596
+ }
597
+ catch (err) {
598
+ console.error(chalk.red('Git branches error:'), err);
599
+ this.socket?.emit('git:branches:response', {
600
+ type: 'git:branches:response',
601
+ requestId: msg.requestId,
602
+ current: '',
603
+ branches: [],
604
+ success: false,
605
+ error: err instanceof Error ? err.message : String(err),
606
+ });
607
+ }
608
+ });
609
+ // Git checkout (discard changes)
610
+ this.socket.on('git:checkout', async (msg) => {
611
+ const cwd = this.resolveGitCwd(msg);
612
+ if (this.config.debug) {
613
+ console.log(chalk.gray(`Git checkout request: ${msg.files.join(', ')}`));
614
+ }
615
+ try {
616
+ const result = await this.gitManager.checkout(cwd, msg.files);
617
+ this.socket?.emit('git:checkout:response', {
618
+ type: 'git:checkout:response',
619
+ requestId: msg.requestId,
620
+ ...result,
621
+ });
622
+ }
623
+ catch (err) {
624
+ console.error(chalk.red('Git checkout error:'), err);
625
+ this.socket?.emit('git:checkout:response', {
626
+ type: 'git:checkout:response',
627
+ requestId: msg.requestId,
628
+ success: false,
629
+ error: err instanceof Error ? err.message : String(err),
630
+ });
631
+ }
632
+ });
633
+ }
634
+ /**
635
+ * Send system info to server
636
+ */
637
+ sendSystemInfo() {
638
+ const systemInfo = getSystemInfo();
639
+ this.socket?.emit('system:info', {
640
+ type: 'system:info',
641
+ os: systemInfo.os,
642
+ hostname: systemInfo.hostname,
643
+ version: VERSION,
644
+ shell: systemInfo.shell,
645
+ homeDir: systemInfo.homeDir,
646
+ });
647
+ }
648
+ /**
649
+ * Start heartbeat timer
650
+ */
651
+ startHeartbeat() {
652
+ this.stopHeartbeat();
653
+ this.heartbeatTimer = setInterval(() => {
654
+ if (!this.isConnected)
655
+ return;
656
+ const systemInfo = getSystemInfo();
657
+ this.socket?.emit('heartbeat', {
658
+ type: 'heartbeat',
659
+ timestamp: Date.now(),
660
+ os: systemInfo.os,
661
+ hostname: systemInfo.hostname,
662
+ version: VERSION,
663
+ uptime: process.uptime(),
664
+ });
665
+ if (this.config.debug) {
666
+ console.log(chalk.gray('Heartbeat sent'));
667
+ }
668
+ }, this.config.heartbeatInterval);
669
+ }
670
+ /**
671
+ * Stop heartbeat timer
672
+ */
673
+ stopHeartbeat() {
674
+ if (this.heartbeatTimer) {
675
+ clearInterval(this.heartbeatTimer);
676
+ this.heartbeatTimer = null;
677
+ }
678
+ }
679
+ /**
680
+ * Disconnect from server
681
+ */
682
+ disconnect() {
683
+ this.stopHeartbeat();
684
+ this.socket?.disconnect();
685
+ this.socket = null;
686
+ this.isConnected = false;
687
+ }
688
+ /**
689
+ * Check if connected
690
+ */
691
+ get connected() {
692
+ return this.isConnected;
693
+ }
694
+ }
695
+ //# sourceMappingURL=websocket.js.map