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
text/typescript
/**
* 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();
}
}