-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
Copy pathtransport.ts
105 lines (91 loc) · 3.43 KB
/
transport.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import type { BaseTransportOptions, Transport, TransportMakeRequestResponse, TransportRequest } from '@sentry/core';
import { SENTRY_BUFFER_FULL_ERROR, createTransport, suppressTracing } from '@sentry/core';
export interface CloudflareTransportOptions extends BaseTransportOptions {
/** Fetch API init parameters. */
fetchOptions?: RequestInit;
/** Custom headers for the transport. */
headers?: { [key: string]: string };
}
const DEFAULT_TRANSPORT_BUFFER_SIZE = 30;
/**
* This is a modified promise buffer that collects tasks until drain is called.
* We need this in the edge runtime because edge function invocations may not share I/O objects, like fetch requests
* and responses, and the normal PromiseBuffer inherently buffers stuff inbetween incoming requests.
*
* A limitation we need to be aware of is that DEFAULT_TRANSPORT_BUFFER_SIZE is the maximum amount of payloads the
* SDK can send for a given edge function invocation.
*/
export class IsolatedPromiseBuffer {
// We just have this field because the promise buffer interface requires it.
// If we ever remove it from the interface we should also remove it here.
public $: Array<PromiseLike<TransportMakeRequestResponse>>;
private _taskProducers: (() => PromiseLike<TransportMakeRequestResponse>)[];
private readonly _bufferSize: number;
public constructor(_bufferSize = DEFAULT_TRANSPORT_BUFFER_SIZE) {
this.$ = [];
this._taskProducers = [];
this._bufferSize = _bufferSize;
}
/**
* @inheritdoc
*/
public add(taskProducer: () => PromiseLike<TransportMakeRequestResponse>): PromiseLike<TransportMakeRequestResponse> {
if (this._taskProducers.length >= this._bufferSize) {
return Promise.reject(SENTRY_BUFFER_FULL_ERROR);
}
this._taskProducers.push(taskProducer);
return Promise.resolve({});
}
/**
* @inheritdoc
*/
public drain(timeout?: number): PromiseLike<boolean> {
const oldTaskProducers = [...this._taskProducers];
this._taskProducers = [];
return new Promise(resolve => {
const timer = setTimeout(() => {
if (timeout && timeout > 0) {
resolve(false);
}
}, timeout);
// This cannot reject
// eslint-disable-next-line @typescript-eslint/no-floating-promises
Promise.all(
oldTaskProducers.map(taskProducer =>
taskProducer().then(null, () => {
// catch all failed requests
}),
),
).then(() => {
// resolve to true if all fetch requests settled
clearTimeout(timer);
resolve(true);
});
});
}
}
/**
* Creates a Transport that uses the native fetch API to send events to Sentry.
*/
export function makeCloudflareTransport(options: CloudflareTransportOptions): Transport {
function makeRequest(request: TransportRequest): PromiseLike<TransportMakeRequestResponse> {
const requestOptions: RequestInit = {
body: request.body,
method: 'POST',
headers: options.headers,
...options.fetchOptions,
};
return suppressTracing(() => {
return fetch(options.url, requestOptions).then(response => {
return {
statusCode: response.status,
headers: {
'x-sentry-rate-limits': response.headers.get('X-Sentry-Rate-Limits'),
'retry-after': response.headers.get('Retry-After'),
},
};
});
});
}
return createTransport(options, makeRequest, new IsolatedPromiseBuffer(options.bufferSize));
}