diff --git a/src/FishjamClient.ts b/src/FishjamClient.ts index 805667a..74be763 100644 --- a/src/FishjamClient.ts +++ b/src/FishjamClient.ts @@ -15,6 +15,7 @@ import { EventEmitter } from 'events'; import { PeerMessage } from './protos'; import { ReconnectConfig, ReconnectManager } from './reconnection'; import { AuthErrorReason, isAuthError } from './auth'; +import { Deferred } from './webrtc/deferred'; export type Peer = Endpoint< PeerMetadata, @@ -74,6 +75,15 @@ export interface MessageEvents { /** Emitted when the connection is closed */ disconnected: () => void; + /** Emitted when the process of reconnection starts */ + reconnectionStarted: () => void; + + /** Emitted on successful reconnection */ + reconnected: () => void; + + /** Emitted when the maximum number of reconnection retries is reached */ + reconnectionRetriesLimitReached: () => void; + /** * Called when peer was accepted. */ @@ -387,7 +397,7 @@ export class FishjamClient< this.initConnection(config.peerMetadata); } - private initConnection(peerMetadata: PeerMetadata): void { + private async initConnection(peerMetadata: PeerMetadata): Promise { if (this.status === 'initialized') { this.disconnect(); } @@ -558,7 +568,7 @@ export class FishjamClient< this.webrtc?.on( 'connected', - ( + async ( peerId: string, endpointsInRoom: Endpoint[], ) => { @@ -572,6 +582,8 @@ export class FishjamClient< (component) => component as Component, ); + await this.reconnectManager.handleReconnect(); + this.emit('joined', peerId, peers, components); }, ); @@ -1033,6 +1045,10 @@ export class FishjamClient< this.webrtc.updateTrackMetadata(trackId, trackMetadata); }; + public isReconnecting() { + return this.reconnectManager.isReconnecting(); + } + /** * Leaves the room. This function should be called when user leaves the room in a clean way e.g. by clicking a * dedicated, custom button `disconnect`. As a result there will be generated one more media event that should be sent diff --git a/src/auth.ts b/src/auth.ts index 9d906a7..d53ee4f 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -4,7 +4,6 @@ export const AUTH_ERROR_REASONS = [ 'expired token', 'room not found', 'peer not found', - 'peer already connected', ] as const; export type AuthErrorReason = (typeof AUTH_ERROR_REASONS)[number]; diff --git a/src/index.ts b/src/index.ts index 0cf6701..787efb0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,7 +7,7 @@ export type { SignalingUrl, } from './FishjamClient'; -export type { ReconnectConfig } from './reconnection'; +export type { ReconnectConfig, ReconnectionStatus } from './reconnection'; export type { AuthErrorReason } from './auth.js'; diff --git a/src/reconnection.ts b/src/reconnection.ts index f805a73..ec8f3fe 100644 --- a/src/reconnection.ts +++ b/src/reconnection.ts @@ -2,6 +2,8 @@ import { Endpoint } from './webrtc'; import { FishjamClient, MessageEvents } from './FishjamClient'; import { isAuthError } from './auth'; +export type ReconnectionStatus = 'reconnecting' | 'idle' | 'error'; + export type ReconnectConfig = { /* + default: 3 @@ -47,8 +49,7 @@ export class ReconnectManager { private reconnectAttempt: number = 0; private reconnectTimeoutId: NodeJS.Timeout | null = null; - private reconnectFailedNotificationSend: boolean = false; - private ongoingReconnection: boolean = false; + private status: ReconnectionStatus = 'idle'; private lastLocalEndpoint: Endpoint | null = null; private removeEventListeners: () => void = () => {}; @@ -96,23 +97,18 @@ export class ReconnectManager { }; this.client.on('authSuccess', onAuthSuccess); - const onJoined: MessageEvents< - PeerMetadata, - TrackMetadata - >['joined'] = () => { - this.handleReconnect(); - }; - this.client.on('joined', onJoined); - this.removeEventListeners = () => { this.client.off('socketError', onSocketError); this.client.off('connectionError', onConnectionError); this.client.off('socketClose', onSocketClose); this.client.off('authSuccess', onAuthSuccess); - this.client.off('joined', onJoined); }; } + public isReconnecting(): boolean { + return this.status === 'reconnecting'; + } + public reset(initialMetadata: PeerMetadata) { this.initialMetadata = initialMetadata; this.reconnectAttempt = 0; @@ -128,14 +124,19 @@ export class ReconnectManager { if (this.reconnectTimeoutId) return; if (this.reconnectAttempt >= this.reconnectConfig.maxAttempts) { - if (!this.reconnectFailedNotificationSend) { - this.reconnectFailedNotificationSend = true; + if (this.status === 'reconnecting') { + this.status = 'error'; + + this.client.emit('reconnectionRetriesLimitReached'); } return; } - if (!this.ongoingReconnection) { - this.ongoingReconnection = true; + if (this.status !== 'reconnecting') { + this.status = 'reconnecting'; + + this.client.emit('reconnectionStarted'); + this.lastLocalEndpoint = this.client.getLocalEndpoint() || null; } @@ -152,11 +153,12 @@ export class ReconnectManager { }, timeout); } - private handleReconnect() { - if (!this.ongoingReconnection) return; + public async handleReconnect() { + if (this.status !== 'reconnecting') return; if (this.lastLocalEndpoint && this.reconnectConfig.addTracksOnReconnect) { - this.lastLocalEndpoint.tracks.forEach(async (track) => { + for await (const element of this.lastLocalEndpoint.tracks) { + const [_, track] = element; if (!track.track || track.track.readyState !== 'live') return; await this.client.addTrack( @@ -165,11 +167,13 @@ export class ReconnectManager { track.simulcastConfig, track.maxBandwidth, ); - }); + } } this.lastLocalEndpoint = null; - this.ongoingReconnection = false; + this.status = 'idle'; + + this.client.emit('reconnected'); } public cleanup() {