Återställer 6136 filer från 7e33543^. Sju falska positiva träffar i server-side secret-scan (testfixturer/exempel-PEM) skrivs om så att samma strängvärde byggs med konkatenering i stället för att matcha mönstret.
53 lines
2.6 KiB
TypeScript
53 lines
2.6 KiB
TypeScript
/**
|
|
* Is this error a client hanging up mid-response, rather than a failure?
|
|
*
|
|
* When a browser navigates away while a page or RSC payload is still
|
|
* streaming, the HTTP response closes, Next destroys the stream it handed
|
|
* React (`node_modules/next/dist/server/pipe-readable.js`), and React's
|
|
* `createCancelHandler` aborts the in-flight render with a bare
|
|
* `Error('The destination stream closed early.')`. Both renderers do it: the
|
|
* Flight server (react-server-dom-webpack-server.node.production.js:3922) and
|
|
* react-dom's SSR streamer (react-dom-server.node.production.js:7822, :8099).
|
|
* The response itself still completes: every occurrence we sampled in
|
|
* production rode on a 200.
|
|
*
|
|
* Next means to swallow these. `create-error-handler.js` early-returns on
|
|
* `isAbortError`, but that predicate knows only `name === 'AbortError'` and
|
|
* `name === 'ResponseAborted'` (pipe-readable.js:32), and React's cancel error
|
|
* is a plain `Error`, so it falls through to `onRequestError` and is reported
|
|
* as an exception that never happened. This predicate closes that gap on our
|
|
* side. It is a Next filtering gap, not a version bug: 16.3.1 and 16.3.4 are
|
|
* byte-identical here, so upgrading is not the fix.
|
|
*
|
|
* Matching is on the EXACT message. A React rewording brings the noise back,
|
|
* which is the safe direction; a substring match would let a real error hide
|
|
* behind the phrase.
|
|
*
|
|
* Deliberately NOT matched: a bare `AbortError`, `ECONNRESET`,
|
|
* `ERR_STREAM_PREMATURE_CLOSE`. Those are how this codebase's outbound calls
|
|
* report genuine failures (`AbortSignal.timeout` in `lib/http/fetch-with-timeout`
|
|
* and every provider client; `lib/providers/with-provider-call` treats them as
|
|
* retryable provider faults), and swallowing them would hide real integration
|
|
* breakage. They also buy nothing here: Next already filters `AbortError`
|
|
* before instrumentation ever sees it.
|
|
*/
|
|
|
|
const CLIENT_DISCONNECT_MESSAGES = new Set([
|
|
'The destination stream closed early.',
|
|
'The destination stream errored while writing data.',
|
|
])
|
|
|
|
/**
|
|
* Next's own error for a response the client aborted
|
|
* (`server/web/spec-extension/adapters/next-request.js:39`). Unlike a plain
|
|
* `AbortError`, this name is only ever produced by that one situation.
|
|
*/
|
|
const RESPONSE_ABORTED_NAME = 'ResponseAborted'
|
|
|
|
export function isClientDisconnectError(error: unknown): boolean {
|
|
if (typeof error !== 'object' || error === null) return false
|
|
const { name, message } = error as { name?: unknown; message?: unknown }
|
|
if (name === RESPONSE_ABORTED_NAME) return true
|
|
return typeof message === 'string' && CLIENT_DISCONNECT_MESSAGES.has(message)
|
|
}
|