From 56d50e677bc2e3ad28dfb0949667b1ba3894aead Mon Sep 17 00:00:00 2001 From: Steve Date: Tue, 24 Mar 2020 00:32:58 +0800 Subject: [PATCH] Fix SSR issues for SignalR: require is not defined (#19832) --- .../clients/ts/signalr/src/FetchHttpClient.ts | 55 ++++++++++--------- .../clients/ts/signalr/src/HttpConnection.ts | 31 ++++++----- 2 files changed, 44 insertions(+), 42 deletions(-) diff --git a/src/SignalR/clients/ts/signalr/src/FetchHttpClient.ts b/src/SignalR/clients/ts/signalr/src/FetchHttpClient.ts index ddfe8503a1..2700660f64 100644 --- a/src/SignalR/clients/ts/signalr/src/FetchHttpClient.ts +++ b/src/SignalR/clients/ts/signalr/src/FetchHttpClient.ts @@ -9,35 +9,36 @@ import { HttpClient, HttpRequest, HttpResponse } from "./HttpClient"; import { ILogger, LogLevel } from "./ILogger"; import { Platform } from "./Utils"; -let abortControllerType: { prototype: AbortController, new(): AbortController }; -let fetchType: (input: RequestInfo, init?: RequestInit) => Promise; -let jar: tough.CookieJar; -if (typeof fetch === "undefined") { - // In order to ignore the dynamic require in webpack builds we need to do this magic - // @ts-ignore: TS doesn't know about these names - const requireFunc = typeof __webpack_require__ === "function" ? __non_webpack_require__ : require; - - // Cookies aren't automatically handled in Node so we need to add a CookieJar to preserve cookies across requests - jar = new (requireFunc("tough-cookie")).CookieJar(); - fetchType = requireFunc("node-fetch"); - - // node-fetch doesn't have a nice API for getting and setting cookies - // fetch-cookie will wrap a fetch implementation with a default CookieJar or a provided one - fetchType = requireFunc("fetch-cookie")(fetchType, jar); - - // Node needs EventListener methods on AbortController which our custom polyfill doesn't provide - abortControllerType = requireFunc("abort-controller"); -} else { - fetchType = fetch; - abortControllerType = AbortController; -} - export class FetchHttpClient extends HttpClient { + private readonly abortControllerType: { prototype: AbortController, new(): AbortController }; + private readonly fetchType: (input: RequestInfo, init?: RequestInit) => Promise; + private readonly jar?: tough.CookieJar; + private readonly logger: ILogger; public constructor(logger: ILogger) { super(); this.logger = logger; + + if (typeof fetch === "undefined") { + // In order to ignore the dynamic require in webpack builds we need to do this magic + // @ts-ignore: TS doesn't know about these names + const requireFunc = typeof __webpack_require__ === "function" ? __non_webpack_require__ : require; + + // Cookies aren't automatically handled in Node so we need to add a CookieJar to preserve cookies across requests + this.jar = new (requireFunc("tough-cookie")).CookieJar(); + this.fetchType = requireFunc("node-fetch"); + + // node-fetch doesn't have a nice API for getting and setting cookies + // fetch-cookie will wrap a fetch implementation with a default CookieJar or a provided one + this.fetchType = requireFunc("fetch-cookie")(this.fetchType, this.jar); + + // Node needs EventListener methods on AbortController which our custom polyfill doesn't provide + this.abortControllerType = requireFunc("abort-controller"); + } else { + this.fetchType = fetch.bind(self); + this.abortControllerType = AbortController; + } } /** @inheritDoc */ @@ -54,7 +55,7 @@ export class FetchHttpClient extends HttpClient { throw new Error("No url defined."); } - const abortController = new abortControllerType(); + const abortController = new this.abortControllerType(); let error: any; // Hook our abortSignal into the abort controller @@ -79,7 +80,7 @@ export class FetchHttpClient extends HttpClient { let response: Response; try { - response = await fetchType(request.url!, { + response = await this.fetchType(request.url!, { body: request.content!, cache: "no-cache", credentials: request.withCredentials === true ? "include" : "same-origin", @@ -127,9 +128,9 @@ export class FetchHttpClient extends HttpClient { public getCookieString(url: string): string { let cookies: string = ""; - if (Platform.isNode) { + if (Platform.isNode && this.jar) { // @ts-ignore: unused variable - jar.getCookies(url, (e, c) => cookies = c.join("; ")); + this.jar.getCookies(url, (e, c) => cookies = c.join("; ")); } return cookies; } diff --git a/src/SignalR/clients/ts/signalr/src/HttpConnection.ts b/src/SignalR/clients/ts/signalr/src/HttpConnection.ts index c42c882478..e439ba159b 100644 --- a/src/SignalR/clients/ts/signalr/src/HttpConnection.ts +++ b/src/SignalR/clients/ts/signalr/src/HttpConnection.ts @@ -14,7 +14,7 @@ import { WebSocketTransport } from "./WebSocketTransport"; /** @private */ const enum ConnectionState { - Connecting = "Connecting ", + Connecting = "Connecting", Connected = "Connected", Disconnected = "Disconnected", Disconnecting = "Disconnecting", @@ -39,16 +39,6 @@ export interface IAvailableTransport { const MAX_REDIRECTS = 100; -let WebSocketModule: any = null; -let EventSourceModule: any = null; -if (Platform.isNode && typeof require !== "undefined") { - // In order to ignore the dynamic require in webpack builds we need to do this magic - // @ts-ignore: TS doesn't know about these names - const requireFunc = typeof __webpack_require__ === "function" ? __non_webpack_require__ : require; - WebSocketModule = requireFunc("ws"); - EventSourceModule = requireFunc("eventsource"); -} - /** @private */ export class HttpConnection implements IConnection { private connectionState: ConnectionState; @@ -88,19 +78,30 @@ export class HttpConnection implements IConnection { throw new Error("withCredentials option was not a 'boolean' or 'undefined' value"); } + let webSocketModule: any = null; + let eventSourceModule: any = null; + + if (Platform.isNode && typeof require !== "undefined") { + // In order to ignore the dynamic require in webpack builds we need to do this magic + // @ts-ignore: TS doesn't know about these names + const requireFunc = typeof __webpack_require__ === "function" ? __non_webpack_require__ : require; + webSocketModule = requireFunc("ws"); + eventSourceModule = requireFunc("eventsource"); + } + if (!Platform.isNode && typeof WebSocket !== "undefined" && !options.WebSocket) { options.WebSocket = WebSocket; } else if (Platform.isNode && !options.WebSocket) { - if (WebSocketModule) { - options.WebSocket = WebSocketModule; + if (webSocketModule) { + options.WebSocket = webSocketModule; } } if (!Platform.isNode && typeof EventSource !== "undefined" && !options.EventSource) { options.EventSource = EventSource; } else if (Platform.isNode && !options.EventSource) { - if (typeof EventSourceModule !== "undefined") { - options.EventSource = EventSourceModule; + if (typeof eventSourceModule !== "undefined") { + options.EventSource = eventSourceModule; } }