import http from 'http' ; import express , { Request , Response } from 'express' ; import { v4 as uuidv4 } from 'uuid' ; import cors from 'cors' ; import * as WebSocket from 'ws' ; import { ServerConfig , ServerState } from './type' ; import ServerAbstract from './ServerAbstract' ; enum ServerStatus { RUNNING = 'RUNNING' , STOPPED = 'STOPPED' } class ServerManager { private servers : { [ index ] : ServerAbstract ; } ; constructor ( ) { this . servers = { } ; } returns { Array . < ServerState > } getServers = ( ) => Object . keys ( this . servers ) . map ( ( currentKey ) => ( { id : this . servers [ currentKey ] . id , name : this . servers [ currentKey ] . name , status : this . servers [ currentKey ] . isListening ( ) ? ServerStatus . RUNNING : ServerStatus . STOPPED , } ) ) ; param { string } id getServerAbstract = ( id ) => this . servers [ id ] ; param { string } id getServer = ( id ) => { const { name } = this . servers [ id ] ; return { id , name , status : this . servers [ id ] . isListening ( ) ? ServerStatus . RUNNING : ServerStatus . STOPPED , } ; } ; param { ServerConfig } config param { string } config . name param { Integer } config . port param { Function } config . onConnection param { Function } config . onMessage param { Function } config . onError returns { Promise } createServer = ( config ) : Promise < ServerState | Error > => { const { name , port , onConnection , onMessage , id , onServerClose , protocol , endpoint } = config ; const app = express ( ) ; const server = new http . Server ( app ) ; const serverId = id || uuidv4 ( ) ; app . use ( cors ( ) ) ; if ( protocol === "websocket" ) { const wss = new WebSocket . Server ( { server } ) ; wss . on ( 'connection' , ( ws , req ) => { if ( onConnection ) onConnection ( ) ; ws . on ( 'message' , ( msg ) => { if ( onMessage ) onMessage ( msg , serverId ) ; } ) ; } ) ; /**
             * TODO Promisify this somehow. The complication is promisifying it is that we
             * need to wait messages to be sent to ALL clients. If we don't do this, we assume
             * that the message is always successfully emitted at the time it displays on the UI
             * (which may not be true)
             */ const broadcast = ( message ) => { wss . clients . forEach ( ( client ) => { client . send ( message ) ; } ) ; } ; const stopServer = ( ) => { wss . clients . forEach ( ( ws ) => { ws . terminate ( ) ; } ) ; server . close ( ) . once ( 'close' , ( ) => { if ( onServerClose ) { onServerClose ( { id : serverId , name , port , protocol : protocol , status : ServerStatus . STOPPED , } ) ; } } ) ; } ; this . servers [ serverId ] = new ServerAbstract ( wss , { broadcast , stopServer } , name , serverId , port , protocol ) ; } else { let clients = [ ] ; const sseEndpointHandler = ( req , res ) => { res . writeHead ( 200 , { 'Content-Type' : 'text/event-stream' , Connection : 'keep-alive' , 'Cache-Control' : 'no-cache' , } ) ; const clientId = uuidv4 ( ) ; clients . push ( { id : clientId , res } ) ; req . on ( 'close' , ( ) => { clients = clients . filter ( ( client ) => client . id !== clientId ) ; } ) ; } ; const broadcast = ( message ) => { clients . forEach ( ( client ) => client . res . write ( message ) ) ; } ; const stopServer = ( ) => { clients . forEach ( ( client ) => client . res . end ( ) ) ; server . close ( ) . once ( 'close' , ( ) => { if ( onServerClose ) { onServerClose ( { id : serverId , name , port , status : ServerStatus . STOPPED , protocol : protocol } ) ; } } ) ; } ; app . use ( ( endpoint || '/' ) , sseEndpointHandler ) ; this . servers [ serverId ] = new ServerAbstract ( server , { broadcast , stopServer } , name , serverId , port ) ; } /**
                 * We return a promise, but we _cannot_ ever reject this promise because any errors
                 * that would happen when calling server.listen() will be a Node Event. We choose
                 * to use a promise, however, because we want to tell our consumers when our server
                 * is able to listen to an event.
                 * https://github.com/expressjs/express/issues/2856
                 */ return new Promise ( ( resolve , reject ) => { server . listen ( port || 3000 , ( ) => resolve ( { id , name , port , protocol , status : ServerStatus . RUNNING , } ) ) ; } ) ; } ; param { string } id param { string } message returns { Promise } broadcastToAll = ( id , message ) => { this . servers [ id ] . broadcastToAll ( message ) ; } ; param id returns { Promise } stopServer = ( id ) => { this . servers [ id ] . stop ( ) ; } ; } export default new ServerManager ( ) ;