Skip to content

Commit

Permalink
Improve reconnection under bad network conditions (#811)
Browse files Browse the repository at this point in the history
* prefer timer instead of boolean

* reattach logs to trace level

* reduce logs

* reset socket status on reconnect

* hangup the connection after detach timeout

* add leaveReason to BaseConnection

* set leaveReason on timeout

* update types for room.left

* add room.left listener in heroku demo

* changeset

* RESUME_TIMEOUT to 12s

* restore commented code
  • Loading branch information
Edoardo Gallo authored Jun 21, 2023
1 parent 29acde3 commit f3711f1
Show file tree
Hide file tree
Showing 13 changed files with 54 additions and 15 deletions.
7 changes: 7 additions & 0 deletions .changeset/honest-tigers-promise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@signalwire/webrtc': patch
'@signalwire/core': patch
'@signalwire/js': patch
---

Improve reconnection under bad network conditions.
3 changes: 3 additions & 0 deletions internal/playground-js/src/heroku/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,9 @@ window.connect = () => {
console.debug('>> destroy')
restoreUI()
})
roomObj.on('room.left', (payload) => {
console.debug('>> room.left', payload)
})
roomObj.on('room.updated', (params) =>
console.debug('>> room.updated', params)
)
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/BaseSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,8 @@ export class BaseSession {
protected async _onSocketOpen(event: Event) {
this.logger.debug('_onSocketOpen', event.type)
try {
// Reset to "unknown" in case of reconnect
this._status = 'unknown'
this._clearTimers()
await this.authenticate()
this._status = 'connected'
Expand Down Expand Up @@ -399,7 +401,6 @@ export class BaseSession {
if (this._status !== 'disconnected') {
this._status = 'reconnecting'
this.dispatch(sessionReconnectingAction())
// yield put(pubSubChannel, sessionReconnectingAction())
this._clearTimers()
this._clearPendingRequests()
this._reconnectTimer = setTimeout(() => {
Expand Down Expand Up @@ -588,6 +589,7 @@ export class BaseSession {
status === 'disconnected' ? 'unauthorized' : 'unknown'
)
)
this._removeSocketListeners()
this.destroySocket()
this._checkCurrentStatus()
}
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/redux/rootSaga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ export function* sessionStatusWatcher(options: StartSagaOptions): SagaIterator {
sessionForceCloseAction.type,
])

getLogger().debug('sessionStatusWatcher', action.type, action.payload)
getLogger().trace('sessionStatusWatcher', action.type, action.payload)
switch (action.type) {
case authSuccessAction.type: {
const { session, pubSubChannel } = options
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ export interface BaseConnectionContract<
/** @internal The BaseConnection options */
readonly options: Record<any, any>

/** @internal */
readonly leaveReason: 'RECONNECTION_ATTEMPT_TIMEOUT' | undefined

/** The id of the video device, or null if not available */
readonly cameraId: string | null
/** The label of the video device, or null if not available */
Expand Down
6 changes: 5 additions & 1 deletion packages/core/src/types/videoRoomSession.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { SwEvent } from '.'
import type { BaseConnectionContract, SwEvent } from '.'
import type {
CamelToSnakeCase,
EntityUpdated,
Expand Down Expand Up @@ -26,6 +26,10 @@ export type RoomJoined = 'room.joined'
export type RoomLeft = 'room.left'
export type RoomAudienceCount = 'room.audienceCount'

export type RoomLeftEventParams = {
reason?: BaseConnectionContract<any>['leaveReason']
}

export type VideoRoomAudienceCountEventNames = ToInternalVideoEvent<
InternalRoomAudienceCount | RoomAudienceCount
>
Expand Down
8 changes: 4 additions & 4 deletions packages/js/src/JWTSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export class JWTSession extends BaseJWTSession {

const key = this.getProtocolSessionStorageKey()
if (key) {
this.logger.info('Hijacking: search protocol for', key)
this.logger.trace('Hijacking: search protocol for', key)
return getStorage()?.getItem(key) ?? ''
}
return ''
Expand All @@ -53,7 +53,7 @@ export class JWTSession extends BaseJWTSession {

const key = this.getProtocolSessionStorageKey()
if (key) {
this.logger.info('Hijacking: persist protocol', key, this.relayProtocol)
this.logger.trace('Hijacking: persist protocol', key, this.relayProtocol)
getStorage()?.setItem(key, this.relayProtocol)
}
}
Expand All @@ -75,14 +75,14 @@ export class JWTSession extends BaseJWTSession {

const key = this.getAuthStateSessionStorageKey()
if (key) {
this.logger.info('Hijacking: persist auth state', key, state)
this.logger.trace('Hijacking: persist auth state', key, state)
getStorage()?.setItem(key, state)
}
}

protected override _onSocketClose(event: CloseEvent) {
if (this.status === 'unknown') {
this.logger.info('Hijacking: invalid values - cleaning up storage')
this.logger.trace('Hijacking: invalid values - cleaning up storage')
const protocolKey = this.getProtocolSessionStorageKey()
if (protocolKey) {
getStorage()?.removeItem(protocolKey)
Expand Down
2 changes: 1 addition & 1 deletion packages/js/src/RoomSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ export const RoomSession = function (roomOptions: RoomSessionOptions) {
// WebRTC connection left the room.
room.once('destroy', () => {
// @ts-expect-error
room.emit('room.left')
room.emit('room.left', { reason: room.leaveReason })

// Remove callId to reattach
reattachManager.destroy()
Expand Down
3 changes: 2 additions & 1 deletion packages/js/src/RoomSessionDevice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
BaseConnectionContract,
BaseConnectionState,
RoomLeft,
RoomLeftEventParams,
} from '@signalwire/core'
import { BaseConnection, MediaEvent } from '@signalwire/webrtc'
import { RoomSessionDeviceMethods } from './utils/interfaces'
Expand All @@ -12,7 +13,7 @@ type RoomSessionDeviceEventsHandlerMap = Record<
BaseConnectionState,
(params: RoomSessionDevice) => void
> &
Record<RoomLeft, (params: void) => void> &
Record<RoomLeft, (params?: RoomLeftEventParams) => void> &
Record<MediaEvent, () => void>

export type RoomSessionDeviceEvents = {
Expand Down
3 changes: 2 additions & 1 deletion packages/js/src/RoomSessionScreenShare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
BaseConnectionContract,
BaseConnectionState,
RoomLeft,
RoomLeftEventParams,
} from '@signalwire/core'
import { BaseConnection, MediaEvent } from '@signalwire/webrtc'
import { RoomScreenShareMethods } from './utils/interfaces'
Expand All @@ -12,7 +13,7 @@ type RoomSessionScreenShareEventsHandlerMap = Record<
BaseConnectionState,
(params: RoomSessionScreenShare) => void
> &
Record<RoomLeft, (params: void) => void> &
Record<RoomLeft, (params?: RoomLeftEventParams) => void> &
Record<MediaEvent, () => void>

export type RoomSessionScreenShareEvents = {
Expand Down
3 changes: 2 additions & 1 deletion packages/js/src/utils/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import type {
RoomAudienceCount,
VideoRoomAudienceCountEventParams,
RoomLeft,
RoomLeftEventParams,
VideoStreamEventNames,
RoomSessionStream,
RoomJoined,
Expand Down Expand Up @@ -121,7 +122,7 @@ export type RoomSessionObjectEventsHandlerMap = Record<
RoomJoined | RoomSubscribed,
(params: VideoRoomSubscribedEventParams) => void
> &
Record<RoomLeft, (params: void) => void> &
Record<RoomLeft, (params?: RoomLeftEventParams) => void> &
Record<MediaEvent, () => void> &
Record<
RoomAudienceCount,
Expand Down
3 changes: 3 additions & 0 deletions packages/webrtc/src/BaseConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ export class BaseConnection<EventTypes extends EventEmitter.ValidEventTypes>
EventTypes & BaseConnectionStateEventTypes
>
/** @internal */
public leaveReason: BaseConnectionContract<EventTypes>['leaveReason'] =
undefined
/** @internal */
public cause: string
/** @internal */
public causeCode: string
Expand Down
22 changes: 18 additions & 4 deletions packages/webrtc/src/RTCPeer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
import { ConnectionOptions } from './utils/interfaces'
import { watchRTCPeerMediaPackets } from './utils/watchRTCPeerMediaPackets'

const RESUME_TIMEOUT = 12_000

export default class RTCPeer<EventTypes extends EventEmitter.ValidEventTypes> {
public uuid = uuid()

Expand All @@ -25,10 +27,10 @@ export default class RTCPeer<EventTypes extends EventEmitter.ValidEventTypes> {
private _iceTimeout: any
private _negotiating = false
private _processingRemoteSDP = false
private needResume = false
private _restartingIce = false
private _watchMediaPacketsTimer: ReturnType<typeof setTimeout>
private _connectionStateTimer: ReturnType<typeof setTimeout>
private _resumeTimer?: ReturnType<typeof setTimeout>
private _mediaWatcher: ReturnType<typeof watchRTCPeerMediaPackets>
/**
* Both of these properties are used to have granular
Expand Down Expand Up @@ -279,7 +281,7 @@ export default class RTCPeer<EventTypes extends EventEmitter.ValidEventTypes> {

triggerResume() {
this.logger.info('Probably half-open so force close from client')
if (this.needResume) {
if (this._resumeTimer) {
this.logger.info('[skipped] Already in "resume" state')
return
}
Expand All @@ -289,12 +291,18 @@ export default class RTCPeer<EventTypes extends EventEmitter.ValidEventTypes> {
// @ts-expect-error
this.call.emit('media.reconnecting')
this.clearTimers()
this.needResume = true
this._resumeTimer = setTimeout(() => {
this.logger.warn('Disconnecting due to RECONNECTION_ATTEMPT_TIMEOUT')
// @ts-expect-error
this.call.emit('media.disconnected')
this.call.leaveReason = 'RECONNECTION_ATTEMPT_TIMEOUT'
this.call.setState('hangup')
}, RESUME_TIMEOUT) // TODO: read from call verto.invite response
this.call._closeWSConnection()
}

private resetNeedResume() {
this.needResume = false
this.clearResumeTimer()
if (this.options.watchMediaPackets) {
this.startWatchMediaPackets()
}
Expand Down Expand Up @@ -892,6 +900,7 @@ export default class RTCPeer<EventTypes extends EventEmitter.ValidEventTypes> {
}

private clearTimers() {
this.clearResumeTimer()
this.clearWatchMediaPacketsTimer()
this.clearConnectionStateTimer()
}
Expand All @@ -904,6 +913,11 @@ export default class RTCPeer<EventTypes extends EventEmitter.ValidEventTypes> {
clearTimeout(this._watchMediaPacketsTimer)
}

private clearResumeTimer() {
clearTimeout(this._resumeTimer)
this._resumeTimer = undefined
}

private emitMediaConnected() {
// @ts-expect-error
this.call.emit('media.connected')
Expand Down

0 comments on commit f3711f1

Please sign in to comment.