{"record":{"id":"e93e6b01da6595d1","repo":"dotnet/aspnetcore","slug":"no-usable-httpclient-found","errorCode":null,"errorMessage":"No usable HttpClient found.","messagePattern":"No usable HttpClient found\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"src/SignalR/clients/ts/signalr/src/DefaultHttpClient.ts","lineNumber":24,"sourceCode":"import { HttpClient, HttpRequest, HttpResponse } from \"./HttpClient\";\nimport { ILogger } from \"./ILogger\";\nimport { Platform } from \"./Utils\";\nimport { XhrHttpClient } from \"./XhrHttpClient\";\n\n/** Default implementation of {@link @microsoft/signalr.HttpClient}. */\nexport class DefaultHttpClient extends HttpClient {\n    private readonly _httpClient: HttpClient;\n\n    /** Creates a new instance of the {@link @microsoft/signalr.DefaultHttpClient}, using the provided {@link @microsoft/signalr.ILogger} to log messages. */\n    public constructor(logger: ILogger) {\n        super();\n\n        if (typeof fetch !== \"undefined\" || Platform.isNode) {\n            this._httpClient = new FetchHttpClient(logger);\n        } else if (typeof XMLHttpRequest !== \"undefined\") {\n            this._httpClient = new XhrHttpClient(logger);\n        } else {\n            throw new Error(\"No usable HttpClient found.\");\n        }\n    }\n\n    /** @inheritDoc */\n    public send(request: HttpRequest): Promise<HttpResponse> {\n        // Check that abort was not signaled before calling send\n        if (request.abortSignal && request.abortSignal.aborted) {\n            return Promise.reject(new AbortError());\n        }\n\n        if (!request.method) {\n            return Promise.reject(new Error(\"No method defined.\"));\n        }\n        if (!request.url) {\n            return Promise.reject(new Error(\"No url defined.\"));\n        }\n\n        return this._httpClient.send(request);","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/dotnet/aspnetcore/blob/3600ca084e9c8b5f4174fc5e747f4c52d2100806/src/SignalR/clients/ts/signalr/src/DefaultHttpClient.ts#L6-L42","documentation":"DefaultHttpClient picks an HTTP backend at construction time: it prefers the Fetch API (or Node's fetch/Platform.isNode), then falls back to XMLHttpRequest. If neither global is present, there is no transport available to perform HTTP requests, so the constructor throws synchronously.","triggerScenarios":"Instantiating new DefaultHttpClient(logger) in a JavaScript runtime that exposes neither global fetch nor global XMLHttpRequest. Common in older Node.js (< 18, no built-in fetch) without a polyfill, or in a minimal/embedded JS engine (QuickJS, duktape, some React Native renderers without the network polyfill).","commonSituations":"Running the SignalR client on Node.js 16 or earlier (fetch is not global there). Bundling for a non-browser target with tree-shaking that strips the fetch polyfill. Using the client inside a Web Worker context that lacks XMLHttpRequest in some browsers, when fetch detection fails.","solutions":["Upgrade to Node.js 18+ where fetch is a global, or install and register a fetch polyfill (e.g. node-fetch assigned to globalThis.fetch) before constructing the client.","If you must support an older environment, supply your own HttpClient implementation to HttpConnection via options.httpClient.","Verify that the bundler is not stripping window.fetch/XMLHttpRequest via dead-code elimination; mark the SignalR package as external or add the globals to your platform.","In React Native, ensure the networking globals are loaded before any SignalR import."],"exampleFix":"// before: Node 16, no fetch global\nconst connection = new signalR.HubConnectionBuilder().withUrl(url).build();\n\n// after: polyfill fetch before importing signalr\nimport { fetch as undiciFetch } from \"undici\";\n(globalThis as any).fetch = (globalThis as any).fetch ?? undiciFetch;\nimport * as signalR from \"@microsoft/signalr\";\nconst connection = new signalR.HubConnectionBuilder().withUrl(url).build();","handlingStrategy":"validation","validationCode":"function hasHttpBackend(): boolean {\n  return typeof fetch !== \"undefined\" || typeof XMLHttpRequest !== \"undefined\";\n}\nif (!hasHttpBackend()) {\n  // register polyfills before importing signalr\n  throw new Error(\"Environment has no HTTP backend — install node-fetch or upgrade Node.\");\n}","typeGuard":"function supportsDefaultHttpClient(): boolean {\n  return typeof fetch !== \"undefined\" || typeof XMLHttpRequest !== \"undefined\" || (typeof process !== \"undefined\" && process.versions?.node != null && typeof globalThis.fetch === \"function\");\n}","tryCatchPattern":"try {\n  const client = new signalR.HttpClient(...);\n} catch (e) {\n  if (e instanceof Error && e.message.includes(\"No usable HttpClient\")) {\n    // install polyfill and retry, or supply a custom HttpClient\n  }\n  throw e;\n}","preventionTips":["Run on Node 18+ (built-in fetch) or polyfill globalThis.fetch before importing signalr.","Verify fetch/XMLHttpRequest exist in your target runtime as part of your smoke tests.","For exotic runtimes, implement and inject a custom HttpClient via options.httpClient."],"tags":["http-client","environment","fetch","xmlhttprequest","node"],"backgroundTag":null,"analyzedSha":"3600ca084e9c8b5f4174fc5e747f4c52d2100806","analyzedAt":"2026-08-11T16:32:30.678Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}