/*
* Copyright 2017-2019 Tom Swindell
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
const EventEmitter = require('events')
/**
* @private
*/
const sleep = (delay) => new Promise((resolve, reject) => setTimeout(resolve, delay))
/**
* Emitted when a new connector has been attached to this instance
* @event ConnectionManager#attached
*
* @property {string} uid
*
* @public
*/
/**
* Emitted when a connector has been detached from this instance.
* @event ConnectionManager#detached
* @property {string} uid
*
* @public
*/
/**
* Emitted when a connector has began connecting to a remote device.
* @event ConnectionManager#connecting
*
* @property {string} uid
*
* @public
*/
/**
* Emitted when a device has connected.
* @event ConnectionManager#connected
*
* @property {string} uid
* @property {Channel} channel
*
* @public
*/
/**
* Emitted when a device has disconnected.
* @event ConnectionManager#disconnected
*
* @property {string} uid
*
* @public
*/
/**
* Emitted when an error has occured
* @event ConnectionManager#error
*
* @property {Error} error
*
* @public
*/
/**
* Creates a new ARSDK ConnectionManager instance for managing connections
* to ARSDK based protocol devices.
*
* @class
* @implements EventEmitter
* @public
*
* @hideconstructor
*
* @param {string} [controllerType="js-arsdk"] Controller type to declare when connecting
* @param {string} [controllerName=HOSTNAME] Controller name to declare when connecting
* @param {Object} [opts]
* @param {boolean} [opts.debug=false] Enable debugging output
*
* @example
*
* const manager = new ConnectionManager('js-arsdk', 'MyController')
*
* manager.on('connected', (uid, device) => { ... })
* manager.on('disconnected', (uid) => { ... })
* manager.on('error', (error) => { ... })
*
* manager.attach(new NetworkConnector('192.168.53.1', 44444))
*
*/
function ConnectionManager (controllerType, controllerName, __opts) {
const __self = this
const __connectors = {}
const __connections = {}
// Extend instance with EventEmitter class
EventEmitter.call(this)
/**
* Attach a device protocol connector to this manager for managing
* device connectivity.
*
* @method ConnectionManager#attach
* @public
*
* @param {ConnectorInterface} connector
* @param {Object} [opts]
* @param {boolean} [opts.autoconnect=true] Automatically connect
* @param {boolean} [opts.reconnect=true] Enable/Disable auto-reconnects
*
* @returns {Promise}
*/
const attach = (connector, opts) => {
opts = Object.assign({
autoconnect: true,
reconnect: true
}, opts || {})
__connectors[connector.uid()] = [connector, opts]
this.emit('attached', connector.uid())
if (opts.autoconnect) return connect(connector.uid())
return null
}
const __onClose = (uid) => {
const [ _, opts ] = __connectors[uid]
__connections[uid].off('close', __onClose)
delete __connections[uid]
__self.emit('disconnected', uid)
if (opts.reconnect) setTimeout(() => connect(uid), 1000)
}
/**
*
* @method ConnectionManager#connect
* @public
*
* @param {string} uid
*
* @returns {MessageChannel}
*/
const connect = async (uid) => {
if (uid in __connections) throw 'ALREADY_CONNECTED'
if (!(uid in __connectors)) throw 'UKNOWN_UID'
const [ connector, opts ] = __connectors[uid]
this.emit('connecting', uid)
try {
const channel = await connector.connect()
channel.on('close', __onClose)
__connections[uid] = channel
this.emit('connected', uid, channel)
return channel
} catch (error) {
console.error(error.errno)
if (opts.reconnect) setTimeout(() => connect(uid), 2000)
}
}
/**
* @method ConnectionManager#disconnect
* @public
*
* @param {string} uid
*
* @returns {boolean}
*/
const disconnect = async (uid) => {
if (!(uid in __connections)) return false
await __connections[uid].close()
return true
}
/**
* Detach a device protocol connector from this manager and disconnect from the
* device if it's connected.
* @method ConnectionManager#detach
* @public
*
* @param {string} uid
* @param {Object} [opts]
* @param {boolean} [opts.disconnect=true]
*
* @returns {Promise}
*/
const detach = async (uid, opts) => {
if (!(uid in __connectors)) return
console.info('Detaching connection:', uid)
if (uid in __connections) {
console.info('Disconnecting from device:', uid)
await disconnect(uid)
}
delete __connectors[uid]
this.emit('detached', uid)
}
// Expose Public Interface
this.attach = attach.bind(this)
this.detach = detach.bind(this)
this.connect = connect.bind(this)
this.disconnect = disconnect.bind(this)
}
ConnectionManager.prototype = Object.create(EventEmitter.prototype)
module.exports = ConnectionManager