Skip to content

Commit 8958f61

Browse files
feat: Send transactions in envelopes (#2553)
The new Envelope endpoint and format is what we want to use for transactions going forward. Also unify how `@sentry/browser` and `@sentry/node` send requests to Sentry. Co-authored-by: Kamil Ogórek <[email protected]>
1 parent cf2bc7e commit 8958f61

File tree

15 files changed

+178
-54
lines changed

15 files changed

+178
-54
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott
66

7+
- [core] feat: Send transactions in envelopes (#2553)
8+
79
## 5.15.5
810

911
- [browser/node] Add missing `BreadcrumbHint` and `EventHint` types exports (#2545)

packages/browser/src/integrations/breadcrumbs.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ export class Breadcrumbs implements Integration {
170170
const client = getCurrentHub().getClient<BrowserClient>();
171171
const dsn = client && client.getDsn();
172172
if (this._options.sentry && dsn) {
173-
const filterUrl = new API(dsn).getStoreEndpoint();
173+
const filterUrl = new API(dsn).getBaseApiEndpoint();
174174
// if Sentry key appears in URL, don't capture it as a request
175175
// but rather as our own 'sentry' type breadcrumb
176176
if (

packages/browser/src/transports/base.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,20 @@ import { PromiseBuffer, SentryError } from '@sentry/utils';
55
/** Base Transport class implementation */
66
export abstract class BaseTransport implements Transport {
77
/**
8-
* @inheritDoc
8+
* @deprecated
99
*/
1010
public url: string;
1111

12+
/** Helper to get Sentry API endpoints. */
13+
protected readonly _api: API;
14+
1215
/** A simple buffer holding all requests. */
1316
protected readonly _buffer: PromiseBuffer<Response> = new PromiseBuffer(30);
1417

1518
public constructor(public options: TransportOptions) {
16-
this.url = new API(this.options.dsn).getStoreEndpointWithUrlEncodedAuth();
19+
this._api = new API(this.options.dsn);
20+
// tslint:disable-next-line:deprecation
21+
this.url = this._api.getStoreEndpointWithUrlEncodedAuth();
1722
}
1823

1924
/**

packages/browser/src/transports/fetch.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { eventToSentryRequest } from '@sentry/core';
12
import { Event, Response, Status } from '@sentry/types';
23
import { getGlobalObject, logger, parseRetryAfterHeader, supportsReferrerPolicy, SyncPromise } from '@sentry/utils';
34

@@ -22,8 +23,10 @@ export class FetchTransport extends BaseTransport {
2223
});
2324
}
2425

25-
const defaultOptions: RequestInit = {
26-
body: JSON.stringify(event),
26+
const sentryReq = eventToSentryRequest(event, this._api);
27+
28+
const options: RequestInit = {
29+
body: sentryReq.body,
2730
method: 'POST',
2831
// Despite all stars in the sky saying that Edge supports old draft syntax, aka 'never', 'always', 'origin' and 'default
2932
// https://caniuse.com/#feat=referrer-policy
@@ -33,13 +36,13 @@ export class FetchTransport extends BaseTransport {
3336
};
3437

3538
if (this.options.headers !== undefined) {
36-
defaultOptions.headers = this.options.headers;
39+
options.headers = this.options.headers;
3740
}
3841

3942
return this._buffer.add(
4043
new SyncPromise<Response>((resolve, reject) => {
4144
global
42-
.fetch(this.url, defaultOptions)
45+
.fetch(sentryReq.url, options)
4346
.then(response => {
4447
const status = Status.fromHttpCode(response.status);
4548

packages/browser/src/transports/xhr.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { eventToSentryRequest } from '@sentry/core';
12
import { Event, Response, Status } from '@sentry/types';
23
import { logger, parseRetryAfterHeader, SyncPromise } from '@sentry/utils';
34

@@ -20,6 +21,8 @@ export class XHRTransport extends BaseTransport {
2021
});
2122
}
2223

24+
const sentryReq = eventToSentryRequest(event, this._api);
25+
2326
return this._buffer.add(
2427
new SyncPromise<Response>((resolve, reject) => {
2528
const request = new XMLHttpRequest();
@@ -45,13 +48,13 @@ export class XHRTransport extends BaseTransport {
4548
reject(request);
4649
};
4750

48-
request.open('POST', this.url);
51+
request.open('POST', sentryReq.url);
4952
for (const header in this.options.headers) {
5053
if (this.options.headers.hasOwnProperty(header)) {
5154
request.setRequestHeader(header, this.options.headers[header]);
5255
}
5356
}
54-
request.send(JSON.stringify(event));
57+
request.send(sentryReq.body);
5558
}),
5659
);
5760
}

packages/browser/test/unit/transports/base.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ describe('BaseTransport', () => {
1919

2020
it('has correct endpoint url', () => {
2121
const transport = new SimpleTransport({ dsn: testDsn });
22+
// tslint:disable-next-line:deprecation
2223
expect(transport.url).equal('https://sentry.io/api/42/store/?sentry_key=123&sentry_version=7');
2324
});
2425
});

packages/browser/test/unit/transports/fetch.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ describe('FetchTransport', () => {
2828
});
2929

3030
it('inherits composeEndpointUrl() implementation', () => {
31+
// tslint:disable-next-line:deprecation
3132
expect(transport.url).equal(transportUrl);
3233
});
3334

packages/browser/test/unit/transports/xhr.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ describe('XHRTransport', () => {
2828
});
2929

3030
it('inherits composeEndpointUrl() implementation', () => {
31+
// tslint:disable-next-line:deprecation
3132
expect(transport.url).equal(transportUrl);
3233
});
3334

packages/core/src/api.ts

Lines changed: 51 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,29 +17,59 @@ export class API {
1717
return this._dsnObject;
1818
}
1919

20-
/** Returns a string with auth headers in the url to the store endpoint. */
20+
/** Returns the prefix to construct Sentry ingestion API endpoints. */
21+
public getBaseApiEndpoint(): string {
22+
const dsn = this._dsnObject;
23+
const protocol = dsn.protocol ? `${dsn.protocol}:` : '';
24+
const port = dsn.port ? `:${dsn.port}` : '';
25+
return `${protocol}//${dsn.host}${port}${dsn.path ? `/${dsn.path}` : ''}/api/`;
26+
}
27+
28+
/** Returns the store endpoint URL. */
2129
public getStoreEndpoint(): string {
22-
return `${this._getBaseUrl()}${this.getStoreEndpointPath()}`;
30+
return this._getIngestEndpoint('store');
31+
}
32+
33+
/** Returns the envelope endpoint URL. */
34+
private _getEnvelopeEndpoint(): string {
35+
return this._getIngestEndpoint('envelope');
36+
}
37+
38+
/** Returns the ingest API endpoint for target. */
39+
private _getIngestEndpoint(target: 'store' | 'envelope'): string {
40+
const base = this.getBaseApiEndpoint();
41+
const dsn = this._dsnObject;
42+
return `${base}${dsn.projectId}/${target}/`;
2343
}
2444

25-
/** Returns the store endpoint with auth added in url encoded. */
45+
/**
46+
* Returns the store endpoint URL with auth in the query string.
47+
*
48+
* Sending auth as part of the query string and not as custom HTTP headers avoids CORS preflight requests.
49+
*/
2650
public getStoreEndpointWithUrlEncodedAuth(): string {
51+
return `${this.getStoreEndpoint()}?${this._encodedAuth()}`;
52+
}
53+
54+
/**
55+
* Returns the envelope endpoint URL with auth in the query string.
56+
*
57+
* Sending auth as part of the query string and not as custom HTTP headers avoids CORS preflight requests.
58+
*/
59+
public getEnvelopeEndpointWithUrlEncodedAuth(): string {
60+
return `${this._getEnvelopeEndpoint()}?${this._encodedAuth()}`;
61+
}
62+
63+
/** Returns a URL-encoded string with auth config suitable for a query string. */
64+
private _encodedAuth(): string {
2765
const dsn = this._dsnObject;
2866
const auth = {
29-
sentry_key: dsn.user, // sentry_key is currently used in tracing integration to identify internal sentry requests
67+
// We send only the minimum set of required information. See
68+
// https://github.com/getsentry/sentry-javascript/issues/2572.
69+
sentry_key: dsn.user,
3070
sentry_version: SENTRY_API_VERSION,
3171
};
32-
// Auth is intentionally sent as part of query string (NOT as custom HTTP header)
33-
// to avoid preflight CORS requests
34-
return `${this.getStoreEndpoint()}?${urlEncode(auth)}`;
35-
}
36-
37-
/** Returns the base path of the url including the port. */
38-
private _getBaseUrl(): string {
39-
const dsn = this._dsnObject;
40-
const protocol = dsn.protocol ? `${dsn.protocol}:` : '';
41-
const port = dsn.port ? `:${dsn.port}` : '';
42-
return `${protocol}//${dsn.host}${port}`;
72+
return urlEncode(auth);
4373
}
4474

4575
/** Returns only the path component for the store endpoint. */
@@ -48,7 +78,11 @@ export class API {
4878
return `${dsn.path ? `/${dsn.path}` : ''}/api/${dsn.projectId}/store/`;
4979
}
5080

51-
/** Returns an object that can be used in request headers. */
81+
/**
82+
* Returns an object that can be used in request headers.
83+
*
84+
* @deprecated in favor of `getStoreEndpointWithUrlEncodedAuth` and `getEnvelopeEndpointWithUrlEncodedAuth`.
85+
*/
5286
public getRequestHeaders(clientName: string, clientVersion: string): { [key: string]: string } {
5387
const dsn = this._dsnObject;
5488
const header = [`Sentry sentry_version=${SENTRY_API_VERSION}`];
@@ -71,7 +105,7 @@ export class API {
71105
} = {},
72106
): string {
73107
const dsn = this._dsnObject;
74-
const endpoint = `${this._getBaseUrl()}${dsn.path ? `/${dsn.path}` : ''}/api/embed/error-page/`;
108+
const endpoint = `${this.getBaseApiEndpoint()}embed/error-page/`;
75109

76110
const encodedOptions = [];
77111
encodedOptions.push(`dsn=${dsn.toString()}`);

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export { addGlobalEventProcessor, getCurrentHub, getHubFromCarrier, Hub, Scope }
1616
export { API } from './api';
1717
export { BaseClient } from './baseclient';
1818
export { BackendClass, BaseBackend } from './basebackend';
19+
export { eventToSentryRequest } from './request';
1920
export { initAndBind, ClientClass } from './sdk';
2021
export { NoopTransport } from './transports/noop';
2122

0 commit comments

Comments
 (0)