UNPKG

11.9 kBJavaScriptView Raw
1Object.defineProperty(exports, "__esModule", { value: true });
2var tslib_1 = require("tslib");
3var core_1 = require("@sentry/core");
4var types_1 = require("@sentry/types");
5var utils_1 = require("@sentry/utils");
6var fs = require("fs");
7var url_1 = require("url");
8var version_1 = require("../../version");
9var CATEGORY_MAPPING = {
10 event: 'error',
11 transaction: 'transaction',
12 session: 'session',
13 attachment: 'attachment',
14};
15/** Base Transport class implementation */
16var BaseTransport = /** @class */ (function () {
17 /** Create instance and set this.dsn */
18 function BaseTransport(options) {
19 this.options = options;
20 /** A simple buffer holding all requests. */
21 this._buffer = new utils_1.PromiseBuffer(30);
22 /** Locks transport after receiving rate limits in a response */
23 this._rateLimits = {};
24 /** Default function used to parse URLs */
25 this.urlParser = function (url) { return new url_1.URL(url); };
26 this._api = new core_1.API(options.dsn, options._metadata, options.tunnel);
27 }
28 /**
29 * @inheritDoc
30 */
31 BaseTransport.prototype.sendEvent = function (_) {
32 throw new utils_1.SentryError('Transport Class has to implement `sendEvent` method.');
33 };
34 /**
35 * @inheritDoc
36 */
37 BaseTransport.prototype.close = function (timeout) {
38 return this._buffer.drain(timeout);
39 };
40 /**
41 * Extracts proxy settings from client options and env variables.
42 *
43 * Honors `no_proxy` env variable with the highest priority to allow for hosts exclusion.
44 *
45 * An order of priority for available protocols is:
46 * `http` => `options.httpProxy` | `process.env.http_proxy`
47 * `https` => `options.httpsProxy` | `options.httpProxy` | `process.env.https_proxy` | `process.env.http_proxy`
48 */
49 BaseTransport.prototype._getProxy = function (protocol) {
50 var e_1, _a;
51 var _b = process.env, no_proxy = _b.no_proxy, http_proxy = _b.http_proxy, https_proxy = _b.https_proxy;
52 var _c = this.options, httpProxy = _c.httpProxy, httpsProxy = _c.httpsProxy;
53 var proxy = protocol === 'http' ? httpProxy || http_proxy : httpsProxy || httpProxy || https_proxy || http_proxy;
54 if (!no_proxy) {
55 return proxy;
56 }
57 var _d = this._api.getDsn(), host = _d.host, port = _d.port;
58 try {
59 for (var _e = tslib_1.__values(no_proxy.split(',')), _f = _e.next(); !_f.done; _f = _e.next()) {
60 var np = _f.value;
61 if (host.endsWith(np) || (host + ":" + port).endsWith(np)) {
62 return;
63 }
64 }
65 }
66 catch (e_1_1) { e_1 = { error: e_1_1 }; }
67 finally {
68 try {
69 if (_f && !_f.done && (_a = _e.return)) _a.call(_e);
70 }
71 finally { if (e_1) throw e_1.error; }
72 }
73 return proxy;
74 };
75 /** Returns a build request option object used by request */
76 BaseTransport.prototype._getRequestOptions = function (urlParts) {
77 var headers = tslib_1.__assign(tslib_1.__assign({}, this._api.getRequestHeaders(version_1.SDK_NAME, core_1.SDK_VERSION)), this.options.headers);
78 var hostname = urlParts.hostname, pathname = urlParts.pathname, port = urlParts.port, protocol = urlParts.protocol;
79 // See https://github.com/nodejs/node/blob/38146e717fed2fabe3aacb6540d839475e0ce1c6/lib/internal/url.js#L1268-L1290
80 // We ignore the query string on purpose
81 var path = "" + pathname;
82 return tslib_1.__assign({ agent: this.client, headers: headers,
83 hostname: hostname, method: 'POST', path: path,
84 port: port,
85 protocol: protocol }, (this.options.caCerts && {
86 ca: fs.readFileSync(this.options.caCerts),
87 }));
88 };
89 /**
90 * Gets the time that given category is disabled until for rate limiting
91 */
92 BaseTransport.prototype._disabledUntil = function (requestType) {
93 var category = CATEGORY_MAPPING[requestType];
94 return this._rateLimits[category] || this._rateLimits.all;
95 };
96 /**
97 * Checks if a category is rate limited
98 */
99 BaseTransport.prototype._isRateLimited = function (requestType) {
100 return this._disabledUntil(requestType) > new Date(Date.now());
101 };
102 /**
103 * Sets internal _rateLimits from incoming headers. Returns true if headers contains a non-empty rate limiting header.
104 */
105 BaseTransport.prototype._handleRateLimit = function (headers) {
106 var e_2, _a, e_3, _b;
107 var now = Date.now();
108 var rlHeader = headers['x-sentry-rate-limits'];
109 var raHeader = headers['retry-after'];
110 if (rlHeader) {
111 try {
112 // rate limit headers are of the form
113 // <header>,<header>,..
114 // where each <header> is of the form
115 // <retry_after>: <categories>: <scope>: <reason_code>
116 // where
117 // <retry_after> is a delay in ms
118 // <categories> is the event type(s) (error, transaction, etc) being rate limited and is of the form
119 // <category>;<category>;...
120 // <scope> is what's being limited (org, project, or key) - ignored by SDK
121 // <reason_code> is an arbitrary string like "org_quota" - ignored by SDK
122 for (var _c = tslib_1.__values(rlHeader.trim().split(',')), _d = _c.next(); !_d.done; _d = _c.next()) {
123 var limit = _d.value;
124 var parameters = limit.split(':', 2);
125 var headerDelay = parseInt(parameters[0], 10);
126 var delay = (!isNaN(headerDelay) ? headerDelay : 60) * 1000; // 60sec default
127 try {
128 for (var _e = (e_3 = void 0, tslib_1.__values((parameters[1] && parameters[1].split(';')) || ['all'])), _f = _e.next(); !_f.done; _f = _e.next()) {
129 var category = _f.value;
130 // categoriesAllowed is added here to ensure we are only storing rate limits for categories we support in this
131 // sdk and any categories that are not supported will not be added redundantly to the rateLimits object
132 var categoriesAllowed = tslib_1.__spread(Object.keys(CATEGORY_MAPPING).map(function (k) { return CATEGORY_MAPPING[k]; }), [
133 'all',
134 ]);
135 if (categoriesAllowed.includes(category))
136 this._rateLimits[category] = new Date(now + delay);
137 }
138 }
139 catch (e_3_1) { e_3 = { error: e_3_1 }; }
140 finally {
141 try {
142 if (_f && !_f.done && (_b = _e.return)) _b.call(_e);
143 }
144 finally { if (e_3) throw e_3.error; }
145 }
146 }
147 }
148 catch (e_2_1) { e_2 = { error: e_2_1 }; }
149 finally {
150 try {
151 if (_d && !_d.done && (_a = _c.return)) _a.call(_c);
152 }
153 finally { if (e_2) throw e_2.error; }
154 }
155 return true;
156 }
157 else if (raHeader) {
158 this._rateLimits.all = new Date(now + utils_1.parseRetryAfterHeader(now, raHeader));
159 return true;
160 }
161 return false;
162 };
163 /** JSDoc */
164 BaseTransport.prototype._send = function (sentryRequest, originalPayload) {
165 return tslib_1.__awaiter(this, void 0, void 0, function () {
166 var _this = this;
167 return tslib_1.__generator(this, function (_a) {
168 if (!this.module) {
169 throw new utils_1.SentryError('No module available');
170 }
171 if (originalPayload && this._isRateLimited(sentryRequest.type)) {
172 return [2 /*return*/, Promise.reject({
173 payload: originalPayload,
174 type: sentryRequest.type,
175 reason: "Transport for " + sentryRequest.type + " requests locked till " + this._disabledUntil(sentryRequest.type) + " due to too many requests.",
176 status: 429,
177 })];
178 }
179 if (!this._buffer.isReady()) {
180 return [2 /*return*/, Promise.reject(new utils_1.SentryError('Not adding Promise due to buffer limit reached.'))];
181 }
182 return [2 /*return*/, this._buffer.add(function () {
183 return new Promise(function (resolve, reject) {
184 if (!_this.module) {
185 throw new utils_1.SentryError('No module available');
186 }
187 var options = _this._getRequestOptions(_this.urlParser(sentryRequest.url));
188 var req = _this.module.request(options, function (res) {
189 var statusCode = res.statusCode || 500;
190 var status = types_1.Status.fromHttpCode(statusCode);
191 res.setEncoding('utf8');
192 /**
193 * "Key-value pairs of header names and values. Header names are lower-cased."
194 * https://nodejs.org/api/http.html#http_message_headers
195 */
196 var retryAfterHeader = res.headers ? res.headers['retry-after'] : '';
197 retryAfterHeader = (Array.isArray(retryAfterHeader) ? retryAfterHeader[0] : retryAfterHeader);
198 var rlHeader = res.headers ? res.headers['x-sentry-rate-limits'] : '';
199 rlHeader = (Array.isArray(rlHeader) ? rlHeader[0] : rlHeader);
200 var headers = {
201 'x-sentry-rate-limits': rlHeader,
202 'retry-after': retryAfterHeader,
203 };
204 var limited = _this._handleRateLimit(headers);
205 if (limited)
206 utils_1.logger.warn("Too many " + sentryRequest.type + " requests, backing off until: " + _this._disabledUntil(sentryRequest.type));
207 if (status === types_1.Status.Success) {
208 resolve({ status: status });
209 }
210 else {
211 var rejectionMessage = "HTTP Error (" + statusCode + ")";
212 if (res.headers && res.headers['x-sentry-error']) {
213 rejectionMessage += ": " + res.headers['x-sentry-error'];
214 }
215 reject(new utils_1.SentryError(rejectionMessage));
216 }
217 // Force the socket to drain
218 res.on('data', function () {
219 // Drain
220 });
221 res.on('end', function () {
222 // Drain
223 });
224 });
225 req.on('error', reject);
226 req.end(sentryRequest.body);
227 });
228 })];
229 });
230 });
231 };
232 return BaseTransport;
233}());
234exports.BaseTransport = BaseTransport;
235//# sourceMappingURL=index.js.map
\No newline at end of file