@segment/analytics-core
Version:
This package represents core 'shared' functionality that is shared by analytics packages. This is not designed to be used directly, but internal to analytics-node and analytics-browser.
347 lines • 16.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.CoreEventQueue = void 0;
var tslib_1 = require("tslib");
var group_by_1 = require("../utils/group-by");
var priority_queue_1 = require("../priority-queue");
var context_1 = require("../context");
var analytics_generic_utils_1 = require("@segment/analytics-generic-utils");
var task_group_1 = require("../task/task-group");
var delivery_1 = require("./delivery");
var CoreEventQueue = /** @class */ (function (_super) {
tslib_1.__extends(CoreEventQueue, _super);
function CoreEventQueue(priorityQueue) {
var _this = _super.call(this) || this;
/**
* All event deliveries get suspended until all the tasks in this task group are complete.
* For example: a middleware that augments the event object should be loaded safely as a
* critical task, this way, event queue will wait for it to be ready before sending events.
*
* This applies to all the events already in the queue, and the upcoming ones
*/
_this.criticalTasks = (0, task_group_1.createTaskGroup)();
_this.plugins = [];
_this.failedInitializations = [];
_this.flushing = false;
_this.queue = priorityQueue;
_this.queue.on(priority_queue_1.ON_REMOVE_FROM_FUTURE, function () {
_this.scheduleFlush(0);
});
return _this;
}
CoreEventQueue.prototype.register = function (ctx, plugin, instance) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var handleLoadError, err_1;
var _this = this;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
this.plugins.push(plugin);
handleLoadError = function (err) {
_this.failedInitializations.push(plugin.name);
_this.emit('initialization_failure', plugin);
console.warn(plugin.name, err);
ctx.log('warn', 'Failed to load destination', {
plugin: plugin.name,
error: err,
});
// Filter out the failed plugin by excluding it from the list
_this.plugins = _this.plugins.filter(function (p) { return p !== plugin; });
};
if (!(plugin.type === 'destination' && plugin.name !== 'Segment.io')) return [3 /*break*/, 1];
plugin.load(ctx, instance).catch(handleLoadError);
return [3 /*break*/, 4];
case 1:
_a.trys.push([1, 3, , 4]);
return [4 /*yield*/, plugin.load(ctx, instance)];
case 2:
_a.sent();
return [3 /*break*/, 4];
case 3:
err_1 = _a.sent();
handleLoadError(err_1);
return [3 /*break*/, 4];
case 4: return [2 /*return*/];
}
});
});
};
CoreEventQueue.prototype.deregister = function (ctx, plugin, instance) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var e_1;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 3, , 4]);
if (!plugin.unload) return [3 /*break*/, 2];
return [4 /*yield*/, Promise.resolve(plugin.unload(ctx, instance))];
case 1:
_a.sent();
_a.label = 2;
case 2:
this.plugins = this.plugins.filter(function (p) { return p.name !== plugin.name; });
return [3 /*break*/, 4];
case 3:
e_1 = _a.sent();
ctx.log('warn', 'Failed to unload destination', {
plugin: plugin.name,
error: e_1,
});
return [3 /*break*/, 4];
case 4: return [2 /*return*/];
}
});
});
};
CoreEventQueue.prototype.dispatch = function (ctx) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var willDeliver;
return tslib_1.__generator(this, function (_a) {
ctx.log('debug', 'Dispatching');
ctx.stats.increment('message_dispatched');
this.queue.push(ctx);
willDeliver = this.subscribeToDelivery(ctx);
this.scheduleFlush(0);
return [2 /*return*/, willDeliver];
});
});
};
CoreEventQueue.prototype.subscribeToDelivery = function (ctx) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var _this = this;
return tslib_1.__generator(this, function (_a) {
return [2 /*return*/, new Promise(function (resolve) {
var onDeliver = function (flushed, delivered) {
if (flushed.isSame(ctx)) {
_this.off('flush', onDeliver);
if (delivered) {
resolve(flushed);
}
else {
resolve(flushed);
}
}
};
_this.on('flush', onDeliver);
})];
});
});
};
CoreEventQueue.prototype.dispatchSingle = function (ctx) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var _this = this;
return tslib_1.__generator(this, function (_a) {
ctx.log('debug', 'Dispatching');
ctx.stats.increment('message_dispatched');
this.queue.updateAttempts(ctx);
ctx.attempts = 1;
return [2 /*return*/, this.deliver(ctx).catch(function (err) {
var accepted = _this.enqueuRetry(err, ctx);
if (!accepted) {
ctx.setFailedDelivery({ reason: err });
return ctx;
}
return _this.subscribeToDelivery(ctx);
})];
});
});
};
CoreEventQueue.prototype.isEmpty = function () {
return this.queue.length === 0;
};
CoreEventQueue.prototype.scheduleFlush = function (timeout) {
var _this = this;
if (timeout === void 0) { timeout = 500; }
if (this.flushing) {
return;
}
this.flushing = true;
setTimeout(function () {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
_this.flush().then(function () {
setTimeout(function () {
_this.flushing = false;
if (_this.queue.length) {
_this.scheduleFlush(0);
}
}, 0);
});
}, timeout);
};
CoreEventQueue.prototype.deliver = function (ctx) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var start, done, err_2, error;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.criticalTasks.done()];
case 1:
_a.sent();
start = Date.now();
_a.label = 2;
case 2:
_a.trys.push([2, 4, , 5]);
return [4 /*yield*/, this.flushOne(ctx)];
case 3:
ctx = _a.sent();
done = Date.now() - start;
this.emit('delivery_success', ctx);
ctx.stats.gauge('delivered', done);
ctx.log('debug', 'Delivered', ctx.event);
return [2 /*return*/, ctx];
case 4:
err_2 = _a.sent();
error = err_2;
ctx.log('error', 'Failed to deliver', error);
this.emit('delivery_failure', ctx, error);
ctx.stats.increment('delivery_failed');
throw err_2;
case 5: return [2 /*return*/];
}
});
});
};
CoreEventQueue.prototype.enqueuRetry = function (err, ctx) {
var retriable = !(err instanceof context_1.ContextCancelation) || err.retry;
if (!retriable) {
return false;
}
return this.queue.pushWithBackoff(ctx);
};
CoreEventQueue.prototype.flush = function () {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var ctx, err_3, accepted;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
if (this.queue.length === 0) {
return [2 /*return*/, []];
}
ctx = this.queue.pop();
if (!ctx) {
return [2 /*return*/, []];
}
ctx.attempts = this.queue.getAttempts(ctx);
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 4]);
return [4 /*yield*/, this.deliver(ctx)];
case 2:
ctx = _a.sent();
this.emit('flush', ctx, true);
return [3 /*break*/, 4];
case 3:
err_3 = _a.sent();
accepted = this.enqueuRetry(err_3, ctx);
if (!accepted) {
ctx.setFailedDelivery({ reason: err_3 });
this.emit('flush', ctx, false);
}
return [2 /*return*/, []];
case 4: return [2 /*return*/, [ctx]];
}
});
});
};
CoreEventQueue.prototype.isReady = function () {
// return this.plugins.every((p) => p.isLoaded())
// should we wait for every plugin to load?
return true;
};
CoreEventQueue.prototype.availableExtensions = function (denyList) {
var available = this.plugins.filter(function (p) {
var _a, _b, _c;
// Only filter out destination plugins or the Segment.io plugin
if (p.type !== 'destination' && p.name !== 'Segment.io') {
return true;
}
var alternativeNameMatch = undefined;
(_a = p.alternativeNames) === null || _a === void 0 ? void 0 : _a.forEach(function (name) {
if (denyList[name] !== undefined) {
alternativeNameMatch = denyList[name];
}
});
// Explicit integration option takes precedence, `All: false` does not apply to Segment.io
return ((_c = (_b = denyList[p.name]) !== null && _b !== void 0 ? _b : alternativeNameMatch) !== null && _c !== void 0 ? _c : (p.name === 'Segment.io' ? true : denyList.All) !== false);
});
var _a = (0, group_by_1.groupBy)(available, 'type'), _b = _a.before, before = _b === void 0 ? [] : _b, _c = _a.enrichment, enrichment = _c === void 0 ? [] : _c, _d = _a.destination, destination = _d === void 0 ? [] : _d, _e = _a.after, after = _e === void 0 ? [] : _e;
return {
before: before,
enrichment: enrichment,
destinations: destination,
after: after,
};
};
CoreEventQueue.prototype.flushOne = function (ctx) {
var _a, _b;
return tslib_1.__awaiter(this, void 0, void 0, function () {
var _c, before, enrichment, _i, before_1, beforeWare, temp, _d, enrichment_1, enrichmentWare, temp, _e, destinations, after, afterCalls;
return tslib_1.__generator(this, function (_f) {
switch (_f.label) {
case 0:
if (!this.isReady()) {
throw new Error('Not ready');
}
if (ctx.attempts > 1) {
this.emit('delivery_retry', ctx);
}
_c = this.availableExtensions((_a = ctx.event.integrations) !== null && _a !== void 0 ? _a : {}), before = _c.before, enrichment = _c.enrichment;
_i = 0, before_1 = before;
_f.label = 1;
case 1:
if (!(_i < before_1.length)) return [3 /*break*/, 4];
beforeWare = before_1[_i];
return [4 /*yield*/, (0, delivery_1.ensure)(ctx, beforeWare)];
case 2:
temp = _f.sent();
if (temp instanceof context_1.CoreContext) {
ctx = temp;
}
this.emit('message_enriched', ctx, beforeWare);
_f.label = 3;
case 3:
_i++;
return [3 /*break*/, 1];
case 4:
_d = 0, enrichment_1 = enrichment;
_f.label = 5;
case 5:
if (!(_d < enrichment_1.length)) return [3 /*break*/, 8];
enrichmentWare = enrichment_1[_d];
return [4 /*yield*/, (0, delivery_1.attempt)(ctx, enrichmentWare)];
case 6:
temp = _f.sent();
if (temp instanceof context_1.CoreContext) {
ctx = temp;
}
this.emit('message_enriched', ctx, enrichmentWare);
_f.label = 7;
case 7:
_d++;
return [3 /*break*/, 5];
case 8:
_e = this.availableExtensions((_b = ctx.event.integrations) !== null && _b !== void 0 ? _b : {}), destinations = _e.destinations, after = _e.after;
return [4 /*yield*/, new Promise(function (resolve, reject) {
setTimeout(function () {
var attempts = destinations.map(function (destination) {
return (0, delivery_1.attempt)(ctx, destination);
});
Promise.all(attempts).then(resolve).catch(reject);
}, 0);
})];
case 9:
_f.sent();
ctx.stats.increment('message_delivered');
this.emit('message_delivered', ctx);
afterCalls = after.map(function (after) { return (0, delivery_1.attempt)(ctx, after); });
return [4 /*yield*/, Promise.all(afterCalls)];
case 10:
_f.sent();
return [2 /*return*/, ctx];
}
});
});
};
return CoreEventQueue;
}(analytics_generic_utils_1.Emitter));
exports.CoreEventQueue = CoreEventQueue;
//# sourceMappingURL=event-queue.js.map