Skip to content

Commit 7cd8707

Browse files
fix: Fix undici browser compatibility with dynamic imports (#424)
Co-authored-by: Claude <[email protected]>
1 parent 87d1780 commit 7cd8707

File tree

1 file changed

+34
-8
lines changed

1 file changed

+34
-8
lines changed

src/utils/fetch-with-retry.ts

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { Agent } from 'undici'
21
import type { HttpResponse, RetryConfig, CustomFetch, CustomFetchResponse } from '../types/http'
32
import { isNetworkError } from '../types/http'
43

@@ -16,13 +15,40 @@ const DEFAULT_RETRY_CONFIG: RetryConfig = {
1615
}
1716

1817
/**
19-
* HTTP agent with keepAlive disabled to prevent hanging connections
20-
* This ensures the process exits immediately after requests complete
18+
* Type for the undici Agent - represents what we need from the Agent instance
19+
* We don't need to match all properties, just what's required for the dispatcher
2120
*/
22-
const httpAgent = new Agent({
23-
keepAliveTimeout: 1, // Close connections after 1ms of idle time
24-
keepAliveMaxTimeout: 1, // Maximum time to keep connections alive
25-
})
21+
type UndiciAgent = {
22+
// This is an opaque type - we just need to be able to pass it as a dispatcher
23+
readonly [key: string]: unknown
24+
}
25+
26+
/**
27+
* Cached HTTP agent to prevent creating multiple agents
28+
* null = not initialized, undefined = browser env, UndiciAgent = Node.js env
29+
*/
30+
let httpAgent: UndiciAgent | undefined | null = null
31+
32+
/**
33+
* Gets the HTTP agent for Node.js environments or undefined for browser environments.
34+
* Uses dynamic import to avoid loading undici in browser environments.
35+
*/
36+
async function getHttpAgent(): Promise<UndiciAgent | undefined> {
37+
if (httpAgent === null) {
38+
if (typeof process !== 'undefined' && process.versions?.node) {
39+
// We're in Node.js - dynamically import undici
40+
const { Agent } = await import('undici')
41+
httpAgent = new Agent({
42+
keepAliveTimeout: 1, // Close connections after 1ms of idle time
43+
keepAliveMaxTimeout: 1, // Maximum time to keep connections alive
44+
}) as unknown as UndiciAgent
45+
} else {
46+
// We're in browser - no agent needed
47+
httpAgent = undefined
48+
}
49+
}
50+
return httpAgent
51+
}
2652

2753
/**
2854
* Converts Headers object to a plain object
@@ -141,7 +167,7 @@ export async function fetchWithRetry<T = unknown>(args: {
141167
...fetchOptions,
142168
signal: requestSignal,
143169
// @ts-expect-error - dispatcher is a valid option for Node.js fetch but not in the TS types
144-
dispatcher: httpAgent,
170+
dispatcher: await getHttpAgent(),
145171
})
146172
fetchResponse = convertResponseToCustomFetch(nativeResponse)
147173
}

0 commit comments

Comments
 (0)