mobilecoder-mcp 1.0.5 → 2.0.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/.security.log +3 -0
- package/dist/agent.d.ts +15 -8
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +161 -61
- package/dist/agent.js.map +1 -1
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +35 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.js +24 -21
- package/dist/index.js.map +1 -1
- package/dist/mcp-handler.d.ts.map +1 -1
- package/dist/mcp-handler.js +46 -6
- package/dist/mcp-handler.js.map +1 -1
- package/dist/security.d.ts.map +1 -1
- package/dist/security.js +2 -5
- package/dist/security.js.map +1 -1
- package/dist/webrtc.d.ts +4 -11
- package/dist/webrtc.d.ts.map +1 -1
- package/dist/webrtc.js +97 -290
- package/dist/webrtc.js.map +1 -1
- package/package.json +23 -24
- package/src/agent.ts +178 -67
- package/src/cli.ts +35 -0
- package/src/types.d.ts +8 -0
- package/src/adapters/cli-adapter.ts +0 -73
- package/src/index.ts +0 -172
- package/src/mcp-handler.ts +0 -327
- package/src/security.ts +0 -345
- package/src/tool-detector.ts +0 -110
- package/src/webrtc.ts +0 -369
package/src/tool-detector.ts
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { exec } from 'child_process';
|
|
2
|
-
import { promisify } from 'util';
|
|
3
|
-
import * as fs from 'fs';
|
|
4
|
-
import * as path from 'path';
|
|
5
|
-
import * as os from 'os';
|
|
6
|
-
|
|
7
|
-
const execAsync = promisify(exec);
|
|
8
|
-
|
|
9
|
-
export interface DetectedTool {
|
|
10
|
-
id: string;
|
|
11
|
-
name: string;
|
|
12
|
-
version: string;
|
|
13
|
-
path: string;
|
|
14
|
-
isInstalled: boolean;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export class ToolDetector {
|
|
18
|
-
async detectAll(): Promise<DetectedTool[]> {
|
|
19
|
-
const tools = [
|
|
20
|
-
this.checkClaude(),
|
|
21
|
-
this.checkGemini(),
|
|
22
|
-
this.checkQoder(),
|
|
23
|
-
this.checkKiro(),
|
|
24
|
-
this.checkAider(),
|
|
25
|
-
this.checkCursor()
|
|
26
|
-
];
|
|
27
|
-
|
|
28
|
-
const results = await Promise.all(tools);
|
|
29
|
-
return results.filter(t => t.isInstalled);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
private async checkCommand(command: string): Promise<string | null> {
|
|
33
|
-
try {
|
|
34
|
-
const { stdout } = await execAsync(`${command} --version`);
|
|
35
|
-
return stdout.trim();
|
|
36
|
-
} catch {
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
private async checkClaude(): Promise<DetectedTool> {
|
|
42
|
-
const version = await this.checkCommand('claude');
|
|
43
|
-
return {
|
|
44
|
-
id: 'claude',
|
|
45
|
-
name: 'Claude Code',
|
|
46
|
-
version: version || '',
|
|
47
|
-
path: '',
|
|
48
|
-
isInstalled: !!version
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
private async checkGemini(): Promise<DetectedTool> {
|
|
53
|
-
const version = await this.checkCommand('gemini');
|
|
54
|
-
return {
|
|
55
|
-
id: 'gemini',
|
|
56
|
-
name: 'Gemini CLI',
|
|
57
|
-
version: version || '',
|
|
58
|
-
path: '',
|
|
59
|
-
isInstalled: !!version
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
private async checkQoder(): Promise<DetectedTool> {
|
|
64
|
-
const version = await this.checkCommand('qoder');
|
|
65
|
-
return {
|
|
66
|
-
id: 'qoder',
|
|
67
|
-
name: 'Qoder',
|
|
68
|
-
version: version || '',
|
|
69
|
-
path: '',
|
|
70
|
-
isInstalled: !!version
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
private async checkKiro(): Promise<DetectedTool> {
|
|
75
|
-
const version = await this.checkCommand('kiro');
|
|
76
|
-
return {
|
|
77
|
-
id: 'kiro',
|
|
78
|
-
name: 'Kiro',
|
|
79
|
-
version: version || '',
|
|
80
|
-
path: '',
|
|
81
|
-
isInstalled: !!version
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
private async checkAider(): Promise<DetectedTool> {
|
|
86
|
-
const version = await this.checkCommand('aider');
|
|
87
|
-
return {
|
|
88
|
-
id: 'aider',
|
|
89
|
-
name: 'Aider',
|
|
90
|
-
version: version || '',
|
|
91
|
-
path: '',
|
|
92
|
-
isInstalled: !!version
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
private async checkCursor(): Promise<DetectedTool> {
|
|
97
|
-
// Cursor is an app, not typically a CLI command for version checking in the same way
|
|
98
|
-
// We check for config file existence as a proxy for "installed/configured"
|
|
99
|
-
const configPath = path.join(os.homedir(), '.cursor', 'mcp.json');
|
|
100
|
-
const isInstalled = fs.existsSync(configPath);
|
|
101
|
-
|
|
102
|
-
return {
|
|
103
|
-
id: 'mcp', // Maps to 'mcp' in ToolSelector
|
|
104
|
-
name: 'Cursor',
|
|
105
|
-
version: 'App',
|
|
106
|
-
path: configPath,
|
|
107
|
-
isInstalled
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
}
|
package/src/webrtc.ts
DELETED
|
@@ -1,369 +0,0 @@
|
|
|
1
|
-
import SimplePeer from 'simple-peer';
|
|
2
|
-
import { createRequire } from 'module';
|
|
3
|
-
import { createHash } from 'crypto';
|
|
4
|
-
const require = createRequire(import.meta.url);
|
|
5
|
-
|
|
6
|
-
// wrtc modülü Node.js ortamında WebRTC desteği sağlar
|
|
7
|
-
let wrtc: any;
|
|
8
|
-
|
|
9
|
-
// Try loading wrtc implementations with better error logging
|
|
10
|
-
try {
|
|
11
|
-
// @roamhq/wrtc is often more stable on modern Node.js versions
|
|
12
|
-
wrtc = require('@roamhq/wrtc');
|
|
13
|
-
// console.log('✅ Loaded @roamhq/wrtc');
|
|
14
|
-
} catch (e) {
|
|
15
|
-
try {
|
|
16
|
-
wrtc = require('wrtc');
|
|
17
|
-
// console.log('✅ Loaded wrtc');
|
|
18
|
-
} catch (e2) {
|
|
19
|
-
console.warn('⚠️ wrtc module not available. Install @roamhq/wrtc or wrtc for WebRTC support.');
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Simple HMAC implementation for Node.js
|
|
24
|
-
function generateHMAC(data: string, secret: string): string {
|
|
25
|
-
return createHash('sha256').update(data + secret).digest('hex');
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Validate SDP for security
|
|
29
|
-
function validateSDP(sdp: string): boolean {
|
|
30
|
-
if (!sdp || typeof sdp !== 'string') return false;
|
|
31
|
-
|
|
32
|
-
// Basic sanity check
|
|
33
|
-
if (sdp.length > 10000) return false;
|
|
34
|
-
|
|
35
|
-
// Check for DTLS fingerprint (critical for encryption)
|
|
36
|
-
if (!sdp.includes('a=fingerprint:')) {
|
|
37
|
-
// console.warn('⚠️ [MCP] SDP missing DTLS fingerprint');
|
|
38
|
-
// Still allow, as some implementations might differ, but warn
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return true;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export class WebRTCConnection {
|
|
45
|
-
private peer: SimplePeer.Instance | null = null;
|
|
46
|
-
private code: string;
|
|
47
|
-
private signalingUrl: string;
|
|
48
|
-
private isConnected: boolean = false;
|
|
49
|
-
private pollingInterval?: NodeJS.Timeout;
|
|
50
|
-
private onMessageCallback?: (message: any) => void;
|
|
51
|
-
private onConnectCallback?: () => void;
|
|
52
|
-
private onDisconnectCallback?: () => void;
|
|
53
|
-
private debug: boolean;
|
|
54
|
-
private hmacSecret: string;
|
|
55
|
-
private cleanupInterval?: NodeJS.Timeout;
|
|
56
|
-
private messageNonce = new Set<string>();
|
|
57
|
-
|
|
58
|
-
constructor(code: string, signalingUrl: string, debug: boolean = false) {
|
|
59
|
-
this.code = code;
|
|
60
|
-
this.signalingUrl = signalingUrl;
|
|
61
|
-
this.debug = debug;
|
|
62
|
-
|
|
63
|
-
// Generate HMAC secret for this session
|
|
64
|
-
this.hmacSecret = createHash('sha256')
|
|
65
|
-
.update(code + Date.now() + Math.random())
|
|
66
|
-
.digest('hex');
|
|
67
|
-
|
|
68
|
-
// Start cleanup interval
|
|
69
|
-
this.startCleanup();
|
|
70
|
-
|
|
71
|
-
// Validate signaling URL
|
|
72
|
-
try {
|
|
73
|
-
const url = new URL(signalingUrl);
|
|
74
|
-
if (!url.protocol.startsWith('https') && url.hostname !== 'localhost' && url.hostname !== '127.0.0.1') {
|
|
75
|
-
console.warn('⚠️ [MCP] Warning: Signaling URL should use HTTPS in production');
|
|
76
|
-
}
|
|
77
|
-
} catch (e) {
|
|
78
|
-
console.error('❌ [MCP] Invalid signaling URL:', signalingUrl);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
private startCleanup(): void {
|
|
83
|
-
this.cleanupInterval = setInterval(() => {
|
|
84
|
-
// Clean up old nonces (prevent memory leak)
|
|
85
|
-
if (this.messageNonce.size > 1000) {
|
|
86
|
-
this.messageNonce.clear();
|
|
87
|
-
}
|
|
88
|
-
}, 60000); // Every minute
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
private async sendAnswerWithRetry(signal: any, attempts = 3): Promise<void> {
|
|
93
|
-
const timestamp = new Date().toLocaleTimeString();
|
|
94
|
-
const signalType = signal.type || (signal.candidate ? 'candidate' : 'answer');
|
|
95
|
-
|
|
96
|
-
// Use trickle endpoint for ICE candidates, standard answer endpoint for SDP answer
|
|
97
|
-
const endpoint = signal.candidate ? '/signaling/answer' : '/signaling/answer';
|
|
98
|
-
|
|
99
|
-
if (this.debug) console.log(`[${timestamp}] 📤 [Desktop] Sending ${signalType} to server...`);
|
|
100
|
-
|
|
101
|
-
for (let i = 0; i < attempts; i++) {
|
|
102
|
-
try {
|
|
103
|
-
const response = await fetch(`${this.signalingUrl}${endpoint}`, {
|
|
104
|
-
method: 'POST',
|
|
105
|
-
headers: { 'Content-Type': 'application/json' },
|
|
106
|
-
body: JSON.stringify({ code: this.code, signal })
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
if (!response.ok) {
|
|
110
|
-
throw new Error(`Server returned ${response.status}`);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (this.debug) console.log(`[${timestamp}] ✅ [Desktop] ${signalType} sent.`);
|
|
114
|
-
return;
|
|
115
|
-
} catch (error) {
|
|
116
|
-
console.error(`[${timestamp}] ❌ [Desktop] Failed to send ${signalType} (Attempt ${i + 1}/${attempts}):`, error);
|
|
117
|
-
if (i < attempts - 1) {
|
|
118
|
-
await new Promise(r => setTimeout(r, 1000)); // Wait 1s
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
console.error(`[${timestamp}] ❌ [Desktop] Failed to send ${signalType} after all attempts.`);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
async connect(): Promise<void> {
|
|
126
|
-
const timestamp = new Date().toLocaleTimeString();
|
|
127
|
-
console.log(`[${timestamp}] 📡 Connecting to signaling server: ${this.signalingUrl}`);
|
|
128
|
-
console.log(`[${timestamp}] 🔑 Using connection code: ${this.code}`);
|
|
129
|
-
|
|
130
|
-
return new Promise((resolve, reject) => {
|
|
131
|
-
try {
|
|
132
|
-
if (!wrtc) {
|
|
133
|
-
console.warn(`[${timestamp}] ⚠️ WebRTC initialized without Node.js native support (wrtc). Connection will fail.`);
|
|
134
|
-
console.warn(`[${timestamp}] 💡 Please install @roamhq/wrtc or wrtc package.`);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Fetch dynamic ICE servers with timeout
|
|
138
|
-
const iceController = new AbortController();
|
|
139
|
-
const iceTimeout = setTimeout(() => iceController.abort(), 3000);
|
|
140
|
-
|
|
141
|
-
fetch(`${this.signalingUrl}/signaling/ice-servers`, { signal: iceController.signal })
|
|
142
|
-
.then(res => res.json())
|
|
143
|
-
.then((data: any) => {
|
|
144
|
-
clearTimeout(iceTimeout);
|
|
145
|
-
this.initPeer(data.iceServers, resolve, reject);
|
|
146
|
-
})
|
|
147
|
-
.catch(err => {
|
|
148
|
-
clearTimeout(iceTimeout);
|
|
149
|
-
console.warn(`[${timestamp}] ⚠️ Failed to fetch ICE servers (using fallback):`, err.message);
|
|
150
|
-
// Fallback to Google STUN
|
|
151
|
-
this.initPeer([{ urls: "stun:stun.l.google.com:19302" }], resolve, reject);
|
|
152
|
-
});
|
|
153
|
-
} catch (error) {
|
|
154
|
-
console.error(`[${timestamp}] ❌ Initialization error:`, error);
|
|
155
|
-
reject(error);
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
private initPeer(iceServers: any[], resolve: any, reject: any) {
|
|
161
|
-
const timestamp = new Date().toLocaleTimeString();
|
|
162
|
-
|
|
163
|
-
// Ensure iceServers is an array
|
|
164
|
-
const validIceServers = Array.isArray(iceServers) ? iceServers : [{ urls: "stun:stun.l.google.com:19302" }];
|
|
165
|
-
|
|
166
|
-
if (this.debug) {
|
|
167
|
-
console.log(`[${timestamp}] 🧊 Using ICE servers:`, validIceServers.length);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const rtcConfig = {
|
|
171
|
-
iceServers: validIceServers,
|
|
172
|
-
iceCandidatePoolSize: 10,
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
try {
|
|
176
|
-
this.peer = new SimplePeer({
|
|
177
|
-
initiator: false,
|
|
178
|
-
trickle: true,
|
|
179
|
-
wrtc: wrtc,
|
|
180
|
-
config: rtcConfig
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
this.setupPeerListeners(resolve, reject);
|
|
184
|
-
console.log(`[${timestamp}] ⌛ Waiting for mobile device to initiate connection...`);
|
|
185
|
-
this.startPollingForOffer();
|
|
186
|
-
} catch (err: any) {
|
|
187
|
-
console.error(`[${timestamp}] ❌ Failed to create SimplePeer:`, err);
|
|
188
|
-
reject(err);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
private setupPeerListeners(resolve: any, reject: any) {
|
|
193
|
-
if (!this.peer) return;
|
|
194
|
-
|
|
195
|
-
// Monitor ICE connection state
|
|
196
|
-
this.peer.on('iceConnectionStateChange', () => {
|
|
197
|
-
const state = (this.peer as any)._pc?.iceConnectionState;
|
|
198
|
-
const timestamp = new Date().toLocaleTimeString();
|
|
199
|
-
if (this.debug) console.log(`[${timestamp}] 🔍 ICE Connection State: ${state}`);
|
|
200
|
-
|
|
201
|
-
if (state === 'connected' || state === 'completed') {
|
|
202
|
-
const pc = (this.peer as any)._pc;
|
|
203
|
-
if (pc) {
|
|
204
|
-
pc.getStats().then((stats: any) => {
|
|
205
|
-
let usingRelay = false;
|
|
206
|
-
stats.forEach((report: any) => {
|
|
207
|
-
if (report.type === 'candidate-pair' && report.selected) {
|
|
208
|
-
const localCandidate = stats.get(report.localCandidateId);
|
|
209
|
-
if (localCandidate && localCandidate.candidateType === 'relay') usingRelay = true;
|
|
210
|
-
}
|
|
211
|
-
});
|
|
212
|
-
if (usingRelay) console.log(`[${timestamp}] 🌐 TURN relay is being used (IP leak protected)`);
|
|
213
|
-
else console.log(`[${timestamp}] ✅ Direct P2P connection established`);
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
} else if (state === 'failed' || state === 'disconnected') {
|
|
217
|
-
if (state === 'failed') console.error(`[${timestamp}] ❌ ICE connection failed`);
|
|
218
|
-
this.isConnected = false;
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
this.peer.on('signal', async (signal: any) => {
|
|
223
|
-
// Send answer and trickle ICE candidates
|
|
224
|
-
await this.sendAnswerWithRetry(signal);
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
this.peer.on('connect', () => {
|
|
228
|
-
const timestamp = new Date().toLocaleTimeString();
|
|
229
|
-
console.log(`\n[${timestamp}] ✨ [ONLINE] Connected to mobile device!`);
|
|
230
|
-
this.isConnected = true;
|
|
231
|
-
|
|
232
|
-
// Send immediate handshake to update UI
|
|
233
|
-
this.send({ type: 'handshake', status: 'ready', timestamp: Date.now() });
|
|
234
|
-
|
|
235
|
-
if (this.onConnectCallback) this.onConnectCallback();
|
|
236
|
-
resolve();
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
this.peer.on('data', (data: any) => {
|
|
240
|
-
try {
|
|
241
|
-
const message = JSON.parse(data.toString());
|
|
242
|
-
this.handleIncomingMessage(message);
|
|
243
|
-
} catch (e) {
|
|
244
|
-
// Silent fail for invalid JSON
|
|
245
|
-
}
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
this.peer.on('error', (err: any) => {
|
|
249
|
-
// Ignore routine errors during disconnect
|
|
250
|
-
if (err.code === 'ERR_DATA_CHANNEL') return;
|
|
251
|
-
console.error('❌ WebRTC error:', err.message || err);
|
|
252
|
-
this.isConnected = false;
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
this.peer.on('close', () => {
|
|
256
|
-
console.log('❌ Connection closed');
|
|
257
|
-
this.isConnected = false;
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
private handleIncomingMessage(message: any): void {
|
|
262
|
-
// Validate message structure
|
|
263
|
-
if (!message || typeof message !== 'object') {
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// Ping/Pong for Keepalive
|
|
268
|
-
if (message.type === 'ping') {
|
|
269
|
-
this.send({ type: 'pong', timestamp: Date.now() });
|
|
270
|
-
return;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Check for nonce to prevent replay attacks
|
|
274
|
-
if (message.nonce) {
|
|
275
|
-
if (this.messageNonce.has(message.nonce)) {
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
this.messageNonce.add(message.nonce);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
if (this.onMessageCallback) {
|
|
282
|
-
this.onMessageCallback(message);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
private async startPollingForOffer(): Promise<void> {
|
|
287
|
-
let pollCount = 0;
|
|
288
|
-
const poll = async () => {
|
|
289
|
-
try {
|
|
290
|
-
// Stop if peer is connected or destroyed
|
|
291
|
-
if (this.peer && (this.peer as any).connected) {
|
|
292
|
-
if (this.pollingInterval) {
|
|
293
|
-
clearInterval(this.pollingInterval);
|
|
294
|
-
this.pollingInterval = undefined;
|
|
295
|
-
}
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
pollCount++;
|
|
300
|
-
const response = await fetch(`${this.signalingUrl}/signaling/poll?code=${this.code}`);
|
|
301
|
-
|
|
302
|
-
if (response.ok) {
|
|
303
|
-
const data = await response.json() as { signal?: any };
|
|
304
|
-
if (data.signal && this.peer) {
|
|
305
|
-
// console.log(`[Desktop] Received signal`);
|
|
306
|
-
this.peer.signal(data.signal);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
} catch (error) {
|
|
310
|
-
// Ignore polling errors
|
|
311
|
-
}
|
|
312
|
-
};
|
|
313
|
-
|
|
314
|
-
// Poll every 1 second
|
|
315
|
-
this.pollingInterval = setInterval(poll, 1000);
|
|
316
|
-
poll();
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
send(message: any): void {
|
|
320
|
-
if (this.peer && this.isConnected) {
|
|
321
|
-
try {
|
|
322
|
-
this.peer.send(JSON.stringify(message));
|
|
323
|
-
} catch (error) {
|
|
324
|
-
// Ignore send errors
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
onMessage(callback: (message: any) => void): void {
|
|
330
|
-
this.onMessageCallback = callback;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
onConnect(callback: () => void): void {
|
|
334
|
-
this.onConnectCallback = callback;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
onDisconnect(callback: () => void): void {
|
|
338
|
-
this.onDisconnectCallback = callback;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
disconnect(): void {
|
|
342
|
-
if (this.pollingInterval) {
|
|
343
|
-
clearInterval(this.pollingInterval);
|
|
344
|
-
}
|
|
345
|
-
if (this.cleanupInterval) {
|
|
346
|
-
clearInterval(this.cleanupInterval);
|
|
347
|
-
}
|
|
348
|
-
if (this.peer) {
|
|
349
|
-
this.peer.removeAllListeners();
|
|
350
|
-
this.peer.destroy();
|
|
351
|
-
this.peer = null;
|
|
352
|
-
}
|
|
353
|
-
this.isConnected = false;
|
|
354
|
-
this.messageNonce.clear();
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// Cleanup method for proper resource disposal
|
|
358
|
-
destroy(): void {
|
|
359
|
-
this.disconnect();
|
|
360
|
-
this.onMessageCallback = undefined;
|
|
361
|
-
this.onConnectCallback = undefined;
|
|
362
|
-
this.onDisconnectCallback = undefined;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
getConnected(): boolean {
|
|
366
|
-
return this.isConnected;
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|