barracuda-client-api
Version:
API Client to connect to Barracuda Enterprise Service Bus
258 lines (257 loc) • 13 kB
JavaScript
;
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;
}
}