UNPKG

fbp-client

Version:

Higher-level client library for the FBP protocol

176 lines (165 loc) 5.79 kB
const debugObserver = require('debug')('fbp-client:observer'); const debugObserverIgnored = require('debug')('fbp-client:observer:ignored'); function stringMatches(string, matcher) { if (matcher === '*') { return true; } return (string === matcher); } function signalMatches(signal, signatures) { if (typeof signatures === 'function') { return signatures(signal); } const matched = signatures.filter((signature) => { const [protocol, command] = signature.split(':'); return (stringMatches(signal.protocol, protocol) && stringMatches(signal.command, command)); }); if (matched.length > 0) { return true; } return false; } function signalToError(signal) { const signature = `${signal.protocol}:${signal.command}`; let errorMessage = `Unexpected ${signature} message`; let errorStack = ''; if (signature === 'runtime:packet' && signal.payload.payload && signal.payload.payload.message) { // Use the error message of the actual payload errorMessage = signal.payload.payload.message; errorStack = signal.payload.payload.stack; } if (signature === 'network:data' && signal.payload.src && signal.payload.src.port === 'error') { // Use the error message of the actual payload errorMessage = signal.payload.data.message || signal.payload.data; errorStack = signal.payload.data.stack; } if (signal.payload.error) { // *:error or network:processerror errorMessage = signal.payload.error; errorStack = signal.payload.stack; } const err = new Error(errorMessage); err.signature = signature; if (errorStack) { err.stack = errorStack; } return err; } class Observer { constructor(client, events) { this.client = client; this.signals = []; this.protocolErrors = []; this.listener = null; this.errorListener = null; this.subscribe(events); } subscribe(events) { this.listener = (signal) => { if (!signalMatches(signal, events)) { debugObserverIgnored(`${signal.protocol}:${signal.command}`); return; } debugObserver(`Observed ${signal.protocol}:${signal.command}`); this.signals.push(signal); }; this.client.on('signal', this.listener); if (!this.client.options.skipValidation) { this.errorListener = (err) => { this.protocolErrors.push(err); }; this.client.on('protocolError', this.errorListener); } } unsubscribe() { if (this.listener) { this.client.removeListener('signal', this.listener); this.listener = null; } if (this.errorListener) { this.client.removeListener('protocolError', this.errorListener); this.errorListener = null; } } until(success, failure) { return new Promise((resolve, reject) => { // Check if there were protocol errors. These should always fail if (this.protocolErrors.length) { const error = this.protocolErrors[0]; debugObserver(`Failed with protocol error ${error.message}`); error.signals = this.signals.slice(0); this.unsubscribe(); this.signals = []; reject(error); return; } // Check signals received until now for failure or success for (let i = 0; i < this.signals.length; i += 1) { const signal = this.signals[i]; if (signalMatches(signal, failure)) { const err = signalToError(signal); debugObserver(`Failed with ${signal.protocol}:${signal.command}`); err.signals = this.signals.slice(0, i + 1); this.unsubscribe(); this.signals = []; reject(err); return; } if (signalMatches(signal, success)) { debugObserver(`Succeeded with ${signal.protocol}:${signal.command}`); const signals = this.signals.slice(0, i + 1); this.unsubscribe(); this.signals = []; resolve(signals); return; } } let errorListener = null; // See if the signal arrives later const listener = (signal) => { if (signalMatches(signal, failure)) { debugObserver(`Failed with ${signal.protocol}:${signal.command}`); const err = signalToError(signal); err.signals = this.signals.slice(0); this.unsubscribe(); this.client.removeListener('signal', listener); if (!this.client.options.skipValidation) { this.client.removeListener('protocolError', errorListener); } this.signals = []; reject(err); return; } if (signalMatches(signal, success)) { debugObserver(`Succeeded with ${signal.protocol}:${signal.command}`); const signals = this.signals.slice(0); this.unsubscribe(); this.client.removeListener('signal', listener); if (!this.client.options.skipValidation) { this.client.removeListener('protocolError', errorListener); } this.signals = []; resolve(signals); } }; // See if a protocol error arrives later errorListener = (err) => { debugObserver(`Failed with protocol error ${err.message}`); const error = err; error.signals = this.signals.slice(0); this.unsubscribe(); this.client.removeListener('signal', listener); if (!this.client.options.skipValidation) { this.client.removeListener('protocolError', errorListener); } this.signals = []; reject(error); }; this.client.on('signal', listener); if (!this.client.options.skipValidation) { this.client.on('protocolError', errorListener); } }); } } module.exports = (client, events) => new Observer(client, events);