Skip to content

Commit

Permalink
feat(v7): Add tunnel support to multiplexed transport (#11851)
Browse files Browse the repository at this point in the history
  • Loading branch information
timfish committed May 2, 2024
1 parent 9ffc7f4 commit ae5d7c8
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 21 deletions.
1 change: 1 addition & 0 deletions packages/core/src/baseclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
if (this._dsn) {
const url = getEnvelopeEndpointWithUrlEncodedAuth(this._dsn, options);
this._transport = options.transport({
tunnel: this._options.tunnel,
recordDroppedEvent: this.recordDroppedEvent.bind(this),
...options.transportOptions,
url,
Expand Down
52 changes: 38 additions & 14 deletions packages/core/src/transports/multiplexed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type {
Transport,
TransportMakeRequestResponse,
} from '@sentry/types';
import { dsnFromString, forEachEnvelopeItem } from '@sentry/utils';
import { createEnvelope, dsnFromString, forEachEnvelopeItem } from '@sentry/utils';

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

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

return {
...transport,
send: async (envelope: Envelope): Promise<void | TransportMakeRequestResponse> => {
const event = eventFromEnvelope(envelope, ['event', 'transaction', 'profile', 'replay_event']);

Expand All @@ -65,11 +66,23 @@ function makeOverrideReleaseTransport<TO extends BaseTransportOptions>(
}
return transport.send(envelope);
},
flush: timeout => transport.flush(timeout),
};
};
}

/** Overrides the DSN in the envelope header */
function overrideDsn(envelope: Envelope, dsn: string): Envelope {
return createEnvelope(
dsn
? {
...envelope[0],
dsn,
}
: envelope[0],
envelope[1],
);
}

/**
* Creates a transport that can send events to different DSNs depending on the envelope contents.
*/
Expand All @@ -79,26 +92,31 @@ export function makeMultiplexedTransport<TO extends BaseTransportOptions>(
): (options: TO) => Transport {
return options => {
const fallbackTransport = createTransport(options);
const otherTransports: Record<string, Transport> = {};
const otherTransports = new Map<string, Transport>();

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

if (!otherTransports[key]) {
let transport = otherTransports.get(key);

if (!transport) {
const validatedDsn = dsnFromString(dsn);
if (!validatedDsn) {
return undefined;
}
const url = getEnvelopeEndpointWithUrlEncodedAuth(validatedDsn);

otherTransports[key] = release
const url = getEnvelopeEndpointWithUrlEncodedAuth(validatedDsn, options.tunnel);

transport = release
? makeOverrideReleaseTransport(createTransport, release)({ ...options, url })
: createTransport({ ...options, url });

otherTransports.set(key, transport);
}

return otherTransports[key];
return [dsn, transport];
}

async function send(envelope: Envelope): Promise<void | TransportMakeRequestResponse> {
Expand All @@ -115,22 +133,28 @@ export function makeMultiplexedTransport<TO extends BaseTransportOptions>(
return getTransport(result.dsn, result.release);
}
})
.filter((t): t is Transport => !!t);
.filter((t): t is [string, Transport] => !!t);

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

const results = await Promise.all(transports.map(transport => transport.send(envelope)));
const results = await Promise.all(
transports.map(([dsn, transport]) => transport.send(overrideDsn(envelope, dsn))),
);

return results[0];
}

async function flush(timeout: number | undefined): Promise<boolean> {
const allTransports = [...Object.keys(otherTransports).map(dsn => otherTransports[dsn]), fallbackTransport];
const results = await Promise.all(allTransports.map(transport => transport.flush(timeout)));
return results.every(r => r);
const promises = [await fallbackTransport.flush(timeout)];
for (const [, transport] of otherTransports) {
promises.push(await transport.flush(timeout));
}

return promises.every(r => r);
}

return {
Expand Down
34 changes: 27 additions & 7 deletions packages/core/test/lib/transports/multiplexed.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { TextDecoder, TextEncoder } from 'util';
import type {
BaseTransportOptions,
ClientReport,
Envelope,
EventEnvelope,
EventItem,
TransactionEvent,
Expand Down Expand Up @@ -48,7 +49,7 @@ const CLIENT_REPORT_ENVELOPE = createClientReportEnvelope(
123456,
);

type Assertion = (url: string, release: string | undefined, body: string | Uint8Array) => void;
type Assertion = (url: string, release: string | undefined, body: Envelope) => void;

const createTestTransport = (...assertions: Assertion[]): ((options: BaseTransportOptions) => Transport) => {
return (options: BaseTransportOptions) =>
Expand All @@ -59,9 +60,10 @@ const createTestTransport = (...assertions: Assertion[]): ((options: BaseTranspo
throw new Error('No assertion left');
}

const event = eventFromEnvelope(parseEnvelope(request.body, new TextEncoder(), new TextDecoder()), ['event']);
const env = parseEnvelope(request.body, new TextEncoder(), new TextDecoder());
const event = eventFromEnvelope(env, ['event']);

assertion(options.url, event?.release, request.body);
assertion(options.url, event?.release, env);
resolve({ statusCode: 200 });
});
});
Expand Down Expand Up @@ -107,11 +109,12 @@ describe('makeMultiplexedTransport', () => {
});

it('DSN can be overridden via match callback', async () => {
expect.assertions(1);
expect.assertions(2);

const makeTransport = makeMultiplexedTransport(
createTestTransport(url => {
createTestTransport((url, _, env) => {
expect(url).toBe(DSN2_URL);
expect(env[0].dsn).toBe(DSN2);
}),
() => [DSN2],
);
Expand All @@ -121,12 +124,13 @@ describe('makeMultiplexedTransport', () => {
});

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

const makeTransport = makeMultiplexedTransport(
createTestTransport((url, release) => {
createTestTransport((url, release, env) => {
expect(url).toBe(DSN2_URL);
expect(release).toBe('something@1.0.0');
expect(env[0].dsn).toBe(DSN2);
}),
() => [{ dsn: DSN2, release: 'something@1.0.0' }],
);
Expand All @@ -135,6 +139,22 @@ describe('makeMultiplexedTransport', () => {
await transport.send(ERROR_ENVELOPE);
});

it('URL can be overridden by tunnel option', async () => {
expect.assertions(3);

const makeTransport = makeMultiplexedTransport(
createTestTransport((url, release, env) => {
expect(url).toBe('http://google.com');
expect(release).toBe('something@1.0.0');
expect(env[0].dsn).toBe(DSN2);
}),
() => [{ dsn: DSN2, release: 'something@1.0.0' }],
);

const transport = makeTransport({ url: DSN1_URL, ...transportOptions, tunnel: 'http://google.com' });
await transport.send(ERROR_ENVELOPE);
});

it('match callback can return multiple DSNs', async () => {
expect.assertions(2);

Expand Down
6 changes: 6 additions & 0 deletions packages/types/src/transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ export type TransportMakeRequestResponse = {
};

export interface InternalBaseTransportOptions {
/**
* @ignore
* Users should pass the tunnel property via the init/client options.
* This is only used by the SDK to pass the tunnel to the transport.
*/
tunnel?: string;
bufferSize?: number;
recordDroppedEvent: Client['recordDroppedEvent'];
textEncoder?: TextEncoderInternal;
Expand Down

0 comments on commit ae5d7c8

Please sign in to comment.