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
9452248
feat(core): Add managed tunnel helpers that derive the tunnel path fr…
logaretm Oct 8, 2026
abbc227
ref(core): Pass the whole request to the tunnel matcher and expose `g…
logaretm Oct 15, 2026
6325536
perf(core): Cache the tunnel path per DSN
logaretm Oct 15, 2026
349b2a5
feat(core): Serve a fixed managed tunnel path from a relative `tunnel…
logaretm Oct 15, 2026
0b22d44
feat(core): Drop the server spans of managed tunnel requests
logaretm Oct 15, 2026
014430f
fix(core): Skip fetch instrumentation while tracing is suppressed
logaretm Oct 15, 2026
ce43fbd
feat(core): Check the tunnel DSN before reading the envelope body
logaretm Oct 15, 2026
aaefa4d
fix(core): Cancel the body of a rejected tunnel request
logaretm Oct 15, 2026
ecae60e
feat(core): Forward the client IP of tunnel requests to Sentry
logaretm Oct 15, 2026
103a1ba
fix(core): Match a fixed tunnel path that ends with a slash
logaretm Oct 16, 2026
fd0ecdc
fix(core): Only answer the managed tunnel under its base path
logaretm Oct 16, 2026
46a06a6
feat(core): Always accept the server's own DSN in the managed tunnel
logaretm Oct 16, 2026
df26498
feat(core): Add `applyTunnelDefaults` so framework SDKs fill in the b…
logaretm Oct 16, 2026
e3cb289
feat(core): Add `setManagedTunnelBasePath` for servers that learn the…
logaretm Oct 16, 2026
130347f
feat(core): Mark the tunnel handler's responses so the browser can te…
logaretm Oct 16, 2026
e79027b
fix(core): Normalize tunnel base paths with one util that doesn't bac…
logaretm Oct 16, 2026
3cd6b5f
fix(core): Leave a fixed tunnel path to the app when there is no DSN …
logaretm Oct 18, 2026
acbe24d
fix(core): Don't add the base path again to a tunnel path that alread…
logaretm Oct 19, 2026
06de89f
fix(core): Don't report a client that drops the tunnel request as an …
logaretm Oct 19, 2026
2d45708
fix(core): Also drop tunnel spans that only carry `http.url`, like Sv…
logaretm Oct 19, 2026
7075dc7
feat(core): Add enableManagedTunnel for framework options that enable…
logaretm Oct 19, 2026
e1ec637
ref(core): Settle a server client's managed tunnel in one place, from…
logaretm Oct 19, 2026
807444f
test(nextjs): Read back the streamed body in the tunnel route test
logaretm Oct 19, 2026
38bd4c7
ref(core): Compute the managed tunnel paths once per server client
logaretm Oct 19, 2026
8056c7b
ref(core): Resolve the managed tunnel's DSNs once per server client
logaretm Oct 19, 2026
c8fa80c
fix(core): Don't forward the tunnel request's Content-Length with the…
logaretm Oct 19, 2026
c51f2c4
ref(core): Keep the served tunnel paths and DSNs out of ClientOptions
logaretm Oct 19, 2026
8fb5e80
ref(core): Trim the managed tunnel comments
logaretm Oct 19, 2026
ab157de
feat(core): Keep the tunnel body size checks with the streamed forward
logaretm Oct 19, 2026
855675b
ref(core): Rename withPathBelowBasePath to stripBasePathFromTunnelPath
logaretm Oct 19, 2026
3cc8f0c
ref(core): Normalize the default base path in applyTunnelDefaults and…
logaretm Oct 19, 2026
4ae6673
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