import http from 'http'; import net from 'net'; import { EventEmitter } from 'events'; import { FrameParser, encodeMessage } from '@network-tool/shared-protocol'; import type { NetworkMessage } from '@network-tool/shared-types'; const SERVER_URL = process.env.SERVER_URL || 'http://localhost:3001'; export class NetworkClient extends EventEmitter { private token: string | null = null; private relaySocket: net.Socket | null = null; private relayParser = new FrameParser(); setToken(token: string): void { this.token = token; } /** * 向 Control Server 发送 HTTP 请求 */ apiRequest(path: string, method = 'GET', body?: any): Promise { return new Promise((resolve, reject) => { const url = new URL(path, SERVER_URL); const data = body ? JSON.stringify(body) : undefined; const options: http.RequestOptions = { hostname: url.hostname, port: url.port, path: url.pathname, method, headers: { 'Content-Type': 'application/json', ...(this.token ? { Authorization: `Bearer ${this.token}` } : {}), ...(data ? { 'Content-Length': Buffer.byteLength(data).toString() } : {}), }, }; const req = http.request(options, (res) => { let body = ''; res.on('data', (chunk) => (body += chunk)); res.on('end', () => { try { resolve(JSON.parse(body)); } catch { resolve({ success: false, error: 'PARSE_ERROR', message: body }); } }); }); req.on('error', (err) => { resolve({ success: false, error: 'NETWORK_ERROR', message: err.message }); }); if (data) req.write(data); req.end(); }); } /** * 带认证的请求 */ async authenticatedRequest(path: string, method = 'GET', body?: any): Promise { if (!this.token) { return { success: false, error: 'NOT_AUTHENTICATED', message: 'Please login first' }; } return this.apiRequest(path, method, body); } /** * 连接到 Relay Node */ connectToRelay(host: string, port: number, token: string, deviceId: number): Promise { return new Promise((resolve, reject) => { this.disconnectFromRelay(); this.relaySocket = new net.Socket(); this.relayParser.reset(); const timeout = setTimeout(() => { reject(new Error('Connection timeout')); this.relaySocket?.destroy(); }, 10000); this.relaySocket.connect(port, host, () => { clearTimeout(timeout); this.emit('status', 'connected'); console.log(`[Client] 已连接到 Relay Node: ${host}:${port}`); // 发送认证消息 const authMsg: NetworkMessage = { type: 'auth', from_device_id: deviceId, to_device_id: 0, payload: JSON.stringify({ action: 'connect', device_id: deviceId, token, }), timestamp: Date.now(), message_id: this.generateId(), }; this.relaySocket!.write(encodeMessage(authMsg)); this.emit('status', 'authenticating'); }); this.relaySocket.on('data', (chunk: Buffer) => { const messages = this.relayParser.push(chunk); for (const msg of messages) { if (msg.type === 'control') { try { const ctrl = JSON.parse(msg.payload); if (ctrl.action === 'connect_ok') { this.emit('status', 'authenticated'); resolve(); continue; } } catch {} } // 转发所有消息给渲染进程 this.emit('message', msg); } }); this.relaySocket.on('error', (err) => { clearTimeout(timeout); this.emit('status', 'error'); reject(err); }); this.relaySocket.on('close', () => { this.emit('status', 'disconnected'); this.relaySocket = null; }); }); } /** * 通过 Relay 发送消息 */ sendMessage(targetDeviceId: number, payload: string): void { if (!this.relaySocket || this.relaySocket.destroyed) { throw new Error('Not connected to relay'); } const msg: NetworkMessage = { type: 'data', from_device_id: 0, // 不暴露真实 device id 给对端 (relay 会处理) to_device_id: targetDeviceId, payload, timestamp: Date.now(), message_id: this.generateId(), }; this.relaySocket.write(encodeMessage(msg)); } /** * 断开 Relay 连接 */ disconnectFromRelay(): void { if (this.relaySocket) { this.relaySocket.destroy(); this.relaySocket = null; } this.emit('status', 'disconnected'); } private generateId(): string { return Date.now().toString(36) + Math.random().toString(36).substring(2, 8); } }