Skip to content

Commit a4fc0b3

Browse files
committed
feat(core): Introduce New Transports API
1 parent f7e2796 commit a4fc0b3

File tree

2 files changed

+492
-0
lines changed

2 files changed

+492
-0
lines changed

packages/core/src/transports/base.ts

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import { Envelope, EventStatus } from '@sentry/types';
2+
import {
3+
disabledUntil,
4+
eventStatusFromHttpCode,
5+
isRateLimited,
6+
makePromiseBuffer,
7+
PromiseBuffer,
8+
RateLimits,
9+
rejectedSyncPromise,
10+
resolvedSyncPromise,
11+
SentryError,
12+
serializeEnvelope,
13+
updateRateLimits,
14+
} from '@sentry/utils';
15+
16+
export const ERROR_TRANSPORT_CATEGORY = 'error';
17+
18+
export const TRANSACTION_TRANSPORT_CATEGORY = 'transaction';
19+
20+
export const ATTACHMENT_TRANSPORT_CATEGORY = 'attachment';
21+
22+
export const SESSION_TRANSPORT_CATEGORY = 'session';
23+
24+
type TransportCategory =
25+
| typeof ERROR_TRANSPORT_CATEGORY
26+
| typeof TRANSACTION_TRANSPORT_CATEGORY
27+
| typeof ATTACHMENT_TRANSPORT_CATEGORY
28+
| typeof SESSION_TRANSPORT_CATEGORY;
29+
30+
export type TransportRequest = {
31+
body: string;
32+
category: TransportCategory;
33+
};
34+
35+
export type TransportMakeRequestResponse = {
36+
body?: string;
37+
headers?: Record<string, string | null>;
38+
reason?: string;
39+
statusCode: number;
40+
};
41+
42+
export type TransportResponse = {
43+
status: EventStatus;
44+
reason?: string;
45+
};
46+
47+
export interface BaseTransportOptions {
48+
// url to send the event
49+
// transport does not care about dsn specific - client should take care of
50+
// parsing and figuring that out
51+
url: string;
52+
bufferSize?: number;
53+
}
54+
55+
// TODO: Move into Browser Transport
56+
export interface BrowserTransportOptions extends BaseTransportOptions {
57+
// options to pass into fetch request
58+
fetchParams: Record<string, string>;
59+
headers?: Record<string, string>;
60+
sendClientReports?: boolean;
61+
}
62+
63+
// TODO: Move into Node transport
64+
export interface NodeTransportOptions extends BaseTransportOptions {
65+
// Set a HTTP proxy that should be used for outbound requests.
66+
httpProxy?: string;
67+
// Set a HTTPS proxy that should be used for outbound requests.
68+
httpsProxy?: string;
69+
// HTTPS proxy certificates path
70+
caCerts?: string;
71+
}
72+
73+
interface NewTransport {
74+
// If `$` is set, we know that this is a new transport.
75+
// TODO(v7): Remove this as we will no longer have split between
76+
// old and new transports.
77+
$: boolean;
78+
send(request: Envelope, category: TransportCategory): PromiseLike<TransportResponse>;
79+
flush(timeout: number): PromiseLike<boolean>;
80+
}
81+
82+
type MakeTransportRequest = (request: TransportRequest) => PromiseLike<TransportMakeRequestResponse>;
83+
84+
export const DEFAULT_TRANSPORT_BUFFER_SIZE = 30;
85+
86+
/**
87+
* Creates a `NewTransport`
88+
*
89+
* @param options
90+
* @param makeRequest
91+
*/
92+
export function createTransport<O extends BaseTransportOptions>(
93+
options: O,
94+
makeRequest: MakeTransportRequest,
95+
buffer: PromiseBuffer<TransportResponse> = makePromiseBuffer(options.bufferSize || DEFAULT_TRANSPORT_BUFFER_SIZE),
96+
): NewTransport {
97+
let rateLimits: RateLimits = {};
98+
99+
const flush = (timeout?: number): PromiseLike<boolean> => buffer.drain(timeout);
100+
101+
function send(envelope: Envelope, category: TransportCategory): PromiseLike<TransportResponse> {
102+
const request: TransportRequest = {
103+
category,
104+
body: serializeEnvelope(envelope),
105+
};
106+
107+
// Don't add to buffer if transport is already rate-limited
108+
if (isRateLimited(rateLimits, category)) {
109+
return rejectedSyncPromise({
110+
status: 'rate_limit',
111+
reason: getRateLimitReason(rateLimits, category),
112+
});
113+
}
114+
115+
const requestTask = (): PromiseLike<TransportResponse> =>
116+
makeRequest(request).then(({ body, headers, reason, statusCode }): PromiseLike<TransportResponse> => {
117+
const status = eventStatusFromHttpCode(statusCode);
118+
if (headers) {
119+
rateLimits = updateRateLimits(rateLimits, headers);
120+
}
121+
if (status === 'success') {
122+
return resolvedSyncPromise({ status, reason });
123+
}
124+
return rejectedSyncPromise({
125+
status,
126+
reason:
127+
body || reason || status === 'rate_limit'
128+
? getRateLimitReason(rateLimits, category)
129+
: 'Unknown transport error',
130+
});
131+
});
132+
133+
return buffer.add(requestTask);
134+
}
135+
136+
return {
137+
$: true,
138+
send,
139+
flush,
140+
};
141+
}
142+
143+
function getRateLimitReason(rateLimits: RateLimits, category: TransportCategory): string {
144+
return `Too many ${category} requests, backing off until: ${new Date(disabledUntil(rateLimits, category))}`;
145+
}

0 commit comments

Comments
 (0)