UNPKG

@segment/analytics-next

Version:

Analytics Next (aka Analytics 2.0) is the latest version of Segment’s JavaScript SDK - enabling you to send your data to any tool without having to learn, test, or use a new API every time.

155 lines 6.24 kB
import { __assign, __awaiter, __generator, __rest } from "tslib"; import { fetch } from '../../lib/fetch'; import { onPageChange } from '../../lib/on-page-change'; import { RateLimitError } from './ratelimit-error'; import { Context } from '../../core/context'; var MAX_PAYLOAD_SIZE = 500; var MAX_KEEPALIVE_SIZE = 64; function kilobytes(buffer) { var size = encodeURI(JSON.stringify(buffer)).split(/%..|./).length - 1; return size / 1024; } /** * Checks if the payload is over or close to * the maximum payload size allowed by tracking * API. */ function approachingTrackingAPILimit(buffer) { return kilobytes(buffer) >= MAX_PAYLOAD_SIZE - 50; } /** * Checks if payload is over or approaching the limit for keepalive * requests. If keepalive is enabled we want to avoid * going over this to prevent data loss. */ function passedKeepaliveLimit(buffer) { return kilobytes(buffer) >= MAX_KEEPALIVE_SIZE - 10; } function chunks(batch) { var result = []; var index = 0; batch.forEach(function (item) { var size = kilobytes(result[index]); if (size >= 64) { index++; } if (result[index]) { result[index].push(item); } else { result[index] = [item]; } }); return result; } export default function batch(apiHost, config) { var _a, _b; var buffer = []; var pageUnloaded = false; var limit = (_a = config === null || config === void 0 ? void 0 : config.size) !== null && _a !== void 0 ? _a : 10; var timeout = (_b = config === null || config === void 0 ? void 0 : config.timeout) !== null && _b !== void 0 ? _b : 5000; var rateLimitTimeout = 0; function sendBatch(batch) { var _a; if (batch.length === 0) { return; } var writeKey = (_a = batch[0]) === null || _a === void 0 ? void 0 : _a.writeKey; // Remove sentAt from every event as batching only needs a single timestamp var updatedBatch = batch.map(function (event) { var _a = event, sentAt = _a.sentAt, newEvent = __rest(_a, ["sentAt"]); return newEvent; }); return fetch("https://".concat(apiHost, "/b"), { keepalive: (config === null || config === void 0 ? void 0 : config.keepalive) || pageUnloaded, headers: { 'Content-Type': 'text/plain', }, method: 'post', body: JSON.stringify({ writeKey: writeKey, batch: updatedBatch, sentAt: new Date().toISOString(), }), }).then(function (res) { var _a; if (res.status >= 500) { throw new Error("Bad response from server: ".concat(res.status)); } if (res.status === 429) { var retryTimeoutStringSecs = (_a = res.headers) === null || _a === void 0 ? void 0 : _a.get('x-ratelimit-reset'); var retryTimeoutMS = typeof retryTimeoutStringSecs == 'string' ? parseInt(retryTimeoutStringSecs) * 1000 : timeout; throw new RateLimitError("Rate limit exceeded: ".concat(res.status), retryTimeoutMS); } }); } function flush(attempt) { var _a; if (attempt === void 0) { attempt = 1; } return __awaiter(this, void 0, void 0, function () { var batch_1; return __generator(this, function (_b) { if (buffer.length) { batch_1 = buffer; buffer = []; return [2 /*return*/, (_a = sendBatch(batch_1)) === null || _a === void 0 ? void 0 : _a.catch(function (error) { var _a; var ctx = Context.system(); ctx.log('error', 'Error sending batch', error); if (attempt <= ((_a = config === null || config === void 0 ? void 0 : config.maxRetries) !== null && _a !== void 0 ? _a : 10)) { if (error.name === 'RateLimitError') { rateLimitTimeout = error.retryTimeout; } buffer.push.apply(buffer, batch_1); buffer.map(function (event) { if ('_metadata' in event) { var segmentEvent = event; segmentEvent._metadata = __assign(__assign({}, segmentEvent._metadata), { retryCount: attempt }); } }); scheduleFlush(attempt + 1); } })]; } return [2 /*return*/]; }); }); } var schedule; function scheduleFlush(attempt) { if (attempt === void 0) { attempt = 1; } if (schedule) { return; } schedule = setTimeout(function () { schedule = undefined; flush(attempt).catch(console.error); }, rateLimitTimeout ? rateLimitTimeout : timeout); rateLimitTimeout = 0; } onPageChange(function (unloaded) { pageUnloaded = unloaded; if (pageUnloaded && buffer.length) { var reqs = chunks(buffer).map(sendBatch); Promise.all(reqs).catch(console.error); } }); function dispatch(_url, body) { return __awaiter(this, void 0, void 0, function () { var bufferOverflow; return __generator(this, function (_a) { buffer.push(body); bufferOverflow = buffer.length >= limit || approachingTrackingAPILimit(buffer) || ((config === null || config === void 0 ? void 0 : config.keepalive) && passedKeepaliveLimit(buffer)); return [2 /*return*/, bufferOverflow || pageUnloaded ? flush() : scheduleFlush()]; }); }); } return { dispatch: dispatch, }; } //# sourceMappingURL=batched-dispatcher.js.map