UNPKG

@sentry/core

Version:
145 lines (117 loc) 4.1 kB
Object.defineProperty(exports, '__esModule', { value: true }); const utils = require('@sentry/utils'); const debugBuild = require('../debug-build.js'); const MIN_DELAY = 100; // 100 ms const START_DELAY = 5000; // 5 seconds const MAX_DELAY = 3.6e6; // 1 hour /** * Wraps a transport and stores and retries events when they fail to send. * * @param createTransport The transport to wrap. */ function makeOfflineTransport( createTransport, ) { function log(...args) { debugBuild.DEBUG_BUILD && utils.logger.info('[Offline]:', ...args); } return options => { const transport = createTransport(options); if (!options.createStore) { throw new Error('No `createStore` function was provided'); } const store = options.createStore(options); let retryDelay = START_DELAY; let flushTimer; function shouldQueue(env, error, retryDelay) { // We want to drop client reports because they can be generated when we retry sending events while offline. if (utils.envelopeContainsItemType(env, ['client_report'])) { return false; } if (options.shouldStore) { return options.shouldStore(env, error, retryDelay); } return true; } function flushIn(delay) { if (flushTimer) { clearTimeout(flushTimer ); } flushTimer = setTimeout(async () => { flushTimer = undefined; const found = await store.shift(); if (found) { log('Attempting to send previously queued event'); // We should to update the sent_at timestamp to the current time. found[0].sent_at = new Date().toISOString(); void send(found, true).catch(e => { log('Failed to retry sending', e); }); } }, delay) ; // We need to unref the timer in node.js, otherwise the node process never exit. if (typeof flushTimer !== 'number' && flushTimer.unref) { flushTimer.unref(); } } function flushWithBackOff() { if (flushTimer) { return; } flushIn(retryDelay); retryDelay = Math.min(retryDelay * 2, MAX_DELAY); } async function send(envelope, isRetry = false) { // We queue all replay envelopes to avoid multiple replay envelopes being sent at the same time. If one fails, we // need to retry them in order. if (!isRetry && utils.envelopeContainsItemType(envelope, ['replay_event', 'replay_recording'])) { await store.push(envelope); flushIn(MIN_DELAY); return {}; } try { const result = await transport.send(envelope); let delay = MIN_DELAY; if (result) { // If there's a retry-after header, use that as the next delay. if (result.headers && result.headers['retry-after']) { delay = utils.parseRetryAfterHeader(result.headers['retry-after']); } else if (result.headers && result.headers['x-sentry-rate-limits']) { delay = 60000; // 60 seconds } // If we have a server error, return now so we don't flush the queue. else if ((result.statusCode || 0) >= 400) { return result; } } flushIn(delay); retryDelay = START_DELAY; return result; } catch (e) { if (await shouldQueue(envelope, e , retryDelay)) { // If this envelope was a retry, we want to add it to the front of the queue so it's retried again first. if (isRetry) { await store.unshift(envelope); } else { await store.push(envelope); } flushWithBackOff(); log('Error sending. Event queued.', e ); return {}; } else { throw e; } } } if (options.flushAtStartup) { flushWithBackOff(); } return { send, flush: t => transport.flush(t), }; }; } exports.MIN_DELAY = MIN_DELAY; exports.START_DELAY = START_DELAY; exports.makeOfflineTransport = makeOfflineTransport; //# sourceMappingURL=offline.js.map