{"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":null,"httpStatus":null,"severity":"error","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/294cab2f9b2e03af6b953820c7ab497c3c8b7ad9/src/SignalR/clients/ts/signalr/src/DefaultHttpClient.ts#L6-L42","documentation":"Thrown by the DefaultHttpClient constructor when neither a global fetch nor XMLHttpRequest is available in the runtime. DefaultHttpClient picks FetchHttpClient if fetch exists or it's Node, else XhrHttpClient if XMLHttpRequest exists, else throws this. It means the host environment is missing both HTTP primitives SignalR can use.","triggerScenarios":"Constructing new DefaultHttpClient(logger) (done automatically by new HubConnectionBuilder()) in an environment with no fetch and no XHR: an old Node version (<18 with node-fetch not installed), a bare minimal JS engine, or a sandbox stripping globals.","commonSituations":"Running SignalR in an old Node (<18) without polyfilling fetch/node-fetch; a test runner or worker context lacking DOM APIs; a bundler misconfiguring the environment so Platform.isNode is false and globals are absent; React Native edge cases.","solutions":["On Node <18: install node-fetch and abort-controller and ensure they're required/polyfilled before constructing the connection.","Upgrade to Node >=18 where global fetch is available.","In non-browser runtimes, provide a custom HttpClient via HubConnectionBuilder.configureLogging/.withHttpClient (pass a custom HttpClient subclass) to inject fetch manually.","In minimal JS engines, polyfill both fetch and XMLHttpRequest before starting the connection."],"exampleFix":"// before (Node 16, no fetch)\nconst conn = new signalR.HubConnectionBuilder().withUrl('/hub').build();\n// DefaultHttpClient throws 'No usable HttpClient found.'\n\n// after\nimport fetch from 'node-fetch';\nimport AbortController from 'abort-controller';\n(globalThis as any).fetch = fetch;\n(globalThis as any).AbortController = AbortController;\nconst conn = new signalR.HubConnectionBuilder().withUrl('/hub').build();","handlingStrategy":"validation","validationCode":"// detect the missing HTTP primitive BEFORE building the connection\nfunction environmentSupportsHttp(): boolean {\n  return typeof fetch !== 'undefined' || typeof XMLHttpRequest !== 'undefined';\n}\nif (!environmentSupportsHttp()) {\n  throw new Error('This runtime lacks fetch and XMLHttpRequest; polyfill before starting SignalR.');\n}","typeGuard":"function hasFetch(): boolean { return typeof fetch !== 'undefined'; }\nfunction hasXhr(): boolean { return typeof XMLHttpRequest !== 'undefined'; }","tryCatchPattern":"try {\n  const conn = new signalR.HubConnectionBuilder().withUrl('/hub').build();\n} catch (e) {\n  if (e.message === 'No usable HttpClient found.') {\n    // polyfill fetch/XHR, then retry, or supply a custom HttpClient\n    throw new Error('Install node-fetch/abort-controller or upgrade to Node >=18');\n  }\n  throw e;\n}","preventionTips":["On Node <18, require node-fetch and abort-controller before constructing the connection.","Prefer Node >=18 where global fetch exists.","In exotic runtimes, provide a custom HttpClient implementation to HubConnectionBuilder."],"tags":["http-client","environment","fetch","xhr","node","polyfill"],"analyzedSha":"294cab2f9b2e03af6b953820c7ab497c3c8b7ad9","analyzedAt":"2026-08-06T20:08:02.189Z","schemaVersion":2},"datasetVersion":"2026-08-06T23:17:07.152Z"}