/**
* @module flitter-socket/SocketUnit
*/
const Unit = require('libflitter/Unit')
const express = require('express')
const SocketController = require('./Controller')
/**
* Adds a two-way transactional websocket server to Flitter.
* @extends module:libflitter/Unit~Unit
*/
class SocketUnit extends Unit {
static get services() {
return [...super.services, 'app', 'configs', 'express', 'output', 'routers', 'canon']
}
static get name() {
return 'sockets'
}
/**
* Initialize the SocketUnit. Bootstraps the websocket adapters into the Express
* app and http/s server, then registers handlers for any route definitions.
* @param {module:libflitter/app/FlitterApp~FlitterApp} app - the current Flitter app
* @returns {Promise<void>}
*/
async go(app){
// Bootstrap express with the websocket server
const server = this.express.server
const ws = require('express-ws')(app.express, server)
// Store the server for future use
this.server = ws
// if we have routing, search for socket route definitions
if ( app.di().has('routers') ){
this.output.debug('Loading routing schemas for socket route definitions.')
// Fetch the registered router schemas
const schemas = Object.values(this.routers.canonical_items).map(x => x.schema)
for ( const schema of schemas ) {
if ( schema.socket ) {
const prefix = schema.prefix ? schema.prefix : '/'
for ( const route in schema.socket ) {
if ( !schema.socket.hasOwnProperty(route) ) continue
this.output.debug(`Registering socket handlers for ${prefix} -> ${route}`)
let handlers = schema.socket[route]
const router = express.Router()
if ( typeof handlers === 'string' ) handlers = [handlers]
if ( !Array.isArray(handlers) ) throw new Error(`Invalid socket handler definition on ${prefix} -> ${route}`)
const final_handler = handlers.pop()
// Register middleware handlers
for ( const mw of handlers ) {
if ( typeof mw === 'string' ) {
const mw_fn = this.canon.get(mw)
if ( typeof mw_fn !== 'function' ) throw new Error(`Invalid socket middleware. The canonical reference must resolve to a function: ${mw}`)
router.use(mw_fn)
} else if ( typeof mw === 'function' ) {
this.output.warn('Specifying socket handlers as functions is deprecated and will throw an error in the future. Prefer canonical reference names.')
router.use(mw)
} else throw new Error(`Invalid socket middleware on ${prefix} -> ${route}. Please provide a canonical reference name.`)
}
// register the final handler - should be canonical ref to SocketController instance
if ( typeof final_handler === 'string' ) {
const ctrl = this.canon.get(final_handler)
if ( !(ctrl instanceof SocketController) ) {
throw new Error (`Unable to register socket handler ${final_handler}. Canonical names must resolve to instances of flitter-socket/Controller.`)
}
router.ws(route, ctrl._connect.bind(ctrl))
app.express.use(prefix, router)
} else throw new Error(`Invalid socket handler for ${prefix} -> ${route}. Please provide a canonical reference name.`)
}
}
}
}
}
/**
* Get the templates provided by the unit.
* @returns {{controller: {template: ((function(string): string)|*|controller), extension: string, directory: string}}}
*/
templates(){
return {
"socket:controller": {
template: require('./templates/controller'),
directory: this.app.directories.controllers,
extension: '.controller.js'
}
}
}
}
module.exports = exports = SocketUnit