UNPKG

barracuda-client-api

Version:

API Client to connect to Barracuda Enterprise Service Bus

258 lines (257 loc) 13 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.RecoveryProgress = exports.withRecoveryPolicy = void 0; const IBarracudaClient_1 = require("../IBarracudaClient"); const BarracudaClient_1 = require("../BarracudaClient"); const index_1 = require("../logging/index"); const BarracudaRecoveryPolicy_1 = require("./BarracudaRecoveryPolicy"); const debounce_1 = __importDefault(require("lodash/debounce")); const recoveryDelay = 5000; function strRecovery(recoveryPolicy) { if (typeof (recoveryPolicy) === "number") { return `${BarracudaRecoveryPolicy_1.BarracudaRecoveryPolicy[recoveryPolicy]}(${recoveryPolicy})`; } else { return recoveryPolicy; } } let reconnectionDetectionCounter = 0; /** * Return a clone of the supplied BCjs instance with recovery enabled. * Please note all constructor BarracudaConnectionProps is utilized to clone properties across BCjs instances. * @param {IBarracudaClientConsumer} originalBC * @param {BarracudaRecoveryPolicy} recoveryPolicy * @param {number} overrideRecoveryPeriod, The default is 5000 ms to allow a stable connection to be established. * @param {OnRecoveryCompleteCallBack} onRecoveryComplete * @returns {IBarracudaClientConsumer} */ function withRecoveryPolicy(originalBC, recoveryPolicy, overrideRecoveryPeriod = recoveryDelay, onRecoveryComplete) { const originalOnConnectionChange = originalBC.onConnectionStateChange; if (index_1.isInfo(originalBC.loglevel)) { index_1.logInfo(`${originalBC.bcLog(originalBC.lastConnectionProps)} adding recovery policy ${strRecovery(recoveryPolicy)} with ${overrideRecoveryPeriod}ms delay`, { connectionCounter: originalBC.connectionCounter, defaultConnectionProps: originalBC.defaultConnectionProps, }); } let recoveryReconnectionCounter = 0; let recoverySubscriptions; const debouncedStartRecovery = debounce_1.default((bc, onCompl) => { ++recoveryReconnectionCounter; if (index_1.isInfo(bc.loglevel)) { index_1.logInfo(`${bc.bcLog(bc.lastConnectionProps)} reconnection detected, scheduling recovery ${strRecovery(recoveryPolicy)} in ${overrideRecoveryPeriod}ms`, { recoveryReconnectionCounter, reconnectionDetectionCounter, connectionCounter: bc.connectionCounter, }); } const recoveryProgressPromise = recoverSubscriptions(bc, recoveryPolicy, recoverySubscriptions); return recoveryProgressPromise.then((r) => { if (index_1.isInfo(bc.loglevel)) { index_1.logInfo(`${bc.bcLog(bc.lastConnectionProps)} Recovery finished with status: ${RecoveryProgress[r]}(${r})`, { recoveryReconnectionCounter, reconnectionDetectionCounter, connectionCounter: bc.connectionCounter, }); } switch (r) { case RecoveryProgress.none: case RecoveryProgress.inProgress: return; case RecoveryProgress.failedConnectedAckVerification: debouncedStartRecovery(bc, onCompl); return; case RecoveryProgress.partialRecoveryFailure: case RecoveryProgress.completedRecovery: recoverySubscriptions = undefined; break; default: break; } if (onCompl) { try { onCompl({ recoveryCounter: recoveryReconnectionCounter, lastRecoveryStatus: r, }, bc); } catch (e) { if (index_1.shouldLogErrors(bc.loglevel)) { index_1.logError(`UnhandledException error in onRecoveryComplete. Handler should wrap and handle exceptions instead of bubbling to BCjs level.`, e); } } } }); }, overrideRecoveryPeriod); const newOnConnectionStateChange = (state, bc) => { if (originalOnConnectionChange) { try { originalOnConnectionChange(state, bc); } catch (e) { // ...... original handler should have it's own policy for errors } } if (state === IBarracudaClient_1.BarracudaConnectionStatus.reconnecting && recoveryPolicy !== BarracudaRecoveryPolicy_1.BarracudaRecoveryPolicy.None) { if (!recoverySubscriptions) { recoverySubscriptions = bc.subscriptions; index_1.logInfo("recoverySubscriptions ===>", recoverySubscriptions); } ++reconnectionDetectionCounter; debouncedStartRecovery(bc, onRecoveryComplete); } }; const newBc = new BarracudaClient_1.BarracudaClient({ ...originalBC.defaultConnectionProps, onConnectionStateChange: newOnConnectionStateChange, }); if (index_1.isDebug(newBc.loglevel)) { index_1.logDebug(`${newBc.bcLog(newBc.lastConnectionProps)} new BarraucdaClient with recovery ${recoveryPolicy}`, { defaultConnectionProps: newBc.defaultConnectionProps }); } return newBc; } exports.withRecoveryPolicy = withRecoveryPolicy; var RecoveryProgress; (function (RecoveryProgress) { RecoveryProgress[RecoveryProgress["none"] = 0] = "none"; RecoveryProgress[RecoveryProgress["inProgress"] = 1] = "inProgress"; RecoveryProgress[RecoveryProgress["partialRecoveryFailure"] = 2] = "partialRecoveryFailure"; RecoveryProgress[RecoveryProgress["completedRecovery"] = 3] = "completedRecovery"; RecoveryProgress[RecoveryProgress["failedConnectedAckVerification"] = 4] = "failedConnectedAckVerification"; })(RecoveryProgress = exports.RecoveryProgress || (exports.RecoveryProgress = {})); let recoveryStatus = RecoveryProgress.none; function assertUnhandled(recoveryPolicy) { throw new Error(`Unsupported recovery case ${recoveryPolicy}`); } function recoverSubscriptions(bc, recoveryPolicy, baseSubscriptions) { var _a; if (index_1.isInfo(bc.loglevel)) { index_1.logInfo(`${bc.bcLog(bc.lastConnectionProps)} determining which subscriptions to reover ==> ${recoveryPolicy}`, index_1.isDebug(bc.loglevel) ? { baseSubscriptions } : undefined); } if (!verifyConnectedAck(bc)) { if (index_1.isWarn(bc.loglevel)) { index_1.logWarn(`${bc.bcLog(bc.lastConnectionProps)} failed verifyConnectedAck, skipping recovery.`, { connectionStatus: bc.connectionState, recoveryPolicy, baseSubscriptions, }); } return Promise.resolve(RecoveryProgress.failedConnectedAckVerification); } switch (recoveryStatus) { case RecoveryProgress.none: case RecoveryProgress.partialRecoveryFailure: case RecoveryProgress.completedRecovery: break; case RecoveryProgress.inProgress: if (index_1.isWarn(bc.loglevel)) { index_1.logWarn(`${bc.bcLog(bc.lastConnectionProps)} recovery already in progress. Ignoring.`, { recoveryStatus }); } return Promise.resolve(RecoveryProgress.inProgress); } let recoveryPolicyEnum; switch (typeof (recoveryPolicy)) { case "string": recoveryPolicyEnum = BarracudaRecoveryPolicy_1.BarracudaRecoveryPolicy[recoveryPolicy]; break; case "number": recoveryPolicyEnum = recoveryPolicy; break; } // const previousSessionSubscriptions: IBarracudaSubscriptionStatus[] = bc.previousSessionSubscriptions; const previousSessionSubscriptions = baseSubscriptions; let replaySubscriptions = previousSessionSubscriptions; switch (recoveryPolicyEnum) { case BarracudaRecoveryPolicy_1.BarracudaRecoveryPolicy.None: index_1.logInfo("Skipping recovery, policy set to none"); return Promise.resolve(RecoveryProgress.none); case BarracudaRecoveryPolicy_1.BarracudaRecoveryPolicy.ResubscribeAllInterruptedSubscriptions: replaySubscriptions = (_a = previousSessionSubscriptions === null || previousSessionSubscriptions === void 0 ? void 0 : previousSessionSubscriptions.filter(s => !s.unsubscribed && !s.excludeFromRecovery && s.requestCommandType)) === null || _a === void 0 ? void 0 : _a.map(s => ({ ...s, requestCommandType: subscribeOnly(s.requestCommandType), })); break; case BarracudaRecoveryPolicy_1.BarracudaRecoveryPolicy.ReplayAllInterruptedSubscriptions: replaySubscriptions = previousSessionSubscriptions === null || previousSessionSubscriptions === void 0 ? void 0 : previousSessionSubscriptions.filter(s => !s.unsubscribed); break; case BarracudaRecoveryPolicy_1.BarracudaRecoveryPolicy.ReplayAllRequests: replaySubscriptions = previousSessionSubscriptions; break; default: throw new Error(`Unsupported recovery case ${recoveryPolicy} --> ${recoveryPolicyEnum}`); } return executeSubscriptions(replaySubscriptions, bc, recoveryPolicy); } function executeSubscriptions(recoverSubcriptions, bc, recoveryPolicy) { var _a; if (index_1.isInfo(bc.loglevel)) { index_1.logInfo(`${bc.bcLog(bc.lastConnectionProps)} executeSubscriptions ==> ${recoveryPolicy}`, { recoverSubcriptions, }); } if (!verifyConnectedAck(bc)) { if (index_1.isWarn(bc.loglevel)) { index_1.logWarn(`${bc.bcLog(bc.lastConnectionProps)} failed verifyConnectedAck, skipping recovery.`, { connectionStatus: bc.connectionState, recoveryPolicy, }); } return Promise.resolve(RecoveryProgress.failedConnectedAckVerification); } if (!recoverSubcriptions || (recoverSubcriptions === null || recoverSubcriptions === void 0 ? void 0 : recoverSubcriptions.length) === 0) { if (index_1.isDebug(bc.loglevel)) { index_1.logDebug(`${bc.bcLog(bc.lastConnectionProps)} No subscriptions to recover.`, { recoverSubcriptions, previousSessionSubscriptions: bc.previousSessionSubscriptions, }); } return Promise.resolve(RecoveryProgress.none); } else { const recoveryOperations = (_a = recoverSubcriptions === null || recoverSubcriptions === void 0 ? void 0 : recoverSubcriptions.filter(sub => sub.requestCommandType && sub.requestBarracudaQuery)) === null || _a === void 0 ? void 0 : _a.map(sub => { return bc.subscribeQuery(sub.requestCommandType, sub.requestBarracudaQuery); }); const allRecoveryPromises = Promise.all(recoveryOperations) .then(recoveryOperationResults => { if (index_1.isInfo(bc.loglevel)) { index_1.logInfo(`${bc.bcLog(bc.lastConnectionProps)} Executing recovery ${recoveryPolicy} finished`, { recoveryOperationResults }); } recoveryStatus = RecoveryProgress.completedRecovery; return recoveryStatus; }) .catch(error => { if (index_1.shouldLogErrors(bc.loglevel)) { index_1.logError(`${bc.bcLog(bc.lastConnectionProps)} Error while executing recovery ${recoveryPolicy}`, error); } recoveryStatus = RecoveryProgress.partialRecoveryFailure; return recoveryStatus; }); return allRecoveryPromises; } } function verifyConnectedAck(bc) { if (bc.connectionState !== IBarracudaClient_1.BarracudaConnectionStatus.connectedAck) { const errMsg = `${bc.bcLog(bc.lastConnectionProps)} verification failed: not in ConnectedAck state [${bc.connectionState}]. Canceling recovery..`; if (index_1.shouldLogErrors(bc.loglevel)) { index_1.logError(errMsg); } return false; } return true; } function subscribeOnly(requestCommandType) { switch (requestCommandType) { case BarracudaClient_1.BarracudaBridgeReadQueryCommands.snapshot: return BarracudaClient_1.BarracudaBridgeReadQueryCommands.snapshot; case BarracudaClient_1.BarracudaBridgeReadQueryCommands.snapshotAndSubscribe: case BarracudaClient_1.BarracudaBridgeReadQueryCommands.subscribe: return BarracudaClient_1.BarracudaBridgeReadQueryCommands.subscribe; case BarracudaClient_1.BarracudaBridgeReadQueryCommands.snapshotAndDiffSubscribe: case BarracudaClient_1.BarracudaBridgeReadQueryCommands.diffSubscribe: return BarracudaClient_1.BarracudaBridgeReadQueryCommands.diffSubscribe; default: return requestCommandType; } }