Skip to content

Commit f0b49b4

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

File tree

2 files changed

+491
-0
lines changed

2 files changed

+491
-0
lines changed

packages/core/src/transports/base.ts

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

0 commit comments

Comments
 (0)