Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
1761f88
feat(core): Add managed tunnel helpers that derive the tunnel path fr…
logaretm Oct 8, 2026
4d25222
ref(core): Pass the whole request to the tunnel matcher and expose `g…
logaretm Oct 15, 2026
0663722
perf(core): Cache the tunnel path per DSN
logaretm Oct 15, 2026
2571164
feat(core): Serve a fixed managed tunnel path from a relative `tunnel…
logaretm Oct 15, 2026
bdd9dd3
feat(core): Drop the server spans of managed tunnel requests
logaretm Oct 15, 2026
0ca4167
fix(core): Skip fetch instrumentation while tracing is suppressed
logaretm Oct 15, 2026
2f04c1e
feat(core): Check the tunnel DSN before reading the envelope body
logaretm Oct 15, 2026
eb3b484
fix(core): Cancel the body of a rejected tunnel request
logaretm Oct 15, 2026
da47f3b
feat(core): Forward the client IP of tunnel requests to Sentry
logaretm Oct 15, 2026
5ce1dad
fix(core): Match a fixed tunnel path that ends with a slash
logaretm Oct 16, 2026
53aa687
fix(core): Only answer the managed tunnel under its base path
logaretm Oct 16, 2026
88fdc70
feat(core): Always accept the server's own DSN in the managed tunnel
logaretm Oct 16, 2026
2f1126b
feat(core): Add `applyTunnelDefaults` so framework SDKs fill in the b…
logaretm Oct 16, 2026
0620c82
feat(core): Add `setManagedTunnelBasePath` for servers that learn the…
logaretm Oct 16, 2026
52e8577
feat(core): Mark the tunnel handler's responses so the browser can te…
logaretm Oct 16, 2026
dcd5dbf
fix(core): Normalize tunnel base paths with one util that doesn't bac…
logaretm Oct 16, 2026
0052d4c
fix(core): Leave a fixed tunnel path to the app when there is no DSN …
logaretm Oct 18, 2026
e98405d
fix(core): Don't add the base path again to a tunnel path that alread…
logaretm Oct 19, 2026
7e7e5fb
fix(core): Don't report a client that drops the tunnel request as an …
logaretm Oct 19, 2026
077557b
fix(core): Also drop tunnel spans that only carry `http.url`, like Sv…
logaretm Oct 19, 2026
4c6211b
feat(core): Add enableManagedTunnel for framework options that enable…
logaretm Oct 19, 2026
81812da
ref(core): Settle a server client's managed tunnel in one place, from…
logaretm Oct 19, 2026
5c90301
test(nextjs): Read back the streamed body in the tunnel route test
logaretm Oct 19, 2026
54472c7
ref(core): Compute the managed tunnel paths once per server client
logaretm Oct 19, 2026
2a51cae
ref(core): Resolve the managed tunnel's DSNs once per server client
logaretm Oct 19, 2026
466d219
fix(core): Don't forward the tunnel request's Content-Length with the…
logaretm Oct 19, 2026
6443fbb
ref(core): Keep the served tunnel paths and DSNs out of ClientOptions
logaretm Oct 19, 2026
5fc8121
ref(core): Trim the managed tunnel comments
logaretm Oct 19, 2026
baf80cd
feat(core): Keep the tunnel body size checks with the streamed forward
logaretm Oct 19, 2026
f97d8a2
ref(core): Rename withPathBelowBasePath to stripBasePathFromTunnelPath
logaretm Oct 19, 2026
d40133d
ref(core): Normalize the default base path in applyTunnelDefaults and…
logaretm Oct 19, 2026
5c0e6ff
fix(core): Normalize allowedDsns like the envelope header's DSN, so o…
logaretm Oct 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,13 @@ export { handleCallbackErrors } from './utils/handleCallbackErrors';
export { safeCallback } from './utils/safeCallback';
export { parameterize, fmt } from './utils/parameterize';
export type { HandleTunnelRequestOptions } from './utils/tunnel';
export { handleTunnelRequest } from './utils/tunnel';
export {
applyTunnelDefaults,
getTunnelPath,
handleTunnelRequest,
normalizeTunnelBasePath,
TUNNEL_RESPONSE_HEADER,
} from './utils/tunnel';
export { addAutoIpAddressToSession } from './utils/ipAddress';
export {
convertSpanLinksForEnvelope,
Expand Down Expand Up @@ -415,7 +421,7 @@ export type {
HttpHeadersCollection,
ResolvedDataCollection,
} from './types/datacollection';
export type { ClientOptions, CoreOptions as Options } from './types/options';
export type { ClientOptions, CoreOptions as Options, TunnelOptions } from './types/options';
export type { Package } from './types/package';
export type { PolymorphicEvent, PolymorphicRequest } from './types/polymorphics';
export type {
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/integrations/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getClient } from '../currentScopes';
import { instrumentFetchRequest } from '../fetch';
import { defineIntegration } from '../integration';
import { addFetchInstrumentationHandler } from '../instrument/fetch';
import { isTracingSuppressed } from '../tracing/trace';
import type { FetchBreadcrumbData, FetchBreadcrumbHint } from '../types/breadcrumb';
import type { HandlerDataFetch } from '../types/instrument';
import type { Integration, IntegrationFn } from '../types/integration';
Expand Down Expand Up @@ -92,7 +93,8 @@ export function createFetchIntegration({
return;
}

if (isSentryRequestUrl(handlerData.fetchData.url, client)) {
// Like Node's http and undici instrumentation: SDK-internal requests leave no breadcrumb.
if (isTracingSuppressed() || isSentryRequestUrl(handlerData.fetchData.url, client)) {
return;
}

Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/server-runtime-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { uuid4 } from './utils/misc';
import { makePromiseBuffer } from './utils/promisebuffer';
import { resolvedSyncPromise } from './utils/syncpromise';
import { _getTraceInfoFromScope } from './utils/trace-info';
import { applyManagedTunnelDefaults } from './utils/managedTunnelDefaults';

export interface ServerRuntimeClientOptions extends ClientOptions<BaseTransportOptions> {
platform?: string;
Expand Down Expand Up @@ -58,6 +59,7 @@ export class ServerRuntimeClient<

super(options);

applyManagedTunnelDefaults(this);
this._setUpMetricsProcessing();
}

Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@

export type { ServerRuntimeClientOptions } from './server-runtime-client';
export { ServerRuntimeClient } from './server-runtime-client';
export type { TunnelRequestLike } from './utils/tunnel';
export {
getTunnelPaths,
handleTunnelRequestIfMatched,
isTunnelRequest,
resolveServerTunnelOption,
} from './utils/tunnel';
export { _INTERNAL_resetManagedTunnelDefaults, setManagedTunnelDefaults } from './utils/managedTunnelDefaults';
export type { ServerRuntimeOptions } from './types/options';
export { trpcMiddleware } from './trpc';
export { wrapMcpServerWithSentry } from './integrations/mcp-server';
Expand Down
26 changes: 25 additions & 1 deletion packages/core/src/types/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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?: TunnelOptions;

/**
* Controls what data the SDK collects and sends to Sentry.
* All fields are optional — omitted fields use the documented defaults.
Expand Down Expand Up @@ -645,6 +652,23 @@ export type BeforeSendStreamedSpanCallback = (span: StreamedSpanJSON) => Streame
*/
export type BeforeSendStaticSpanCallback = (span: SpanJSON) => SpanJSON;

/** Options for the managed tunnel, see `tunnel`. */
export interface TunnelOptions {
/**
* Server only. DSNs to forward besides the server's own, e.g. a different browser DSN.
*/
allowedDsns?: string[];

/** 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;

/** Browser only. Append a trailing slash to the tunnel path, for servers that redirect paths without one. */
trailingSlash?: boolean;
}

/** Base configuration options for every SDK. */
export interface CoreOptions<TO extends BaseTransportOptions = BaseTransportOptions> extends Omit<
Partial<ClientOptions<TO>>,
Expand Down
77 changes: 77 additions & 0 deletions packages/core/src/utils/managedTunnelDefaults.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import type { Client } from '../client';
import { getClient } from '../currentScopes';
import type { IgnoreSpanFilter, TunnelOptions } 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, e.g. from TanStack Start's `tunnelRoute` build option. */
tunnel?: TunnelOptions;
/** The path prefix the app is served under. */
basePath?: string;
}

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 = options?._managedTunnel ?? managedTunnelDefaults?.tunnel;
if (!client || !options || !tunnelOptions) {
return;
}

const basePath = normalizeTunnelBasePath(managedTunnelDefaults?.basePath);
const resolved = stripBasePathFromTunnelPath(
tunnelOptions.basePath === undefined && basePath ? { ...tunnelOptions, basePath } : tunnelOptions,
);
options._managedTunnel = resolved;
const allowedDsns = resolveAllowedDsns(resolved.allowedDsns, client.getDsn());
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;
}
Loading
Loading