Skip to content

Commit 1c4e776

Browse files
authored
feat: Add tunnel support to multiplexed transport (#11806)
1 parent 08a3b7c commit 1c4e776

File tree

4 files changed

+64
-18
lines changed

4 files changed

+64
-18
lines changed

packages/core/src/baseclient.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
141141
options._metadata ? options._metadata.sdk : undefined,
142142
);
143143
this._transport = options.transport({
144+
tunnel: this._options.tunnel,
144145
recordDroppedEvent: this.recordDroppedEvent.bind(this),
145146
...options.transportOptions,
146147
url,

packages/core/src/transports/multiplexed.ts

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type {
77
Transport,
88
TransportMakeRequestResponse,
99
} from '@sentry/types';
10-
import { dsnFromString, forEachEnvelopeItem } from '@sentry/utils';
10+
import { createEnvelope, dsnFromString, forEachEnvelopeItem } from '@sentry/utils';
1111

1212
import { getEnvelopeEndpointWithUrlEncodedAuth } from '../api';
1313

@@ -57,6 +57,7 @@ function makeOverrideReleaseTransport<TO extends BaseTransportOptions>(
5757
const transport = createTransport(options);
5858

5959
return {
60+
...transport,
6061
send: async (envelope: Envelope): Promise<TransportMakeRequestResponse> => {
6162
const event = eventFromEnvelope(envelope, ['event', 'transaction', 'profile', 'replay_event']);
6263

@@ -65,11 +66,23 @@ function makeOverrideReleaseTransport<TO extends BaseTransportOptions>(
6566
}
6667
return transport.send(envelope);
6768
},
68-
flush: timeout => transport.flush(timeout),
6969
};
7070
};
7171
}
7272

73+
/** Overrides the DSN in the envelope header */
74+
function overrideDsn(envelope: Envelope, dsn: string): Envelope {
75+
return createEnvelope(
76+
dsn
77+
? {
78+
...envelope[0],
79+
dsn,
80+
}
81+
: envelope[0],
82+
envelope[1],
83+
);
84+
}
85+
7386
/**
7487
* Creates a transport that can send events to different DSNs depending on the envelope contents.
7588
*/
@@ -79,26 +92,30 @@ export function makeMultiplexedTransport<TO extends BaseTransportOptions>(
7992
): (options: TO) => Transport {
8093
return options => {
8194
const fallbackTransport = createTransport(options);
82-
const otherTransports: Record<string, Transport> = {};
95+
const otherTransports: Map<string, Transport> = new Map();
8396

84-
function getTransport(dsn: string, release: string | undefined): Transport | undefined {
97+
function getTransport(dsn: string, release: string | undefined): [string, Transport] | undefined {
8598
// We create a transport for every unique dsn/release combination as there may be code from multiple releases in
8699
// use at the same time
87100
const key = release ? `${dsn}:${release}` : dsn;
88101

89-
if (!otherTransports[key]) {
102+
let transport = otherTransports.get(key);
103+
104+
if (!transport) {
90105
const validatedDsn = dsnFromString(dsn);
91106
if (!validatedDsn) {
92107
return undefined;
93108
}
94-
const url = getEnvelopeEndpointWithUrlEncodedAuth(validatedDsn);
109+
const url = getEnvelopeEndpointWithUrlEncodedAuth(validatedDsn, options.tunnel);
95110

96-
otherTransports[key] = release
111+
transport = release
97112
? makeOverrideReleaseTransport(createTransport, release)({ ...options, url })
98113
: createTransport({ ...options, url });
114+
115+
otherTransports.set(key, transport);
99116
}
100117

101-
return otherTransports[key];
118+
return [dsn, transport];
102119
}
103120

104121
async function send(envelope: Envelope): Promise<TransportMakeRequestResponse> {
@@ -115,20 +132,23 @@ export function makeMultiplexedTransport<TO extends BaseTransportOptions>(
115132
return getTransport(result.dsn, result.release);
116133
}
117134
})
118-
.filter((t): t is Transport => !!t);
135+
.filter((t): t is [string, Transport] => !!t);
119136

120137
// If we have no transports to send to, use the fallback transport
121138
if (transports.length === 0) {
122-
transports.push(fallbackTransport);
139+
// Don't override the DSN in the header for the fallback transport. '' is falsy
140+
transports.push(['', fallbackTransport]);
123141
}
124142

125-
const results = await Promise.all(transports.map(transport => transport.send(envelope)));
143+
const results = await Promise.all(
144+
transports.map(([dsn, transport]) => transport.send(overrideDsn(envelope, dsn))),
145+
);
126146

127147
return results[0];
128148
}
129149

130150
async function flush(timeout: number | undefined): Promise<boolean> {
131-
const allTransports = [...Object.keys(otherTransports).map(dsn => otherTransports[dsn]), fallbackTransport];
151+
const allTransports = [...otherTransports.values(), fallbackTransport];
132152
const results = await Promise.all(allTransports.map(transport => transport.flush(timeout)));
133153
return results.every(r => r);
134154
}

packages/core/test/lib/transports/multiplexed.test.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type {
22
BaseTransportOptions,
33
ClientReport,
4+
Envelope,
45
EventEnvelope,
56
EventItem,
67
TransactionEvent,
@@ -47,7 +48,7 @@ const CLIENT_REPORT_ENVELOPE = createClientReportEnvelope(
4748
123456,
4849
);
4950

50-
type Assertion = (url: string, release: string | undefined, body: string | Uint8Array) => void;
51+
type Assertion = (url: string, release: string | undefined, body: Envelope) => void;
5152

5253
const createTestTransport = (...assertions: Assertion[]): ((options: BaseTransportOptions) => Transport) => {
5354
return (options: BaseTransportOptions) =>
@@ -60,7 +61,7 @@ const createTestTransport = (...assertions: Assertion[]): ((options: BaseTranspo
6061

6162
const event = eventFromEnvelope(parseEnvelope(request.body), ['event']);
6263

63-
assertion(options.url, event?.release, request.body);
64+
assertion(options.url, event?.release, parseEnvelope(request.body));
6465
resolve({ statusCode: 200 });
6566
});
6667
});
@@ -105,11 +106,12 @@ describe('makeMultiplexedTransport', () => {
105106
});
106107

107108
it('DSN can be overridden via match callback', async () => {
108-
expect.assertions(1);
109+
expect.assertions(2);
109110

110111
const makeTransport = makeMultiplexedTransport(
111-
createTestTransport(url => {
112+
createTestTransport((url, _, env) => {
112113
expect(url).toBe(DSN2_URL);
114+
expect(env[0].dsn).toBe(DSN2);
113115
}),
114116
() => [DSN2],
115117
);
@@ -119,12 +121,13 @@ describe('makeMultiplexedTransport', () => {
119121
});
120122

121123
it('DSN and release can be overridden via match callback', async () => {
122-
expect.assertions(2);
124+
expect.assertions(3);
123125

124126
const makeTransport = makeMultiplexedTransport(
125-
createTestTransport((url, release) => {
127+
createTestTransport((url, release, env) => {
126128
expect(url).toBe(DSN2_URL);
127129
expect(release).toBe('something@1.0.0');
130+
expect(env[0].dsn).toBe(DSN2);
128131
}),
129132
() => [{ dsn: DSN2, release: 'something@1.0.0' }],
130133
);
@@ -133,6 +136,22 @@ describe('makeMultiplexedTransport', () => {
133136
await transport.send(ERROR_ENVELOPE);
134137
});
135138

139+
it('URL can be overridden by tunnel option', async () => {
140+
expect.assertions(3);
141+
142+
const makeTransport = makeMultiplexedTransport(
143+
createTestTransport((url, release, env) => {
144+
expect(url).toBe('http://google.com');
145+
expect(release).toBe('something@1.0.0');
146+
expect(env[0].dsn).toBe(DSN2);
147+
}),
148+
() => [{ dsn: DSN2, release: 'something@1.0.0' }],
149+
);
150+
151+
const transport = makeTransport({ url: DSN1_URL, ...transportOptions, tunnel: 'http://google.com' });
152+
await transport.send(ERROR_ENVELOPE);
153+
});
154+
136155
it('match callback can return multiple DSNs', async () => {
137156
expect.assertions(2);
138157

packages/types/src/transport.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ export type TransportMakeRequestResponse = {
1515
};
1616

1717
export interface InternalBaseTransportOptions {
18+
/**
19+
* @ignore
20+
* Users should pass the tunnel property via the init/client options.
21+
* This is only used by the SDK to pass the tunnel to the transport.
22+
*/
23+
tunnel?: string;
1824
bufferSize?: number;
1925
recordDroppedEvent: Client['recordDroppedEvent'];
2026
}

0 commit comments

Comments
 (0)