Skip to content

Commit 3ba2356

Browse files
committed
in progress
1 parent 57b3ccb commit 3ba2356

File tree

2 files changed

+124
-0
lines changed

2 files changed

+124
-0
lines changed

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export { getEnvelopeEndpointWithUrlEncodedAuth, getReportDialogEndpoint } from '
2424
export { BaseClient } from './baseclient';
2525
export { initAndBind } from './sdk';
2626
export { createTransport } from './transports/base';
27+
export { makeOfflineTransport } from './transports/offline';
2728
export { SDK_VERSION } from './version';
2829
export { getIntegrationsToSetup } from './integration';
2930
export { FunctionToString, InboundFilters } from './integrations';
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import type { Envelope, InternalBaseTransportOptions, Transport, TransportMakeRequestResponse } from '@sentry/types';
2+
3+
function wasRateLimited(result: TransportMakeRequestResponse): boolean {
4+
return !!(result.headers && 'x-sentry-rate-limits' in result.headers);
5+
}
6+
7+
type BeforeSendResponse = 'send' | 'queue' | 'drop';
8+
9+
interface OfflineTransportOptions extends InternalBaseTransportOptions {
10+
/**
11+
* The maximum number of days to keep an event in the queue.
12+
*/
13+
maxQueueAgeDays?: number;
14+
15+
/**
16+
* The maximum number of events to keep in the queue.
17+
*/
18+
maxQueueCount?: number;
19+
20+
/**
21+
* Called every time the number of requests in the queue changes.
22+
*/
23+
queuedLengthChanged?: (length: number) => void;
24+
25+
/**
26+
* Called before attempting to send an event to Sentry.
27+
*
28+
* Return 'send' to attempt to send the event.
29+
* Return 'queue' to queue the event for sending later.
30+
* Return 'drop' to drop the event.
31+
*/
32+
beforeSend?: (request: Envelope) => BeforeSendResponse | Promise<BeforeSendResponse>;
33+
}
34+
35+
interface OfflineTransportStore {
36+
add(env: Envelope): Promise<number>;
37+
pop(): Promise<[Envelope | undefined, number]>;
38+
}
39+
40+
const START_DELAY = 5_000;
41+
const MAX_DELAY = 2_000_000_000;
42+
43+
/** */
44+
export function makeOfflineTransport<TO>(
45+
transport: (options: TO) => Transport,
46+
store: OfflineTransportStore,
47+
): (options: TO & OfflineTransportOptions) => Transport {
48+
return (options: TO & OfflineTransportOptions) => {
49+
const baseTransport = transport(options);
50+
51+
let retryDelay = START_DELAY;
52+
let lastQueueLength = -1;
53+
54+
function queueLengthChanged(length: number): void {
55+
if (options.queuedLengthChanged && length !== lastQueueLength) {
56+
lastQueueLength = length;
57+
options.queuedLengthChanged(length);
58+
}
59+
}
60+
61+
function queueRequest(envelope: Envelope): Promise<void> {
62+
return store.add(envelope).then(count => {
63+
queueLengthChanged(count);
64+
65+
setTimeout(() => {
66+
flushQueue();
67+
}, retryDelay);
68+
69+
retryDelay *= 3;
70+
71+
// If the delay is bigger than 2^31 (max signed 32-bit int), setTimeout throws
72+
// an error on node.js and falls back to 1 which can cause a huge number of requests.
73+
if (retryDelay > MAX_DELAY) {
74+
retryDelay = MAX_DELAY;
75+
}
76+
});
77+
}
78+
79+
function flushQueue(): void {
80+
void store.pop().then(([found, count]) => {
81+
if (found) {
82+
// We have pending plus just found
83+
queueLengthChanged(count + 1);
84+
void send(found);
85+
} else {
86+
queueLengthChanged(0);
87+
}
88+
});
89+
}
90+
91+
// eslint-disable-next-line @sentry-internal/sdk/no-async-await
92+
async function send(request: Envelope): Promise<void | TransportMakeRequestResponse> {
93+
let action = (await options.beforeSend?.(request)) || 'send';
94+
95+
if (action === 'send') {
96+
try {
97+
const result = await baseTransport.send(request);
98+
if (!wasRateLimited(result || {})) {
99+
// Reset the retry delay
100+
retryDelay = START_DELAY;
101+
// We were successful so check the queue
102+
flushQueue();
103+
return result;
104+
}
105+
} catch (_) {
106+
//
107+
}
108+
action = 'queue';
109+
}
110+
111+
if (action == 'queue') {
112+
void queueRequest(request);
113+
}
114+
115+
return {};
116+
}
117+
118+
return {
119+
send,
120+
flush: (timeout?: number) => baseTransport.flush(timeout),
121+
};
122+
};
123+
}

0 commit comments

Comments
 (0)