Repository navigation
feat: Polish the public tunnel API and export it on Server SDKs - #25783
Conversation
|
bugbot run |
size-limit report 📦Compared: 6c721ce (PR test merge using develop at c5c2a3a) Against: c5c2a3a (develop) · base run #63956 ✅ Baseline matches: this diff contains only this PR's changes.
|
Optional E2E testsSome optional checks were not successful. They do not block the merge. 3 not successful, 64 successful. See the run.
Successful checks
|
|
bugbot run |
Covers the tunnel helpers every server SDK exports and the new handleTunnelRequest signature from getsentry/sentry-javascript#25783.
|
One or more gzipped bundles increased by more than 500 bytes. If this increase is intentional, add the Accept Bundlesize Increase label to this PR to rerun and accept the check. |
|
bugbot run |
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit e794fd6. Configure here.
31401af to
9bbc09d
Compare
|
@Lms24 I think we shouldn't fallback to a fixed path because the browser wouldn't know if the server serves it or not. I think we can document the middleware style for random paths and keep the fixed path for specific paths so it would work both ways. DSN being unset would be safe to use in the middleware style. WDYT? |
yeah I'm fine with handling this on a documentation level. Sounds good, thx! |
Users wiring the tunnel by hand could only import these from `@sentry/core`, which strict pnpm installs do not resolve.
…pt a Node request and response The released options object form keeps working and is deprecated.
…egration` allows
…erveTunnelIntegration`, then the own DSN `isTunnelRequest`, `handleTunnelRequestIfMatched` and `getTunnelPath` work without the integration now, and `handleTunnelRequestIfMatched` also takes a Node request and response. The framework SDKs keep serving only with the integration, through internal served-only versions.
…eTunnelRequest` overload The deprecated overload made every re-export of the function fail the deprecation lint.
…plicit export lists
… did not set it up The Node form skips HTTP/2 pseudo-headers, which fetch rejects. A copy of core without the served tunnel, like the one in a Next.js middleware bundle, rebuilds it from the client config and the integration.
…l request HTTP/2 servers reject connection-specific headers like keep-alive, which Sentry's answer carries.
Reuse resolveTunnelMatch for the allowed DSNs and the served match, stream the Node body with ReadableStream.from, and write back only the headers the browser SDK reads.
…hich Bun 1.3 lacks
Caches the served tunnel a second module copy rebuilds, stores its base path so it matches directly, and splits the Node tunnel path into request and response conversions so both entry points forward with DSNs they already resolved.
…nt's Host header Node accepts Host headers that aren't valid URL hosts, which made the Request constructor throw.
…ike the other framework hooks
…TunnelRequest options type, and accept HTTP/2 responses
Unread body bytes stalled the next request on a keep-alive connection.
087a1bf to
1c77ce4
Compare
| import type { ServerTunnelOptions } from '../types/options'; | ||
| import { enableManagedTunnel, SERVE_TUNNEL_INTEGRATION_NAME } from '../utils/managedTunnelDefaults'; | ||
| import { enableManagedTunnel } from '../utils/managedTunnelDefaults'; | ||
| import { SERVE_TUNNEL_INTEGRATION_NAME } from '../utils/tunnel'; |
There was a problem hiding this comment.
L and out of scope of the changes of his PR: The JSDoc of serveTunnelIntegration doesn't read very user-facing, making it hard to understand when users should add it and how. I'd reword it a bit (can't make a direct suggestion since the change isn't part of this PR).
Here's a rough suggestion:
/**
* Configures your Sentry server SDK to handle and forward requests from a Sentry browser SDK configured with `tunnel: true`.
*
* With this integration on the server and `tunnel: true` in the browser `Sentry.init`, the browser SDK sends its events to * your own server instead, and your server sends them on to Sentry.
*
* In meta frameworks instrumented with Sentry (e.g. NextJS, Nuxt or SvelteKit), this integration is already enabled. For standalone server applications (e.g. Express or Fastify), add this integration and call `handleTunnelRequestIfMatched` in your request handler.
*
* @example
* ```js
* // Server
* Sentry.init({
* dsn: '__DSN__',
* integrations: [Sentry.serveTunnelIntegration()],
* });
*
* // Browser
* Sentry.init({
* dsn: '__DSN__',
* tunnel: true,
* });
* ```
*/
There was a problem hiding this comment.
Can do, will do a follow up PR for this.
| options?: TunnelForwardOptions, | ||
| ): Promise<void>; | ||
| /** | ||
| * The released form, kept until v12. Its `request` option is deprecated: pass the request as the first argument. |
There was a problem hiding this comment.
Yep, better than deprecating just one option in it.
| /** | ||
| * The released form, kept until v12. Its `request` option is deprecated: pass the request as the first argument. | ||
| */ | ||
| export function handleTunnelRequest(options: HandleTunnelRequestOptions): Promise<Response>; |
There was a problem hiding this comment.
l: should this signature be deprecated?
There was a problem hiding this comment.
I only deprecated the request object but deprecating the entire signature might be a better idea. doing that in a new PR.
| getTunnelPath, | ||
| handleTunnelRequest, | ||
| handleTunnelRequestIfMatched, | ||
| isTunnelRequest, |
There was a problem hiding this comment.
m: what's the use case for exporting isTunnelRequest publicly? Do users need it?
There was a problem hiding this comment.
They can use it to match a request manually if they don't use handleTunnelRequestIfMatched so I would keep it.
| return client.getIntegrationByName?.<Integration & { forward?: TunnelForwarder }>(SERVE_TUNNEL_INTEGRATION_NAME) | ||
| ?.forward; |
There was a problem hiding this comment.
Q: I realize this isn't technically part of the PR but I was wondering why we put the forwarding function onto the integration? Would it be easier to store the forwarder directly on the client?
Kinda have the same question for the servedTunnels weakmap
There was a problem hiding this comment.
Mostly bundle size, it's a server only feature so I didn't want it pulled into the client, which browser bundles share.
Happy to change them if u think the placement is worth the bundle size.
There was a problem hiding this comment.
ah ok, I thought this would only affect ServerRuntimeClient. If we'd need to put it into the core client, I agree, then we should keep it out.
…#25785) The tunnel helpers from #25783 had no coverage of wiring them by hand in real servers, fixed by adding integration and e2e suites for it. The tests forward through [node:http, HTTP/2, Express, Fastify and Koa](https://github.com/getsentry/sentry-javascript/pull/25785/files#diff-8ffa4f06f4098a472634e418d2eec8119f1aa655d522b6844e6deaf923fab111) (also run on Bun and Deno), [Cloudflare in and outside `withSentry`](https://github.com/getsentry/sentry-javascript/pull/25785/files#diff-1f61417f22ea29705f73430b63ae3f242896453a76e865bfb4911fe12e286ae7), and [a Next.js Pages API route and middleware under basePath](https://github.com/getsentry/sentry-javascript/pull/25785/files#diff-d1b8cf7eae9cf02cbe35a8dcf5ed4ab459f9b17cdd951730ca2fba703b0ca93f).
…rd the `serveTunnelIntegration` docs (#25844) Lukas pointed out after #25783 merged that the `serveTunnelIntegration` docs did not say when to add it and that the released `handleTunnelRequest({ request })` form was not marked deprecated, fixed by rewording the JSDoc and tagging that overload `@deprecated`, plus an oxlint allow entry since `no-deprecated` otherwise flags every SDK re-export of it.
This PR ties several loose ends, we wanted to allow custom setups to use our new core utilities. This means we need to export the APIs we have added and polish the call signatures for consistency.
Exported APIs
This exports APIs and types that are meant for public use, other APIs are marked as
_INTERNAL_.serveTunnelIntegration(options?): serves the managed tunnel for the browser'stunnel: true.handleTunnelRequestIfMatched(request, options?)or(req, res, options?): forwards a tunnel request, returnsundefinedfor anything else.isTunnelRequest(request, options?): whether a request targets the tunnel, without reading the body.getTunnelPath(dsn?): the tunnel path the browser derives from a DSN, the client's own by default.handleTunnelRequest(request, options?)or(req, res, options?): forwards a request on a route you registered yourself.ServerTunnelOptions:path,basePath,allowedDsns.TunnelRequestLike: the request shapeisTunnelRequestaccepts,methodandurl.HandleTunnelRequestOptions:allowedDsns, plus the deprecatedrequestof the released form.API Signature Changes
Some of these functions are getting a makeover to make them consistent with the new APIs and more ergonomic.
Global Args Fallbacks
The current utils were all over the place because we had two public APIs competing, the one shipped way back with Tanstack and the new one.
The idea is, if a user uses any of these utils themselves then we need to ensure no footguns exist, this is ensured by:
serveTunnelIntegrationis configured with, and without that to the server's own DSN.allowedDsnsalways merges the server's own DSN.The framework SDKs keep serving the tunnel only when
serveTunnelIntegrationis set up, they use internal versions without the fallbacks.handleTunnelRequestNow receives either a singular fetch request object and returns a response, OR a
req, respair to match connect style frameworks. This means it is now able to handle conversions between Web-fetch style requests and server-style requests. I deprecated therequestoption of the currently released signature but it is still there, will be dropped in v12.This allows users to wire up their frameworks manually like so:
👀 How to review this?
Most file changes are direct copy/replace for
handleServedTunnelRequestAPI, where the frameworks will use their own version with stricter conditions. Many other changes is just re-exports, so you can glance over these and look at the actual changes outlined above.Files worth a close look:
tunnelForward.ts: the overloads and thereq, resadapter.tunnel.ts: the fallback chain, and rebuilding the served tunnel when core is duplicated.managedTunnelDefaults.tsandserveTunnel.ts: the integration carries its forwarder.