import { Socket as Engine , SocketOptions as EngineOptions , installTimerFunctions , } from "engine.io-client" ; import { Socket , SocketOptions } from "./socket.js" ; import * as parser from "socket.io-parser" ; import { Decoder , Encoder , Packet } from "socket.io-parser" ; import { on } from "./on.js" ; import Backoff from "backo2" ; import { DefaultEventsMap , EventsMap , Emitter , } from "@socket.io/component-emitter" ; import debugModule from "debug" ; const debug = debugModule ( "socket.io-client:manager" ) ; export interface ManagerOptions extends EngineOptions { default forceNew : boolean ; default multiplex : boolean ; default path : string ; default reconnection : boolean ; default reconnectionAttempts : number ; default reconnectionDelay : number ; default reconnectionDelayMax : number ; default randomizationFactor : number ; default timeout : number ; default autoConnect : boolean ; /**
     * the parser to use. Defaults to an instance of the Parser that ships with socket.io.
     */ parser : any ; } interface ManagerReservedEvents { open : ( ) => void ; error : ( err : Error ) => void ; ping : ( ) => void ; packet : ( packet : Packet ) => void ; close : ( reason : string ) => void ; reconnect_failed : ( ) => void ; reconnect_attempt : ( attempt : number ) => void ; reconnect_error : ( err : Error ) => void ; reconnect : ( attempt : number ) => void ; } export class Manager < ListenEvents extends EventsMap = DefaultEventsMap , EmitEvents extends EventsMap = ListenEvents > extends Emitter < { } , { } , ManagerReservedEvents > { public public engine : Engine ; private _autoConnect : boolean ; private _readyState : "opening" | "open" | "closed" ; private _reconnecting : boolean ; private readonly uri : string ; public opts : Partial < ManagerOptions > ; private nsps : Record < string , Socket > = { } ; private subs : Array < ReturnType < typeof on > > = [ ] ; private backoff : Backoff ; private setTimeoutFn : typeof setTimeout ; private _reconnection : boolean ; private _reconnectionAttempts : number ; private _reconnectionDelay : number ; private _randomizationFactor : number ; private _reconnectionDelayMax : number ; private _timeout : any ; private encoder : Encoder ; private decoder : Decoder ; private skipReconnect : boolean ; param uri param opts public constructor ( opts ) ; constructor ( uri ? , opts ? ) ; constructor ( uri ? , opts ? ) ; constructor ( uri ? , opts ? ) { super ( ) ; if ( uri && "object" === typeof uri ) { opts = uri ; uri = undefined ; } opts = opts || { } ; opts . path = opts . path || "/socket.io" ; this . opts = opts ; installTimerFunctions ( this , opts ) ; this . reconnection ( opts . reconnection !== false ) ; this . reconnectionAttempts ( opts . reconnectionAttempts || Infinity ) ; this . reconnectionDelay ( opts . reconnectionDelay || 1000 ) ; this . reconnectionDelayMax ( opts . reconnectionDelayMax || 5000 ) ; this . randomizationFactor ( opts . randomizationFactor ?? 0.5 ) ; this . backoff = new Backoff ( { min : this . reconnectionDelay ( ) , max : this . reconnectionDelayMax ( ) , jitter : this . randomizationFactor ( ) , } ) ; this . timeout ( null == opts . timeout ? 20000 : opts . timeout ) ; this . _readyState = "closed" ; this . uri = uri as string ; const _parser = opts . parser || parser ; this . encoder = new _parser . Encoder ( ) ; this . decoder = new _parser . Decoder ( ) ; this . _autoConnect = opts . autoConnect !== false ; if ( this . _autoConnect ) this . open ( ) ; } param { Boolean } v return { Manager } public public reconnection ( v ) ; public reconnection ( ) ; public reconnection ( v ? ) ; public reconnection ( v ? ) { if ( ! arguments . length ) return this . _reconnection ; this . _reconnection = ! ! v ; return this ; } param { Number } v return { Manager } public public reconnectionAttempts ( v ) ; public reconnectionAttempts ( ) ; public reconnectionAttempts ( v ? ) ; public reconnectionAttempts ( v ? ) { if ( v === undefined ) return this . _reconnectionAttempts ; this . _reconnectionAttempts = v ; return this ; } param { Number } v return { Manager } public public reconnectionDelay ( v ) ; public reconnectionDelay ( ) ; public reconnectionDelay ( v ? ) ; public reconnectionDelay ( v ? ) { if ( v === undefined ) return this . _reconnectionDelay ; this . _reconnectionDelay = v ; this . backoff ?. setMin ( v ) ; return this ; } param v return public public randomizationFactor ( v ) ; public randomizationFactor ( ) ; public randomizationFactor ( v ? ) ; public randomizationFactor ( v ? ) { if ( v === undefined ) return this . _randomizationFactor ; this . _randomizationFactor = v ; this . backoff ?. setJitter ( v ) ; return this ; } param v return public public reconnectionDelayMax ( v ) ; public reconnectionDelayMax ( ) ; public reconnectionDelayMax ( v ? ) ; public reconnectionDelayMax ( v ? ) { if ( v === undefined ) return this . _reconnectionDelayMax ; this . _reconnectionDelayMax = v ; this . backoff ?. setMax ( v ) ; return this ; } param v return public public timeout ( v ) ; public timeout ( ) ; public timeout ( v ? ) ; public timeout ( v ? ) { if ( ! arguments . length ) return this . _timeout ; this . _timeout = v ; return this ; } private private maybeReconnectOnOpen ( ) { if ( ! this . _reconnecting && this . _reconnection && this . backoff . attempts === 0 ) { this . reconnect ( ) ; } } param { Function } fn return public public open ( fn ? ) { debug ( "readyState %s" , this . _readyState ) ; if ( ~ this . _readyState . indexOf ( "open" ) ) return this ; debug ( "opening %s" , this . uri ) ; this . engine = new Engine ( this . uri , this . opts ) ; const socket = this . engine ; const self = this ; this . _readyState = "opening" ; this . skipReconnect = false ; const openSubDestroy = on ( socket , "open" , function ( ) { self . onopen ( ) ; fn && fn ( ) ; } ) ; const errorSub = on ( socket , "error" , ( err ) => { debug ( "error" ) ; self . cleanup ( ) ; self . _readyState = "closed" ; this . emitReserved ( "error" , err ) ; if ( fn ) { fn ( err ) ; } else { self . maybeReconnectOnOpen ( ) ; } } ) ; if ( false !== this . _timeout ) { const timeout = this . _timeout ; debug ( "connect attempt will timeout after %d" , timeout ) ; if ( timeout === 0 ) { openSubDestroy ( ) ; } const timer = this . setTimeoutFn ( ( ) => { debug ( "connect attempt timed out after %d" , timeout ) ; openSubDestroy ( ) ; socket . close ( ) ; socket . emit ( "error" , new Error ( "timeout" ) ) ; } , timeout ) ; if ( this . opts . autoUnref ) { timer . unref ( ) ; } this . subs . push ( function subDestroy ( ) : void { clearTimeout ( timer ) ; } ) ; } this . subs . push ( openSubDestroy ) ; this . subs . push ( errorSub ) ; return this ; } return public public connect ( fn ? ) { return this . open ( fn ) ; } private private onopen ( ) { debug ( "open" ) ; this . cleanup ( ) ; this . _readyState = "open" ; this . emitReserved ( "open" ) ; const socket = this . engine ; this . subs . push ( on ( socket , "ping" , this . onping . bind ( this ) ) , on ( socket , "data" , this . ondata . bind ( this ) ) , on ( socket , "error" , this . onerror . bind ( this ) ) , on ( socket , "close" , this . onclose . bind ( this ) ) , on ( this . decoder , "decoded" , this . ondecoded . bind ( this ) ) ) ; } private private onping ( ) { this . emitReserved ( "ping" ) ; } private private ondata ( data ) { this . decoder . add ( data ) ; } private private ondecoded ( packet ) { this . emitReserved ( "packet" , packet ) ; } private private onerror ( err ) { debug ( "error" , err ) ; this . emitReserved ( "error" , err ) ; } return { Socket } public public socket ( nsp , opts ? ) { let socket = this . nsps [ nsp ] ; if ( ! socket ) { socket = new Socket ( this , nsp , opts ) ; this . nsps [ nsp ] = socket ; } return socket ; } param socket private _destroy ( socket ) { const nsps = Object . keys ( this . nsps ) ; for ( const nsp of nsps ) { const socket = this . nsps [ nsp ] ; if ( socket . active ) { debug ( "socket %s is still active, skipping close" , nsp ) ; return ; } } this . _close ( ) ; } param packet private _packet ( packet ) { debug ( "writing packet %j" , packet ) ; const encodedPackets = this . encoder . encode ( packet as Packet ) ; for ( let i = 0 ; i < encodedPackets . length ; i ++ ) { this . engine . write ( encodedPackets [ i ] , packet . options ) ; } } private private cleanup ( ) { debug ( "cleanup" ) ; this . subs . forEach ( ( subDestroy ) => subDestroy ( ) ) ; this . subs . length = 0 ; this . decoder . destroy ( ) ; } private _close ( ) { debug ( "disconnect" ) ; this . skipReconnect = true ; this . _reconnecting = false ; this . onclose ( "forced close" ) ; if ( this . engine ) this . engine . close ( ) ; } private private disconnect ( ) { return this . _close ( ) ; } private private onclose ( reason ) { debug ( "closed due to %s" , reason ) ; this . cleanup ( ) ; this . backoff . reset ( ) ; this . _readyState = "closed" ; this . emitReserved ( "close" , reason ) ; if ( this . _reconnection && ! this . skipReconnect ) { this . reconnect ( ) ; } } private private reconnect ( ) { if ( this . _reconnecting || this . skipReconnect ) return this ; const self = this ; if ( this . backoff . attempts >= this . _reconnectionAttempts ) { debug ( "reconnect failed" ) ; this . backoff . reset ( ) ; this . emitReserved ( "reconnect_failed" ) ; this . _reconnecting = false ; } else { const delay = this . backoff . duration ( ) ; debug ( "will wait %dms before reconnect attempt" , delay ) ; this . _reconnecting = true ; const timer = this . setTimeoutFn ( ( ) => { if ( self . skipReconnect ) return ; debug ( "attempting reconnect" ) ; this . emitReserved ( "reconnect_attempt" , self . backoff . attempts ) ; if ( self . skipReconnect ) return ; self . open ( ( err ) => { if ( err ) { debug ( "reconnect attempt error" ) ; self . _reconnecting = false ; self . reconnect ( ) ; this . emitReserved ( "reconnect_error" , err ) ; } else { debug ( "reconnect success" ) ; self . onreconnect ( ) ; } } ) ; } , delay ) ; if ( this . opts . autoUnref ) { timer . unref ( ) ; } this . subs . push ( function subDestroy ( ) { clearTimeout ( timer ) ; } ) ; } } private private onreconnect ( ) { const attempt = this . backoff . attempts ; this . _reconnecting = false ; this . backoff . reset ( ) ; this . emitReserved ( "reconnect" , attempt ) ; } }