UNPKG

nea-helpers

Version:

Collection of helpers for developing a Nymi Enabled Application (NEA)

384 lines (357 loc) 14.1 kB
'use strict'; const cluster = require('cluster'), path = require('path'), debug = require('debug')(__filename.split(path.sep).pop()), EventEmitter = require('events'), NeaResponseFactory = require('./NeaResponseFactory'), WorkerFile = path.resolve(__dirname, 'NapiWorker.js'); /** * The Nea class manages communication with the NAPI. * * <b>Emitted Events:</b> * <li>InitGet - {@link InitResponse}</li> * <li>InfoGet - {@link InfoResponse}</li> * <li>NotificationsGet - {@link NotificationResponse}</li> * <li>NotificationsSet - {@link NotificationResponse}</li> * <li>NotificationsReportFoundChange - {@link FoundChangeEvent}</li> * <li>NotificationsReportPresenceChange - {@link PresenceChangeEvent}</li> * <li>NotificationsReportGeneralError - {@link GeneralErrorEvent}</li> * <li>KeyDelete - {@link KeyDeleteResponse}</li> * <li>BuzzRun - {@link AcknowledgementResponse}</li> * <li>RevokeRun - {@link AcknowledgementResponse}</li> * <li>SignSetup - {@link AcknowledgementResponse}</li> * <li>SignRun - {@link SignatureResponse}</li> * <li>RandomRun - {@link RandomResponse}</li> * <li>TotpRun - {@link AcknowledgementResponse}</li> * <li>TotpGet - {@link TotpResponse}</li> * <li>SymmetricKeyRun - {@link AcknowledgementResponse}</li> * <li>SymmetricKeyGet - {@link SymmetricKeyResponse}</li> * <li>CdfRun - {@link CdfRegistrationResponse}</li> * <li>CdfGet - {@link CdfAuthResponse}</li> * <li>ProvisionRunStart - {@link AcknowledgementResponse}</li> * <li>ProvisionRunStop - {@link AcknowledgementResponse}</li> * <li>ProvisionPattern - {@link AcknowledgementResponse}</li> * <li>ProvisionReportPatterns - {@link PatternEvent}</li> * <li>ProvisionReportProvisioned - {@link ProvisionedEvent}</li> * <li>RoamingAuthSetupRun - {@link RoamingAuthSetupResponse}</li> * <li>RoamingAuthRun - {@link AcknowledgementResponse}</li> * <li>RoamingAuthReportNonce - {@link RoamingAuthNonceEvent}</li> * <li>RoamingAuthSigRun - {@link RoamingAuthSigResponse}</li> * <li>Error - {string}</li> * <li>ProvisionsChanged - {@link ProvisionsChangedEvent}</li> */ class Nea extends EventEmitter { /** * Creates a NEA instance. * @param {NeaConfig} neaConfig * @param {object} storage * @type {object} * @property {function} read * @property {function} write */ constructor (neaConfig, storage) { super(); this._id = null; this._initialized = false; this._config = neaConfig; this._worker = null; this._storage = storage; if (typeof storage.read !== 'function' || typeof storage.write !== 'function') { throw new Error('Invalid storage provided'); } cluster.setupMaster({exec: WorkerFile}); cluster.on('online', worker => worker.send(worker.init)); cluster.on('exit', worker => !worker.exitedAfterDisconnect && Promise.all([this.stop(), this.start()])); } /** * Id getter * @return {int} */ getId () { return this._id; } /** * Config getter * @return {NeaConfig} */ getConfig () { return this._config; } /** * Check if this NEA is running * @return {boolean} */ isRunning () { return this._worker && !this._worker.isDead(); } /** * Spawns a new worker to manage a NEA * * @param {NeaConfig} config * @private * @return {cluster.Worker} */ _spawnWorker (config) { let worker = cluster.fork(); worker.init = { op: 'init', neaName: config.getName(), logDirectory: config.getLogDirectory(), port: config.getPort(), log: config.getLogLevel(), host: config.getHost(), nymulator: config.isNymulator(), retry: config.getRetryCount(), interval: config.getInterval(), provisions: this._storage.read() }; return worker; } /** * Starts this NEA. * * @return {Promise} */ start () { return new Promise((resolve, reject) => { if (!this._initialized) { debug('Starting...'); this._worker = this._spawnWorker(this._config); this._worker.on('message', this._onMessage.bind(this)); this.once('InitGet', res => { this._initialized = res.isSuccessful() && res.isCompleted(); debug('Started: %s', this._initialized.toString()); if (this._initialized) { this._id = this._worker.id; return resolve(res); } this._worker.removeAllListeners('message'); reject(); }); } }); } /** * Stops this NEA. * * @return {Promise} */ stop () { return new Promise((resolve, reject) => { let timeout; if (this._worker !== null) { timeout = setTimeout(() => this._worker.kill(), 1000); this._worker.once('exit', (code, signal) => { debug('Stopped: %s, %d', signal, code); this._initialized = false; clearTimeout(timeout); resolve(); }); if (this._initialized || !this._worker.isDead()) { debug('Stopping...'); this._worker.send({op: 'quit'}); } else if (this._initialized && this._worker.isDead()) { debug('Already stopped'); this._initialized = false; resolve(); } } else { debug('No worker to stop'); reject(); } }); } /** * Send a NeaRequest to the NapiWorker * @param {NeaRequest} request * @return {*} * @fires Nea#event:Error */ send (request) { if (this.isRunning()) { debug('Sending: %o', request); return this._worker.send({op: 'put', put: request}); } /** @event Nea#Error @type {string} */ this.emit('Error', 'Requested NEA is not running'); } /** * Handles incoming messages from NapiWorker. * * @param {string} message * @private * @return {void} * @fires Nea#event:InitGet * @fires Nea#event:InfoGet * @fires Nea#event:NotificationsGet * @fires Nea#event:NotificationsSet * @fires Nea#event:NotificationsReportFoundChange * @fires Nea#event:NotificationsReportPresenceChange * @fires Nea#event:NotificationsReportGeneralError * @fires Nea#event:KeyDelete * @fires Nea#event:BuzzRun * @fires Nea#event:RevokeRun * @fires Nea#event:SignSetup * @fires Nea#event:SignRun * @fires Nea#event:RandomRun * @fires Nea#event:TotpRun * @fires Nea#event:TotpGet * @fires Nea#event:SymmetricKeyRun * @fires Nea#event:SymmetricKeyGet * @fires Nea#event:CdfRun * @fires Nea#event:CdfGet * @fires Nea#event:ProvisionRunStart * @fires Nea#event:ProvisionRunStop * @fires Nea#event:ProvisionPattern * @fires Nea#event:ProvisionReportPatterns * @fires Nea#event:ProvisionReportProvisioned * @fires Nea#event:RoamingAuthSetupRun * @fires Nea#event:RoamingAuthRun * @fires Nea#event:RoamingAuthReportNonce * @fires Nea#event:RoamingAuthSigRun * @fires Nea#event:Error * @fires Nea#event:ProvisionsChanged */ _onMessage (message) { let response = NeaResponseFactory.parse(message); debug('Response: %o', response); if (response === null) { /** @event Nea#Error @type {string} */ return this.emit('Error', 'Unsupported response ' + JSON.stringify(message)); } switch (response.getPath()) { case 'init/get': /** @event Nea#InitGet @type {InitResponse} */ this.emit('InitGet', response); break; case 'info/get': /** @event Nea#InfoGet @type {InfoResponse} */ this.emit('InfoGet', response); break; case 'notifications/get': /** @event Nea#NotificationsGet @type {NotificationResponse} */ this.emit('NotificationsGet', response); break; case 'notifications/set': /** @event Nea#NotificationsSet @type {NotificationResponse} */ this.emit('NotificationsSet', response); break; case 'notifications/report/found-change': /** @event Nea#NotificationsReportFoundChange @type {FoundChangeEvent} */ this.emit('NotificationsReportFoundChange', response); break; case 'notifications/report/presence-change': /** @event Nea#NotificationsReportPresenceChange @type {PresenceChangeEvent} */ this.emit('NotificationsReportPresenceChange', response); break; case 'notifications/report/general-error': /** @event Nea#NotificationsReportGeneralError @type {GeneralErrorEvent} */ this.emit('NotificationsReportGeneralError', response); break; case 'key/delete': /** @event Nea#KeyDelete @type {KeyDeleteResponse} */ this.emit('KeyDelete', response); break; case 'buzz/run': /** @event Nea#BuzzRun @type {AcknowledgementResponse} */ this.emit('BuzzRun', response); break; case 'revoke/run': /** @event Nea#RevokeRun @type {AcknowledgementResponse} */ this.emit('RevokeRun', response); break; case 'sign/setup': /** @event Nea#SignSetup @type {AcknowledgementResponse} */ this.emit('SignSetup', response); break; case 'sign/run': /** @event Nea#SignRun @type {SignatureResponse} */ this.emit('SignRun', response); break; case 'random/run': /** @event Nea#RandomRun @type {RandomResponse} */ this.emit('RandomRun', response); break; case 'totp/run': /** @event Nea#TotpRun @type {AcknowledgementResponse} */ this.emit('TotpRun', response); break; case 'totp/get': /** @event Nea#TotpGet @type {TotpResponse} */ this.emit('TotpGet', response); break; case 'symmetricKey/run': /** @event Nea#SymmetricKeyRun @type {AcknowledgementResponse} */ this.emit('SymmetricKeyRun', response); break; case 'symmetricKey/get': /** @event Nea#SymmetricKeyGet @type {SymmetricKeyResponse} */ this.emit('SymmetricKeyGet', response); break; case 'cdf/run': /** @event Nea#CdfRun @type {CdfRegistrationResponse} */ this.emit('CdfRun', response); break; case 'cdf/get': /** @event Nea#CdfGet @type {CdfAuthResponse} */ this.emit('CdfGet', response); break; case 'provision/run/start': /** @event Nea#ProvisionRunStart @type {AcknowledgementResponse} */ this.emit('ProvisionRunStart', response); break; case 'provision/run/stop': /** @event Nea#ProvisionRunStop @type {AcknowledgementResponse} */ this.emit('ProvisionRunStop', response); break; case 'provision/pattern': /** @event Nea#ProvisionPattern @type {AcknowledgementResponse} */ this.emit('ProvisionPattern', response); break; case 'provision/report/patterns': /** @event Nea#ProvisionReportPatterns {PatternEvent} */ this.emit('ProvisionReportPatterns', response); break; case 'provision/report/provisioned': /** @event Nea#ProvisionReportProvisioned @type {ProvisionedEvent} */ this.emit('ProvisionReportProvisioned', response); break; case 'roaming-auth-setup/run': /** @event Nea#RoamingAuthSetupRun @type {RoamingAuthSetupResponse} */ this.emit('RoamingAuthSetupRun', response); break; case 'roaming-auth/run': /** @event Nea#RoamingAuthRun @type {AcknowledgementResponse} */ this.emit('RoamingAuthRun', response); break; case 'roaming-auth/report/nonce': /** @event Nea#RoamingAuthReportNonce @type {RoamingAuthNonceEvent} */ this.emit('RoamingAuthReportNonce', response); break; case 'roaming-auth-sig/run': /** @event Nea#RoamingAuthSigRun @type {RoamingAuthSigResponse} */ this.emit('RoamingAuthSigRun', response); break; case 'provisions/changed': /** @event Nea#ProvisionsChanged @type {ProvisionsChangedResponse} */ this.emit('ProvisionsChanged', response); this._storage.write(response.getProvisions()); break; default: /** @event Nea#Error @type {string} */ this.emit('Error', 'Unknown path ' + response.getPath()); } } } module.exports = Nea;