import { BaseFramework , HTTP_SERVER_KEY , IMidwayBootstrapOptions , MidwayFrameworkType , } from '@midwayjs/core' ; import { debuglog } from 'util' ; const debug = debuglog ( 'midway:socket.io' ) ; import { IMidwaySocketIOApplication , IMidwaySocketIOConfigurationOptions , IMidwaySocketIOContext , } from './interface' ; import { Server } from 'socket.io' ; import { WS_CONTROLLER_KEY , WS_EVENT_KEY , WSControllerOption , WSEventInfo , WSEventTypeEnum , getClassMetadata , listModule , Framework , } from '@midwayjs/decorator' ; import { MidwaySocketIOContextLogger } from './logger' ; @ Framework ( ) export class MidwaySocketIOFramework extends BaseFramework < IMidwaySocketIOApplication , IMidwaySocketIOContext , IMidwaySocketIOConfigurationOptions > { private namespaceList = [ ] ; configure ( ) { return this . configService . getConfiguration ( 'socketIO' ) ; } applicationInitialize ( options ) { this . app = new Server ( this . configurationOptions ) as IMidwaySocketIOApplication ; } public app : IMidwaySocketIOApplication ; protected async afterContainerReady ( options ) { await this . loadMidwayController ( ) ; } public async run ( ) { if ( this . configurationOptions . adapter ) { this . app . adapter ( this . configurationOptions . adapter ) ; this . logger . debug ( 'init socket.io-redis ready!' ) ; } if ( this . configurationOptions . port ) { this . app . listen ( this . configurationOptions . port , this . configurationOptions ) ; this . logger . info ( `Socket.io server port = ${ this . configurationOptions . port } start success` ) ; } else if ( this . applicationContext . hasObject ( HTTP_SERVER_KEY ) ) { this . app . attach ( this . applicationContext . get ( HTTP_SERVER_KEY ) , this . configurationOptions ) ; this . logger . info ( 'Socket.io server start success and attach to web server' ) ; } } protected async beforeStop ( ) { return new Promise < void > ( resolve => { this . app . close ( ( ) => { setTimeout ( ( ) => { resolve ( ) ; } , 1000 ) ; } ) ; } ) ; } public getFrameworkType ( ) { return MidwayFrameworkType . WS_IO ; } private async loadMidwayController ( ) { const controllerModules = listModule ( WS_CONTROLLER_KEY ) ; for ( const module of controllerModules ) { await this . addNamespace ( module ) ; } } private async addNamespace ( target ) { const controllerOption = getClassMetadata ( WS_CONTROLLER_KEY , target ) ; const nsp = this . app . of ( controllerOption . namespace ) ; this . namespaceList . push ( controllerOption . namespace ) ; nsp . use ( ( socket , next ) => { this . app . createAnonymousContext ( socket ) ; socket . requestContext . registerObject ( 'socket' , socket ) ; socket . app = this . app ; next ( ) ; } ) ; nsp . on ( 'connect' , async ( socket ) => { const wsEventInfos = getClassMetadata ( WS_EVENT_KEY , target ) ; const methodMap = { } ; if ( wsEventInfos . length ) { for ( const wsEventInfo of wsEventInfos ) { methodMap [ wsEventInfo . propertyName ] = methodMap [ wsEventInfo . propertyName ] || { responseEvents : [ ] } ; const controller = await socket . requestContext . getAsync ( target ) ; if ( wsEventInfo . eventType === WSEventTypeEnum . ON_CONNECTION ) { try { const result = await controller [ wsEventInfo . propertyName ] . apply ( controller , [ socket ] ) ; await this . bindSocketResponse ( result , socket , wsEventInfo . propertyName , methodMap ) ; } catch ( err ) { this . logger . error ( err ) ; } } else if ( wsEventInfo . eventType === WSEventTypeEnum . ON_MESSAGE ) { socket . on ( wsEventInfo . messageEventName , async ( ... args ) => { debug ( 'got message' , wsEventInfo . messageEventName , args ) ; try { const result = await ( await this . applyMiddleware ( async ( ctx , next ) => { return controller [ wsEventInfo . propertyName ] . apply ( controller , args ) ; } ) ) ( socket ) ; if ( typeof args [ args . length - 1 ] === 'function' ) { args [ args . length - 1 ] ( result ) ; } else { await this . bindSocketResponse ( result , socket , wsEventInfo . propertyName , methodMap ) ; } } catch ( error ) { this . logger . error ( error ) ; } } ) ; } else if ( wsEventInfo . eventType === WSEventTypeEnum . ON_DISCONNECTION ) { socket . on ( 'disconnect' , async ( reason ) => { try { const result = await controller [ wsEventInfo . propertyName ] . apply ( controller , [ reason ] ) ; await this . bindSocketResponse ( result , socket , wsEventInfo . propertyName , methodMap ) ; } catch ( err ) { this . logger . error ( err ) ; } } ) ; } else { methodMap [ wsEventInfo . propertyName ] . responseEvents . push ( wsEventInfo ) ; } } } } ) ; if ( nsp . adapter ) { nsp . adapter . on ( 'error' , err => { this . logger . error ( err ) ; } ) ; } } private async bindSocketResponse ( result , socket , propertyName , methodMap ) { if ( result && methodMap [ propertyName ] ) { for ( const wsEventInfo of methodMap [ propertyName ] . responseEvents ) { if ( wsEventInfo . eventType === WSEventTypeEnum . EMIT ) { if ( wsEventInfo . roomName . length ) { socket = wsEventInfo . roomName . reduce ( ( socket , name ) => { return socket . to ( name ) ; } , socket ) ; } socket . emit . apply ( socket , [ wsEventInfo . messageEventName ] . concat ( result ) ) ; } else if ( wsEventInfo . eventType === WSEventTypeEnum . BROADCAST ) { socket . nsp . emit . apply ( socket . nsp , [ ] . concat ( result ) ) ; } } } } public getFrameworkName ( ) { return 'midway:socketIO' ; } public getDefaultContextLoggerClass ( ) { return MidwaySocketIOContextLogger ; } }