Repository navigation
feat(core): Add managed tunnel helpers that derive the tunnel path from the DSN #25199
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e05b822
3efd40c
ce011e5
1eb7e9a
a0e794c
1ed892e
4e63500
3dda6fa
6b21417
30ff512
8bd6358
d0fd956
3ee7269
786c90f
3f76c2a
8a5c355
1d62dfb
1fd2f88
dd8c671
6cb4e27
34a938d
ca87483
64bc21b
52c892c
6fee43e
433fd64
688680f
830abd6
059e572
41da293
d525290
f32aca0
41d9ff8
aa2b17b
2774caf
332a7c1
f1a81bf
98ff881
6023ac8
b29bdaf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -96,7 +96,7 @@ export type IgnoreSpanAttributeValue = string | boolean | number | string[] | bo | |
| * At least one of the properties (`name`, `op`, or `attributes`) must be set. | ||
| * If multiple are set, all must match for the span to be ignored. | ||
| */ | ||
| type IgnoreSpanFilter = | ||
| export type IgnoreSpanFilter = | ||
| | { | ||
| /** | ||
| * Spans with a name matching this pattern will be ignored. | ||
|
|
@@ -373,6 +373,13 @@ export interface ClientOptions<TO extends BaseTransportOptions = BaseTransportOp | |
| */ | ||
| tunnel?: string; | ||
|
|
||
| /** | ||
| * Set by server SDKs when the managed tunnel is enabled. | ||
| * | ||
| * @internal This option is not part of the public API and is subject to change at any time. | ||
| */ | ||
| _managedTunnel?: ServerTunnelOptions; | ||
|
|
||
| /** | ||
| * Controls what data the SDK collects and sends to Sentry. | ||
| * All fields are optional — omitted fields use the documented defaults. | ||
|
|
@@ -645,6 +652,40 @@ export type BeforeSendStreamedSpanCallback = (span: StreamedSpanJSON) => Streame | |
| */ | ||
| export type BeforeSendStaticSpanCallback = (span: SpanJSON) => SpanJSON; | ||
|
|
||
| /** Managed tunnel options shared by the browser and the server `init`, see `tunnel`. */ | ||
| export interface TunnelOptions { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. l: Would be nice if we could define some
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Default is
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It must default to something internally. At least for trailing slash our internal default is true or false, right?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
WDYT? |
||
| /** A fixed tunnel path instead of the one derived from the DSN. A relative `tunnel` string sets this. */ | ||
| path?: string; | ||
|
|
||
| /** Path prefix the app is served under, e.g. `/docs`. Set the same value in the browser and the server `init`. */ | ||
| basePath?: string; | ||
|
logaretm marked this conversation as resolved.
|
||
| } | ||
|
|
||
| /** Managed tunnel options for the browser `init`. */ | ||
| export interface BrowserTunnelOptions extends TunnelOptions { | ||
| /** Append a trailing slash to the tunnel path, for servers that redirect paths without one. */ | ||
| trailingSlash?: boolean; | ||
| } | ||
|
|
||
| /** Managed tunnel options for a server `init`. */ | ||
| export interface ServerTunnelOptions extends TunnelOptions { | ||
| /** DSNs to forward besides the server's own, e.g. a different browser DSN. */ | ||
| allowedDsns?: string[]; | ||
| } | ||
|
|
||
| /** | ||
| * The `tunnel` option, where to send envelopes instead of the Sentry ingest URL: | ||
| * | ||
| * - A URL, e.g. `https://example.com/tunnel`, sends envelopes to your own tunnel endpoint. | ||
| * - A relative path, e.g. `/monitor`, enables the managed tunnel at that fixed path. The browser sends envelopes to | ||
| * it and the server SDK serves it, so set the same path in both `init`s. | ||
| * - `true` enables the managed tunnel at a same-origin path derived from the DSN. Set it in both the browser and the | ||
| * server `init`: the browser sends envelopes to that path and the server SDK forwards them to Sentry. This keeps | ||
| * ad-blockers from dropping events. | ||
| * - Options, like `true`, with the browser or server options of the managed tunnel. | ||
| */ | ||
| export type TunnelInit<T extends TunnelOptions = TunnelOptions> = string | true | T; | ||
|
|
||
| /** Base configuration options for every SDK. */ | ||
| export interface CoreOptions<TO extends BaseTransportOptions = BaseTransportOptions> extends Omit< | ||
| Partial<ClientOptions<TO>>, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| import type { Client } from '../client'; | ||
| import { getClient } from '../currentScopes'; | ||
| import type { IgnoreSpanFilter, ServerTunnelOptions } from '../types/options'; | ||
| import { | ||
| getTunnelIgnoreSpanFilters, | ||
| normalizeTunnelBasePath, | ||
| resolveAllowedDsns, | ||
| resolveTunnelPaths, | ||
| setServedTunnel, | ||
| stripBasePathFromTunnelPath, | ||
| } from './tunnel'; | ||
|
|
||
| /** | ||
| * Managed tunnel settings a framework SDK knows outside `init`. | ||
| */ | ||
| interface ManagedTunnelDefaults { | ||
| /** | ||
| * Enables the managed tunnel when `init` didn't, e.g. from TanStack Start's `tunnelRoute` build option. Its base | ||
| * path is `basePath`, so it has none of its own. | ||
| */ | ||
| tunnel?: Omit<ServerTunnelOptions, 'basePath'>; | ||
| /** The path prefix the app is served under. Applies however the tunnel was enabled, unless `init` set its own. */ | ||
| basePath?: string; | ||
|
logaretm marked this conversation as resolved.
|
||
| } | ||
|
|
||
| let managedTunnelDefaults: ManagedTunnelDefaults | undefined; | ||
|
|
||
| // So re-applying defaults replaces a client's filters instead of adding more. | ||
| const installedSpanFilters = new WeakMap<Client, IgnoreSpanFilter[]>(); | ||
|
|
||
| /** | ||
| * Sets framework-known tunnel settings for the current and every later server client, so per-request clients | ||
| * (Cloudflare) start with them. Settings from `init` win. Framework SDKs call this once at load. | ||
| */ | ||
| export function setManagedTunnelDefaults(defaults: ManagedTunnelDefaults): void { | ||
| managedTunnelDefaults = { | ||
| ...managedTunnelDefaults, | ||
| ...(defaults.tunnel && { tunnel: defaults.tunnel }), | ||
| ...(defaults.basePath !== undefined && { basePath: defaults.basePath }), | ||
| }; | ||
| applyManagedTunnelDefaults(getClient()); | ||
| } | ||
|
|
||
| /** | ||
| * Resolves a server client's managed tunnel (from `init` or the defaults), computes its paths and allowed DSNs, and | ||
| * installs its span filters. Runs when a server client is created and from {@link setManagedTunnelDefaults}. | ||
| */ | ||
| export function applyManagedTunnelDefaults(client: Client | undefined): void { | ||
| const options = client?.getOptions(); | ||
| const tunnelOptions: ServerTunnelOptions | undefined = options?._managedTunnel ?? managedTunnelDefaults?.tunnel; | ||
| if (!client || !options || !tunnelOptions) { | ||
| return; | ||
| } | ||
|
|
||
| const basePath = normalizeTunnelBasePath(managedTunnelDefaults?.basePath); | ||
| const resolved = stripBasePathFromTunnelPath( | ||
| tunnelOptions.basePath === undefined && basePath ? { ...tunnelOptions, basePath } : tunnelOptions, | ||
|
logaretm marked this conversation as resolved.
|
||
| ); | ||
| options._managedTunnel = resolved; | ||
| const allowedDsns = resolveAllowedDsns(resolved.allowedDsns, client.getDsn()); | ||
|
logaretm marked this conversation as resolved.
|
||
| const paths = resolveTunnelPaths(resolved, allowedDsns); | ||
| setServedTunnel(client, { paths, allowedDsns }); | ||
|
|
||
| const previousFilters = installedSpanFilters.get(client) ?? []; | ||
| const spanFilters = getTunnelIgnoreSpanFilters(resolved, paths); | ||
| options.ignoreSpans = [ | ||
| ...(options.ignoreSpans ?? []).filter(filter => !(previousFilters as unknown[]).includes(filter)), | ||
| ...spanFilters, | ||
| ]; | ||
| installedSpanFilters.set(client, spanFilters); | ||
| } | ||
|
|
||
| /** | ||
| * Resets {@link setManagedTunnelDefaults}, for tests. | ||
| * | ||
| * @internal | ||
| */ | ||
| export function _INTERNAL_resetManagedTunnelDefaults(): void { | ||
| managedTunnelDefaults = undefined; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.