{"version":3,"file":"offline.js","sources":["../../../src/transports/offline.ts"],"sourcesContent":["import type { Envelope, InternalBaseTransportOptions, Transport, TransportMakeRequestResponse } from '@sentry/types';\nimport { envelopeContainsItemType, logger, parseRetryAfterHeader } from '@sentry/utils';\n\nimport { DEBUG_BUILD } from '../debug-build';\n\nexport const MIN_DELAY = 100; // 100 ms\nexport const START_DELAY = 5_000; // 5 seconds\nconst MAX_DELAY = 3.6e6; // 1 hour\n\nexport interface OfflineStore {\n push(env: Envelope): Promise;\n unshift(env: Envelope): Promise;\n shift(): Promise;\n}\n\nexport type CreateOfflineStore = (options: OfflineTransportOptions) => OfflineStore;\n\nexport interface OfflineTransportOptions extends InternalBaseTransportOptions {\n /**\n * A function that creates the offline store instance.\n */\n createStore?: CreateOfflineStore;\n\n /**\n * Flush the offline store shortly after startup.\n *\n * Defaults: false\n */\n flushAtStartup?: boolean;\n\n /**\n * Called before an event is stored.\n *\n * Return false to drop the envelope rather than store it.\n *\n * @param envelope The envelope that failed to send.\n * @param error The error that occurred.\n * @param retryDelay The current retry delay in milliseconds.\n */\n shouldStore?: (envelope: Envelope, error: Error, retryDelay: number) => boolean | Promise;\n}\n\ntype Timer = number | { unref?: () => void };\n\n/**\n * Wraps a transport and stores and retries events when they fail to send.\n *\n * @param createTransport The transport to wrap.\n */\nexport function makeOfflineTransport(\n createTransport: (options: TO) => Transport,\n): (options: TO & OfflineTransportOptions) => Transport {\n function log(...args: unknown[]): void {\n DEBUG_BUILD && logger.info('[Offline]:', ...args);\n }\n\n return options => {\n const transport = createTransport(options);\n\n if (!options.createStore) {\n throw new Error('No `createStore` function was provided');\n }\n\n const store = options.createStore(options);\n\n let retryDelay = START_DELAY;\n let flushTimer: Timer | undefined;\n\n function shouldQueue(env: Envelope, error: Error, retryDelay: number): boolean | Promise {\n // We want to drop client reports because they can be generated when we retry sending events while offline.\n if (envelopeContainsItemType(env, ['client_report'])) {\n return false;\n }\n\n if (options.shouldStore) {\n return options.shouldStore(env, error, retryDelay);\n }\n\n return true;\n }\n\n function flushIn(delay: number): void {\n if (flushTimer) {\n clearTimeout(flushTimer as ReturnType);\n }\n\n flushTimer = setTimeout(async () => {\n flushTimer = undefined;\n\n const found = await store.shift();\n if (found) {\n log('Attempting to send previously queued event');\n\n // We should to update the sent_at timestamp to the current time.\n found[0].sent_at = new Date().toISOString();\n\n void send(found, true).catch(e => {\n log('Failed to retry sending', e);\n });\n }\n }, delay) as Timer;\n\n // We need to unref the timer in node.js, otherwise the node process never exit.\n if (typeof flushTimer !== 'number' && flushTimer.unref) {\n flushTimer.unref();\n }\n }\n\n function flushWithBackOff(): void {\n if (flushTimer) {\n return;\n }\n\n flushIn(retryDelay);\n\n retryDelay = Math.min(retryDelay * 2, MAX_DELAY);\n }\n\n async function send(envelope: Envelope, isRetry: boolean = false): Promise {\n // We queue all replay envelopes to avoid multiple replay envelopes being sent at the same time. If one fails, we\n // need to retry them in order.\n if (!isRetry && envelopeContainsItemType(envelope, ['replay_event', 'replay_recording'])) {\n await store.push(envelope);\n flushIn(MIN_DELAY);\n return {};\n }\n\n try {\n const result = await transport.send(envelope);\n\n let delay = MIN_DELAY;\n\n if (result) {\n // If there's a retry-after header, use that as the next delay.\n if (result.headers && result.headers['retry-after']) {\n delay = parseRetryAfterHeader(result.headers['retry-after']);\n } else if (result.headers && result.headers['x-sentry-rate-limits']) {\n delay = 60_000; // 60 seconds\n } // If we have a server error, return now so we don't flush the queue.\n else if ((result.statusCode || 0) >= 400) {\n return result;\n }\n }\n\n flushIn(delay);\n retryDelay = START_DELAY;\n return result;\n } catch (e) {\n if (await shouldQueue(envelope, e as Error, retryDelay)) {\n // If this envelope was a retry, we want to add it to the front of the queue so it's retried again first.\n if (isRetry) {\n await store.unshift(envelope);\n } else {\n await store.push(envelope);\n }\n flushWithBackOff();\n log('Error sending. Event queued.', e as Error);\n return {};\n } else {\n throw e;\n }\n }\n }\n\n if (options.flushAtStartup) {\n flushWithBackOff();\n }\n\n return {\n send,\n flush: t => transport.flush(t),\n };\n };\n}\n"],"names":[],"mappings":"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"}