/**
* WebSocket connection to UnityBase server using ubNotifier protocol
*
* @module UBNotifierWSProtocol
* @memberOf module:@unitybase/ub-pub
* @author xmax, mpv
*/
module.exports = UBNotifierWSProtocol
const EventEmitter = require('./events')
const WS_PROTOCOL = 'ubNotifier'
const MAX_BUFFERED_COMMANDS = 100
/**
* @classdesc
*
* WebSocket connection to UnityBase server using ubNotifier protocol.
* The property `supported` indicate `ubNotifier` protocol is supported by server.
* Class mixes an EventEmitter. After got a command from a server the event with command code
* is fired.
*
* The event flow are:
*
* - just after webSocket connection is established `connected` event are fired, when
* - after server accept (verify a user credential) `accepted` are fired - here you can got
* a connectionID of a current connection
* - next is a series of protocol events ( `test_command` for example )
* - in case of protocol error `error` are fired
* - when WebSocket connection are closed `disconnected` event are fired
*
* You do not need to **connect** or **reconnect** a WebSocket manually - UBNotifierWSProtocol recreate
* a WebSocket automatically just after corresponding UBConnection fires a `authorized` event.
*
* For `adminUI` instance of this class is accessible from $App.ubNotifier just after $App.launch
*
* Usage sample:
*
* // Server side
* const WebSockets = require('@unitybase/ub/modules/web-sockets');
* var notifier = WebSockets.getWSNotifier();
* if (notifier) {
* notifier.broadcast('test_command', {name: 'Homer', like: 'donuts'});
* }
*
* // Client side
* // Will output 'Homer like donuts' after got a `test_command` from server
* $App.ubNotifier.on('test_command', function(params}{
* console.debug(params.name, 'like', params.like)
* }
* @class
* @mixes EventEmitter
* @param {UBConnection} connection Warning - only one instance of UBNotifierWSProtocol for a connection is valid.
*/
function UBNotifierWSProtocol (connection) {
const notifier = this
const supported = (connection.supportedWSProtocols.indexOf(WS_PROTOCOL) !== -1) && (typeof WebSocket !== 'undefined')
/**
* Indicate `ubNotifier` protocol is supported by server.
* If not supported all calls to send() will be ignored.
*
* @type {boolean}
*/
this.supported = supported
EventEmitter.call(this)
Object.assign(this, EventEmitter.prototype)
let doDebug = function () {}
let inDebug = false
/**
* Enable output all webSocket interaction to console
*
* @property {boolean} debugMode
*/
Object.defineProperty(notifier, 'debugMode', {
get: function () { return inDebug },
set: function (newValue) {
if (inDebug !== newValue) {
inDebug = newValue
doDebug = inDebug ? console.debug.bind(console, 'ubNotifier') : function () {}
}
},
enumerable: true,
configurable: true
})
const wsURL = 'ws' + connection.serverUrl.slice(4) + 'ws' // remove http part, so in case http://.. we got ws://.., in case https://.. -> wss://..
let $ws = null
let isConnectionAccepted = false
let bufferedCommands = []
/**
*
* @param session
*/
function _createWSConnection (session) {
if (supported) {
$ws = new WebSocket(wsURL + '?SESSION_SIGNATURE=' + session.signature(), 'ubNotifier')
$ws.onopen = function (e) {
doDebug('connected to', e.target.url, 'protocol:', e.target.protocol)
/**
* Emitted for {@link UBNotifierWSProtocol} just after WS connection is established, but before it accepted by server.
* Params: url, protocol
*
* @event connected
*/
notifier.emit('connected', e.target.url, e.target.protocol)
}
$ws.onmessage = function (e) {
let msg
doDebug('Got a raw data', e.data)
try {
msg = JSON.parse(e.data)
} catch (err) {
console.error('ubNotifier: Invalid command from server:', e.data)
}
const
command = msg.command
const params = msg.params
let delayedCmd
doDebug('Got a command', command, 'with params', params)
switch (command) {
case 'accepted': // send a buffered request if any
isConnectionAccepted = true
while ((delayedCmd = bufferedCommands.shift())) {
notifier.sendCommand(delayedCmd.command, delayedCmd.params)
}
bufferedCommands = []
/**
* If server accept a ubNotifier WS connection this event will be emitted for {@link UBNotifierWSProtocol} with `connectionID` parameter
*
* @event accepted
*/
notifier.emit('accepted', params.connectionID)
break
case 'error':
doDebug('Got an error from server', params)
notifier.emit('error', params)
break
default:
doDebug('Emit event ', command, 'with params', params)
notifier.emit(command, params)
break
}
}
$ws.onclose = function (e) {
isConnectionAccepted = false
doDebug('Connection closed. Code:', e.code, 'Reason:', e.reason)
notifier.emit('disconnected', e.code, e.reason)
}
}
}
/**
* Sand a command to server
*
* - if WS connection is not accepted yet will buffer the commands and send it just after connection is accepted
* - if `ubNotifier` protocol not supported by server will do nothing
*
* @function
* @param {string} command
* @param {*} params
*/
this.sendCommand = function (command, params) {
if (supported) {
if (isConnectionAccepted) {
$ws.send(JSON.stringify({ command, params }))
} else { // add to delayed buffer
bufferedCommands.push({ command, params })
if (bufferedCommands.length > MAX_BUFFERED_COMMANDS) bufferedCommands.shift()
}
}
}
/**
*
* @param ubConnection
* @param session
*/
function _onUBConnectionAuthorized (ubConnection, session) {
isConnectionAccepted = false
if (connection.supportedWSProtocols.indexOf(WS_PROTOCOL) !== -1) {
_createWSConnection(session)
}
}
connection.on('authorized', _onUBConnectionAuthorized)
}