import { EventEmitter } from 'events' ; import { Message , Entry } from '../server/protocol' ; export interface Game { gameId : string ; playerId : string ; } export class BaseClient { private ws : WebSocket | null = null ; private game : Game | null = null ; private events : EventEmitter = new EventEmitter ( ) ; private pending : Array < Entry > | null = null ; constructor ( ) { this . events . setMaxListeners ( 50 ) ; } new ( url ) { this . connect ( url , ( ) => { this . send ( { type : 'NEW' } ) ; } ) ; } join ( url , gameId ) { this . connect ( url , ( ) => { this . send ( { type : 'JOIN' , gameId } ) ; } ) ; } disconnect ( ) { this . ws ?. close ( ) ; } private connect ( url , start ) { if ( this . ws ) { return ; } this . ws = new WebSocket ( url ) ; this . ws . onopen = start ; this . ws . onclose = this . onClose . bind ( this ) ; this . ws . onmessage = event => { const message = JSON . parse ( event . data as string ) as Message ; this . onMessage ( message ) ; } ; } on ( what , handler ) ; on ( what , handler ) ; on ( what , handler ) ; on ( what , handler ) { this . events . on ( what , handler ) ; } transaction ( func ) { this . pending = [ ] ; try { func ( ) ; if ( this . pending !== null && this . pending . length > 0 ) { this . send ( { type : 'UPDATE' , entries : this . pending , full : false } ) ; } } finally { this . pending = null ; } } update ( entries ) { if ( this . pending !== null ) { this . pending . push ( ... entries ) ; } else { this . send ( { type : 'UPDATE' , entries , full : false } ) ; } } private send ( message ) { if ( ! this . open ) { return ; } const data = JSON . stringify ( message ) ; this . ws ! . send ( data ) ; } private open ( ) { return this . ws !== null && this . ws . readyState === WebSocket . OPEN ; } connected ( ) { return this . open ( ) && this . game !== null ; } playerId ( ) { return this . game ?. playerId ?? 'offline' ; } private onClose ( ) { this . ws = null ; const game = this . game ; this . game = null ; this . events . emit ( 'disconnect' , game ) ; } private onMessage ( message ) { switch ( message . type ) { case 'JOINED' : this . game = { gameId : message . gameId , playerId : message . playerId , } ; this . events . emit ( 'connect' , this . game , message . isFirst ) ; break ; case 'UPDATE' : this . events . emit ( 'update' , message . entries , message . full ) ; break ; } } }