UNPKG

webrtc2-peer

Version:

WebRTC2 Peer - Low-level WebRTC peer connection management for cross-platform real-time communication with signaling, ICE handling, and media streaming

163 lines (138 loc) 4.41 kB
/** * SignalingClient - WebSocket signaling client */ import { EventEmitter } from 'eventemitter3'; export interface SignalingMessage { type: 'offer' | 'answer' | 'ice-candidate' | 'join-room' | 'leave-room' | 'user-joined' | 'user-left'; data?: any; roomId?: string; userId?: string; } export interface SignalingClientEvents { 'connected': () => void; 'disconnected': () => void; 'message': (message: SignalingMessage) => void; 'error': (error: Error) => void; } export class SignalingClient extends EventEmitter<SignalingClientEvents> { private ws?: WebSocket; private url: string; private reconnectAttempts: number = 0; private maxReconnectAttempts: number = 5; private reconnectDelay: number = 1000; private isConnecting: boolean = false; constructor(url: string, options?: { maxReconnectAttempts?: number; reconnectDelay?: number; }) { super(); this.url = url; if (options?.maxReconnectAttempts) { this.maxReconnectAttempts = options.maxReconnectAttempts; } if (options?.reconnectDelay) { this.reconnectDelay = options.reconnectDelay; } } connect(): Promise<void> { return new Promise((resolve, reject) => { if (this.isConnecting || this.isConnected()) { resolve(); return; } this.isConnecting = true; this.ws = new WebSocket(this.url); this.ws.onopen = () => { this.isConnecting = false; this.reconnectAttempts = 0; this.emit('connected'); resolve(); }; this.ws.onclose = () => { this.isConnecting = false; this.emit('disconnected'); this.attemptReconnect(); }; this.ws.onerror = (event) => { this.isConnecting = false; const error = new Error('WebSocket connection error'); this.emit('error', error); reject(error); }; this.ws.onmessage = (event) => { try { const message: SignalingMessage = JSON.parse(event.data); this.emit('message', message); } catch (error) { this.emit('error', new Error('Failed to parse message')); } }; }); } private attemptReconnect(): void { if (this.reconnectAttempts >= this.maxReconnectAttempts) { this.emit('error', new Error('Max reconnection attempts reached')); return; } this.reconnectAttempts++; setTimeout(() => { this.connect().catch(() => { // Reconnection failed, will try again }); }, this.reconnectDelay * this.reconnectAttempts); } send(message: SignalingMessage): void { if (!this.isConnected()) { throw new Error('WebSocket is not connected'); } this.ws!.send(JSON.stringify(message)); } joinRoom(roomId: string, userId: string): void { this.send({ type: 'join-room', roomId, userId }); } leaveRoom(roomId: string, userId: string): void { this.send({ type: 'leave-room', roomId, userId }); } sendOffer(offer: RTCSessionDescriptionInit, roomId: string, userId: string): void { this.send({ type: 'offer', data: offer, roomId, userId }); } sendAnswer(answer: RTCSessionDescriptionInit, roomId: string, userId: string): void { this.send({ type: 'answer', data: answer, roomId, userId }); } sendIceCandidate(candidate: RTCIceCandidate, roomId: string, userId: string): void { this.send({ type: 'ice-candidate', data: candidate, roomId, userId }); } isConnected(): boolean { return this.ws?.readyState === WebSocket.OPEN; } disconnect(): void { if (this.ws) { this.ws.close(); this.ws = undefined; } this.removeAllListeners(); } }