Skip to content

Commit 58eec6a

Browse files
committed
feat(core): Introduce New Transports API
1 parent af7081c commit 58eec6a

File tree

2 files changed

+160
-0
lines changed

2 files changed

+160
-0
lines changed

packages/core/src/transports/base.ts

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

0 commit comments

Comments
 (0)