mobilecoder-mcp 1.0.4 → 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 +5 -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 -10
- package/dist/webrtc.d.ts.map +1 -1
- package/dist/webrtc.js +96 -305
- 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 -387
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,387 +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
|
-
try {
|
|
9
|
-
// Önce @roamhq/wrtc dene, sonra wrtc
|
|
10
|
-
wrtc = require('@roamhq/wrtc');
|
|
11
|
-
} catch (e) {
|
|
12
|
-
try {
|
|
13
|
-
wrtc = require('wrtc');
|
|
14
|
-
} catch (e2) {
|
|
15
|
-
console.warn('⚠️ wrtc module not available, WebRTC may not work in Node.js environment');
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Simple HMAC implementation for Node.js
|
|
20
|
-
function generateHMAC(data: string, secret: string): string {
|
|
21
|
-
return createHash('sha256').update(data + secret).digest('hex');
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Validate SDP for security
|
|
25
|
-
function validateSDP(sdp: string): boolean {
|
|
26
|
-
if (!sdp || typeof sdp !== 'string') return false;
|
|
27
|
-
|
|
28
|
-
// Check for DTLS fingerprint (critical for encryption)
|
|
29
|
-
if (!sdp.includes('a=fingerprint:')) {
|
|
30
|
-
console.error('❌ [MCP] Invalid SDP: missing DTLS fingerprint');
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Check for DTLS setup
|
|
35
|
-
if (!sdp.includes('a=setup:')) {
|
|
36
|
-
console.error('❌ [MCP] Invalid SDP: missing DTLS setup');
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return true;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export class WebRTCConnection {
|
|
44
|
-
private peer: SimplePeer.Instance | null = null;
|
|
45
|
-
private code: string;
|
|
46
|
-
private signalingUrl: string;
|
|
47
|
-
private isConnected: boolean = false;
|
|
48
|
-
private pollingInterval?: NodeJS.Timeout;
|
|
49
|
-
private onMessageCallback?: (message: any) => void;
|
|
50
|
-
private onConnectCallback?: () => void;
|
|
51
|
-
private onDisconnectCallback?: () => void;
|
|
52
|
-
private debug: boolean;
|
|
53
|
-
private hmacSecret: string;
|
|
54
|
-
private cleanupInterval?: NodeJS.Timeout;
|
|
55
|
-
private messageNonce = new Set<string>();
|
|
56
|
-
|
|
57
|
-
constructor(code: string, signalingUrl: string, debug: boolean = false) {
|
|
58
|
-
this.code = code;
|
|
59
|
-
this.signalingUrl = signalingUrl;
|
|
60
|
-
this.debug = debug;
|
|
61
|
-
|
|
62
|
-
// Generate HMAC secret for this session
|
|
63
|
-
this.hmacSecret = createHash('sha256')
|
|
64
|
-
.update(code + Date.now() + Math.random())
|
|
65
|
-
.digest('hex');
|
|
66
|
-
|
|
67
|
-
// Start cleanup interval
|
|
68
|
-
this.startCleanup();
|
|
69
|
-
|
|
70
|
-
// Validate signaling URL
|
|
71
|
-
try {
|
|
72
|
-
const url = new URL(signalingUrl);
|
|
73
|
-
if (!url.protocol.startsWith('https') && url.hostname !== 'localhost' && url.hostname !== '127.0.0.1') {
|
|
74
|
-
console.warn('⚠️ [MCP] Warning: Signaling URL should use HTTPS in production');
|
|
75
|
-
}
|
|
76
|
-
} catch (e) {
|
|
77
|
-
console.error('❌ [MCP] Invalid signaling URL:', signalingUrl);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
private startCleanup(): void {
|
|
82
|
-
this.cleanupInterval = setInterval(() => {
|
|
83
|
-
// Clean up old nonces (prevent memory leak)
|
|
84
|
-
if (this.messageNonce.size > 1000) {
|
|
85
|
-
this.messageNonce.clear();
|
|
86
|
-
}
|
|
87
|
-
}, 60000); // Every minute
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
private async sendAnswerWithRetry(signal: any, attempts = 3): Promise<void> {
|
|
92
|
-
const timestamp = new Date().toLocaleTimeString();
|
|
93
|
-
const signalType = signal.type || (signal.candidate ? 'candidate' : 'answer');
|
|
94
|
-
|
|
95
|
-
// Use trickle endpoint for ICE candidates, standard answer endpoint for SDP answer
|
|
96
|
-
const endpoint = signal.candidate ? '/signaling/answer' : '/signaling/answer';
|
|
97
|
-
|
|
98
|
-
console.log(`[${timestamp}] 📤 [Desktop] Sending ${signalType} to server...`);
|
|
99
|
-
|
|
100
|
-
for (let i = 0; i < attempts; i++) {
|
|
101
|
-
try {
|
|
102
|
-
const response = await fetch(`${this.signalingUrl}${endpoint}`, {
|
|
103
|
-
method: 'POST',
|
|
104
|
-
headers: { 'Content-Type': 'application/json' },
|
|
105
|
-
body: JSON.stringify({ code: this.code, signal })
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
if (!response.ok) {
|
|
109
|
-
throw new Error(`Server returned ${response.status}`);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
console.log(`[${timestamp}] ✅ [Desktop] ${signalType} sent. Waiting for P2P handshake...`);
|
|
113
|
-
return;
|
|
114
|
-
} catch (error) {
|
|
115
|
-
console.error(`[${timestamp}] ❌ [Desktop] Failed to send ${signalType} (Attempt ${i + 1}/${attempts}):`, error);
|
|
116
|
-
if (i < attempts - 1) {
|
|
117
|
-
await new Promise(r => setTimeout(r, 1000)); // Wait 1s
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
console.error(`[${timestamp}] ❌ [Desktop] Failed to send ${signalType} after all attempts.`);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
async connect(): Promise<void> {
|
|
125
|
-
const timestamp = new Date().toLocaleTimeString();
|
|
126
|
-
console.log(`[${timestamp}] 📡 Connecting to signaling server: ${this.signalingUrl}`);
|
|
127
|
-
console.log(`[${timestamp}] 🔑 Using connection code: ${this.code}`);
|
|
128
|
-
|
|
129
|
-
return new Promise((resolve, reject) => {
|
|
130
|
-
try {
|
|
131
|
-
if (!wrtc) {
|
|
132
|
-
console.warn(`[${timestamp}] ⚠️ WebRTC initialized without Node.js native support (wrtc). This might fail.`);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Fetch dynamic ICE servers from signaling backend
|
|
136
|
-
fetch(`${this.signalingUrl}/signaling/ice-servers`)
|
|
137
|
-
.then(res => res.json())
|
|
138
|
-
.then((data: any) => {
|
|
139
|
-
const rtcConfig = {
|
|
140
|
-
iceServers: data.iceServers || [
|
|
141
|
-
{ urls: "stun:stun.l.google.com:19302" }
|
|
142
|
-
],
|
|
143
|
-
iceCandidatePoolSize: 10,
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
this.peer = new SimplePeer({
|
|
147
|
-
initiator: false,
|
|
148
|
-
trickle: true,
|
|
149
|
-
wrtc: wrtc,
|
|
150
|
-
config: rtcConfig
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
this.setupPeerListeners(resolve, reject);
|
|
154
|
-
})
|
|
155
|
-
.catch(err => {
|
|
156
|
-
console.error(`[${timestamp}] ❌ Failed to fetch ICE servers:`, err);
|
|
157
|
-
// Fallback to basic STUN
|
|
158
|
-
this.peer = new SimplePeer({
|
|
159
|
-
initiator: false,
|
|
160
|
-
trickle: true,
|
|
161
|
-
wrtc: wrtc,
|
|
162
|
-
config: { iceServers: [{ urls: "stun:stun.l.google.com:19302" }] }
|
|
163
|
-
});
|
|
164
|
-
this.setupPeerListeners(resolve, reject);
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
console.log(`[${timestamp}] ⌛ Waiting for mobile device to initiate connection...`);
|
|
168
|
-
// Start polling for offer from mobile
|
|
169
|
-
this.startPollingForOffer();
|
|
170
|
-
} catch (error) {
|
|
171
|
-
console.error(`[${timestamp}] ❌ Initialization error:`, error);
|
|
172
|
-
reject(error);
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
private setupPeerListeners(resolve: any, reject: any) {
|
|
178
|
-
if (!this.peer) return;
|
|
179
|
-
|
|
180
|
-
// Monitor ICE connection state
|
|
181
|
-
this.peer.on('iceConnectionStateChange', () => {
|
|
182
|
-
const state = (this.peer as any)._pc?.iceConnectionState;
|
|
183
|
-
const timestamp = new Date().toLocaleTimeString();
|
|
184
|
-
console.log(`[${timestamp}] 🔍 ICE Connection State: ${state}`);
|
|
185
|
-
|
|
186
|
-
if (state === 'connected' || state === 'completed') {
|
|
187
|
-
const pc = (this.peer as any)._pc;
|
|
188
|
-
if (pc) {
|
|
189
|
-
// Validate SDP for security
|
|
190
|
-
const remoteDescription = pc.remoteDescription;
|
|
191
|
-
if (remoteDescription && !validateSDP(remoteDescription.sdp)) {
|
|
192
|
-
console.error('❌ [MCP] Remote SDP validation failed');
|
|
193
|
-
this.disconnect();
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
pc.getStats().then((stats: any) => {
|
|
198
|
-
let usingRelay = false;
|
|
199
|
-
stats.forEach((report: any) => {
|
|
200
|
-
if (report.type === 'candidate-pair' && report.selected) {
|
|
201
|
-
const localCandidate = stats.get(report.localCandidateId);
|
|
202
|
-
if (localCandidate && localCandidate.candidateType === 'relay') usingRelay = true;
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
if (usingRelay) console.log(`[${timestamp}] 🌐 TURN relay is being used (IP leak protected)`);
|
|
206
|
-
else console.log(`[${timestamp}] ✅ Direct P2P connection established`);
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
} else if (state === 'failed' || state === 'disconnected') {
|
|
210
|
-
console.error(`[${timestamp}] ❌ ICE connection failed: ${state}`);
|
|
211
|
-
this.isConnected = false;
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
this.peer.on('signal', async (signal: any) => {
|
|
216
|
-
const timestamp = new Date().toLocaleTimeString();
|
|
217
|
-
const signalType = signal.type || (signal.candidate ? 'candidate' : 'unknown');
|
|
218
|
-
console.log(`[${timestamp}] 📡 [Desktop] Signal generated: ${signalType}`);
|
|
219
|
-
|
|
220
|
-
// Validate SDP before sending
|
|
221
|
-
if (signal.sdp && !validateSDP(signal.sdp)) {
|
|
222
|
-
console.error('❌ [MCP] Local SDP validation failed, not sending signal');
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Send answer and trickle ICE candidates
|
|
227
|
-
await this.sendAnswerWithRetry(signal);
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
this.peer.on('connect', () => {
|
|
231
|
-
const timestamp = new Date().toLocaleTimeString();
|
|
232
|
-
console.log(`\n[${timestamp}] ✨ [ONLINE] Connected to mobile device!`);
|
|
233
|
-
this.isConnected = true;
|
|
234
|
-
if (this.onConnectCallback) this.onConnectCallback();
|
|
235
|
-
resolve();
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
this.peer.on('data', (data: any) => {
|
|
239
|
-
try {
|
|
240
|
-
const message = JSON.parse(data.toString());
|
|
241
|
-
this.handleIncomingMessage(message);
|
|
242
|
-
} catch (e) {
|
|
243
|
-
// Silent fail for invalid JSON
|
|
244
|
-
}
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
this.peer.on('error', (err: any) => {
|
|
248
|
-
console.error('❌ WebRTC error:', err);
|
|
249
|
-
this.isConnected = false;
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
this.peer.on('close', () => {
|
|
253
|
-
console.log('❌ Connection closed');
|
|
254
|
-
this.isConnected = false;
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
private handleIncomingMessage(message: any): void {
|
|
259
|
-
// Validate message structure
|
|
260
|
-
if (!message || typeof message !== 'object') {
|
|
261
|
-
console.warn('⚠️ [MCP] Invalid message structure');
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Check for nonce to prevent replay attacks
|
|
266
|
-
if (message.nonce) {
|
|
267
|
-
if (this.messageNonce.has(message.nonce)) {
|
|
268
|
-
console.warn('⚠️ [MCP] Duplicate message detected (replay attack)');
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
this.messageNonce.add(message.nonce);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Verify HMAC if present
|
|
275
|
-
if (message.hmac) {
|
|
276
|
-
const data = JSON.stringify({
|
|
277
|
-
type: message.type,
|
|
278
|
-
text: message.text,
|
|
279
|
-
command: message.command,
|
|
280
|
-
timestamp: message.timestamp,
|
|
281
|
-
nonce: message.nonce
|
|
282
|
-
});
|
|
283
|
-
const expectedHMAC = generateHMAC(data, this.hmacSecret);
|
|
284
|
-
|
|
285
|
-
if (message.hmac !== expectedHMAC) {
|
|
286
|
-
console.error('❌ [MCP] HMAC verification failed');
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
if (this.onMessageCallback) {
|
|
292
|
-
this.onMessageCallback(message);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
private async startPollingForOffer(): Promise<void> {
|
|
297
|
-
let pollCount = 0;
|
|
298
|
-
const poll = async () => {
|
|
299
|
-
try {
|
|
300
|
-
// Stop if peer is connected or destroyed
|
|
301
|
-
if (this.peer && (this.peer as any).connected) {
|
|
302
|
-
if (this.pollingInterval) {
|
|
303
|
-
clearInterval(this.pollingInterval);
|
|
304
|
-
this.pollingInterval = undefined;
|
|
305
|
-
}
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
pollCount++;
|
|
310
|
-
const response = await fetch(`${this.signalingUrl}/signaling/poll?code=${this.code}`);
|
|
311
|
-
const timestamp = new Date().toLocaleTimeString();
|
|
312
|
-
|
|
313
|
-
if (response.ok) {
|
|
314
|
-
const data = await response.json() as { signal?: any };
|
|
315
|
-
if (data.signal && this.peer) {
|
|
316
|
-
console.log(`[${timestamp}] 📥 Received signal from mobile device, applying...`);
|
|
317
|
-
this.peer.signal(data.signal);
|
|
318
|
-
// Don't clear interval, keep polling for trickle candidates
|
|
319
|
-
} else if (this.debug && pollCount % 10 === 0) {
|
|
320
|
-
console.log(`[${timestamp}] ⌛ Still waiting for signals (Poll #${pollCount})...`);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
} catch (error) {
|
|
324
|
-
if (this.debug) {
|
|
325
|
-
console.error('❌ Error during polling:', error);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
};
|
|
329
|
-
|
|
330
|
-
// Poll every 1.5 seconds
|
|
331
|
-
this.pollingInterval = setInterval(poll, 1500);
|
|
332
|
-
poll();
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
send(message: any): void {
|
|
336
|
-
if (this.peer && this.isConnected) {
|
|
337
|
-
try {
|
|
338
|
-
this.peer.send(JSON.stringify(message));
|
|
339
|
-
} catch (error) {
|
|
340
|
-
console.error('Failed to send message:', error);
|
|
341
|
-
}
|
|
342
|
-
} else {
|
|
343
|
-
console.warn('Cannot send message: not connected');
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
onMessage(callback: (message: any) => void): void {
|
|
348
|
-
this.onMessageCallback = callback;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
onConnect(callback: () => void): void {
|
|
352
|
-
this.onConnectCallback = callback;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
onDisconnect(callback: () => void): void {
|
|
356
|
-
this.onDisconnectCallback = callback;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
disconnect(): void {
|
|
360
|
-
if (this.pollingInterval) {
|
|
361
|
-
clearInterval(this.pollingInterval);
|
|
362
|
-
}
|
|
363
|
-
if (this.cleanupInterval) {
|
|
364
|
-
clearInterval(this.cleanupInterval);
|
|
365
|
-
}
|
|
366
|
-
if (this.peer) {
|
|
367
|
-
this.peer.removeAllListeners();
|
|
368
|
-
this.peer.destroy();
|
|
369
|
-
this.peer = null;
|
|
370
|
-
}
|
|
371
|
-
this.isConnected = false;
|
|
372
|
-
this.messageNonce.clear();
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// Cleanup method for proper resource disposal
|
|
376
|
-
destroy(): void {
|
|
377
|
-
this.disconnect();
|
|
378
|
-
this.onMessageCallback = undefined;
|
|
379
|
-
this.onConnectCallback = undefined;
|
|
380
|
-
this.onDisconnectCallback = undefined;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
getConnected(): boolean {
|
|
384
|
-
return this.isConnected;
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|