UNPKG

@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
"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