From 095c1c1759daef34e18a94c86236e4f02644ebe9 Mon Sep 17 00:00:00 2001
From: Maxim Dukhanov <m.dukhanov@gmail.com>
Date: Fri, 8 Feb 2019 05:51:24 +0300
Subject: [PATCH] Add webworker support to SignalR JS client (#7058)

* Added Platform utils to detect platform type
* Added additional build for WebWorker
* Changed env param from webworker to platform to make ability to specify platform to the build script
* Updated the readme file with SignalR WebWorker instructions
---
 src/SignalR/clients/ts/signalr/README.md      | 23 +++++++++++++++++++
 src/SignalR/clients/ts/signalr/package.json   |  7 ++++--
 .../clients/ts/signalr/src/HttpConnection.ts  | 15 ++++++------
 .../signalr/src/ServerSentEventsTransport.ts  |  4 ++--
 src/SignalR/clients/ts/signalr/src/Utils.ts   | 16 +++++++++++++
 .../ts/signalr/src/WebSocketTransport.ts      |  4 ++--
 .../clients/ts/signalr/webpack.config.js      |  5 ++--
 src/SignalR/clients/ts/webpack.config.base.js |  3 ++-
 8 files changed, 60 insertions(+), 17 deletions(-)

diff --git a/src/SignalR/clients/ts/signalr/README.md b/src/SignalR/clients/ts/signalr/README.md
index 51105078a26..f38a93401ed 100644
--- a/src/SignalR/clients/ts/signalr/README.md
+++ b/src/SignalR/clients/ts/signalr/README.md
@@ -14,6 +14,10 @@ See the [SignalR Documentation](https://docs.microsoft.com/en-us/aspnet/core/sig
 
 To use the client in a browser, copy `*.js` files from the `dist/browser` folder to your script folder include on your page using the `<script>` tag.
 
+### WebWorker
+
+To use the client in a webworker, copy `*.js` files from the `dist/webworker` folder to your script folder include on your webworker using the `importScripts` function. Note that webworker SignalR hub connection supports only absolute path to a SignalR hub.
+
 ### Node.js
 
 To use the client in a NodeJS application, install the package to your `node_modules` folder and use `require('@aspnet/signalr')` to load the module. The object returned by `require('@aspnet/signalr')` has the same members as the global `signalR` object (when used in a browser).
@@ -33,6 +37,25 @@ connection.start()
     .then(() => connection.invoke("send", "Hello"));
 ```
 
+### Example (WebWorker)
+
+
+```JavaScript
+importScripts('signalr.js');
+
+let connection = new signalR.HubConnectionBuilder()
+    .withUrl("https://example.com/signalr/chat")
+    .build();
+
+connection.on("send", data => {
+    console.log(data);
+});
+
+connection.start()
+    .then(() => connection.invoke("send", "Hello"));
+
+```
+
 ### Example (NodeJS)
 
 ```JavaScript
diff --git a/src/SignalR/clients/ts/signalr/package.json b/src/SignalR/clients/ts/signalr/package.json
index 83acbb358a5..c18ff51d0b4 100644
--- a/src/SignalR/clients/ts/signalr/package.json
+++ b/src/SignalR/clients/ts/signalr/package.json
@@ -12,12 +12,15 @@
   },
   "scripts": {
     "clean": "node ../common/node_modules/rimraf/bin.js ./dist",
-    "build": "npm run clean && npm run build:lint && npm run build:esm && npm run build:cjs && npm run build:browser && npm run build:uglify",
+    "build": "npm run clean && npm run build:lint && npm run build:esm && npm run build:cjs && npm run build:browser && npm run build:webworker && npm run build:uglify",
     "build:lint": "node ../common/node_modules/tslint/bin/tslint -c ../tslint.json -p ./tsconfig.json",
     "build:esm": "node ../common/node_modules/typescript/bin/tsc --project ./tsconfig.json --module es2015 --outDir ./dist/esm -d && node ./build/process-dts.js",
     "build:cjs": "node ../common/node_modules/typescript/bin/tsc --project ./tsconfig.json --module commonjs --outDir ./dist/cjs",
     "build:browser": "node ../common/node_modules/webpack-cli/bin/cli.js",
-    "build:uglify": "node ../common/node_modules/uglify-js/bin/uglifyjs --source-map \"url='signalr.min.js.map',content='./dist/browser/signalr.js.map'\" --comments -o ./dist/browser/signalr.min.js ./dist/browser/signalr.js",
+    "build:webworker": "node ../common/node_modules/webpack-cli/bin/cli.js --env.platform=webworker",
+    "build:uglify": "npm run build:uglify:browser && npm run build:uglify:webworker",
+    "build:uglify:browser": "node ../common/node_modules/uglify-js/bin/uglifyjs --source-map \"url='signalr.min.js.map',content='./dist/browser/signalr.js.map'\" --comments -o ./dist/browser/signalr.min.js ./dist/browser/signalr.js",
+    "build:uglify:webworker": "node ../common/node_modules/uglify-js/bin/uglifyjs --source-map \"url='signalr.min.js.map',content='./dist/webworker/signalr.js.map'\" --comments -o ./dist/webworker/signalr.min.js ./dist/webworker/signalr.js",
     "prepack": "node ../build/embed-version.js",
     "test": "echo \"Run 'npm test' in the 'clients\\ts' folder to test this package\" && exit 1"
   },
diff --git a/src/SignalR/clients/ts/signalr/src/HttpConnection.ts b/src/SignalR/clients/ts/signalr/src/HttpConnection.ts
index d3521f4f858..2d74b80ef39 100644
--- a/src/SignalR/clients/ts/signalr/src/HttpConnection.ts
+++ b/src/SignalR/clients/ts/signalr/src/HttpConnection.ts
@@ -9,7 +9,7 @@ import { ILogger, LogLevel } from "./ILogger";
 import { HttpTransportType, ITransport, TransferFormat } from "./ITransport";
 import { LongPollingTransport } from "./LongPollingTransport";
 import { ServerSentEventsTransport } from "./ServerSentEventsTransport";
-import { Arg, createLogger } from "./Utils";
+import { Arg, createLogger, Platform } from "./Utils";
 import { WebSocketTransport } from "./WebSocketTransport";
 
 /** @private */
@@ -38,7 +38,7 @@ const MAX_REDIRECTS = 100;
 
 let WebSocketModule: any = null;
 let EventSourceModule: any = null;
-if (typeof window === "undefined" && typeof require !== "undefined") {
+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;
@@ -71,18 +71,17 @@ export class HttpConnection implements IConnection {
         options = options || {};
         options.logMessageContent = options.logMessageContent || false;
 
-        const isNode = typeof window === "undefined";
-        if (!isNode && typeof WebSocket !== "undefined" && !options.WebSocket) {
+        if (!Platform.isNode && typeof WebSocket !== "undefined" && !options.WebSocket) {
             options.WebSocket = WebSocket;
-        } else if (isNode && !options.WebSocket) {
+        } else if (Platform.isNode && !options.WebSocket) {
             if (WebSocketModule) {
                 options.WebSocket = WebSocketModule;
             }
         }
 
-        if (!isNode && typeof EventSource !== "undefined" && !options.EventSource) {
+        if (!Platform.isNode && typeof EventSource !== "undefined" && !options.EventSource) {
             options.EventSource = EventSource;
-        } else if (isNode && !options.EventSource) {
+        } else if (Platform.isNode && !options.EventSource) {
             if (typeof EventSourceModule !== "undefined") {
                 options.EventSource = EventSourceModule;
             }
@@ -383,7 +382,7 @@ export class HttpConnection implements IConnection {
             return url;
         }
 
-        if (typeof window === "undefined" || !window || !window.document) {
+        if (!Platform.isBrowser || !window.document) {
             throw new Error(`Cannot resolve '${url}'.`);
         }
 
diff --git a/src/SignalR/clients/ts/signalr/src/ServerSentEventsTransport.ts b/src/SignalR/clients/ts/signalr/src/ServerSentEventsTransport.ts
index ee2de43ef59..09463c5e70d 100644
--- a/src/SignalR/clients/ts/signalr/src/ServerSentEventsTransport.ts
+++ b/src/SignalR/clients/ts/signalr/src/ServerSentEventsTransport.ts
@@ -5,7 +5,7 @@ import { HttpClient } from "./HttpClient";
 import { ILogger, LogLevel } from "./ILogger";
 import { ITransport, TransferFormat } from "./ITransport";
 import { EventSourceConstructor } from "./Polyfills";
-import { Arg, getDataDetail, sendMessage } from "./Utils";
+import { Arg, getDataDetail, Platform, sendMessage } from "./Utils";
 
 /** @private */
 export class ServerSentEventsTransport implements ITransport {
@@ -57,7 +57,7 @@ export class ServerSentEventsTransport implements ITransport {
             }
 
             let eventSource: EventSource;
-            if (typeof window !== "undefined") {
+            if (Platform.isBrowser || Platform.isWebWorker) {
                 eventSource = new this.eventSourceConstructor(url, { withCredentials: true });
             } else {
                 // Non-browser passes cookies via the dictionary
diff --git a/src/SignalR/clients/ts/signalr/src/Utils.ts b/src/SignalR/clients/ts/signalr/src/Utils.ts
index 23780eafa8c..b4e6a78c37e 100644
--- a/src/SignalR/clients/ts/signalr/src/Utils.ts
+++ b/src/SignalR/clients/ts/signalr/src/Utils.ts
@@ -23,6 +23,22 @@ export class Arg {
     }
 }
 
+/** @private */
+export class Platform {
+
+    public static get isBrowser(): boolean {
+        return typeof window === "object";
+    }
+
+    public static get isWebWorker(): boolean {
+        return typeof self === "object" && "importScripts" in self;
+    }
+
+    public static get isNode(): boolean {
+        return !this.isBrowser && !this.isWebWorker;
+    }
+}
+
 /** @private */
 export function getDataDetail(data: any, includeContent: boolean): string {
     let detail = "";
diff --git a/src/SignalR/clients/ts/signalr/src/WebSocketTransport.ts b/src/SignalR/clients/ts/signalr/src/WebSocketTransport.ts
index 74db953b5ab..a9c238599eb 100644
--- a/src/SignalR/clients/ts/signalr/src/WebSocketTransport.ts
+++ b/src/SignalR/clients/ts/signalr/src/WebSocketTransport.ts
@@ -5,7 +5,7 @@ import { HttpClient } from "./HttpClient";
 import { ILogger, LogLevel } from "./ILogger";
 import { ITransport, TransferFormat } from "./ITransport";
 import { WebSocketConstructor } from "./Polyfills";
-import { Arg, getDataDetail } from "./Utils";
+import { Arg, getDataDetail, Platform } from "./Utils";
 
 /** @private */
 export class WebSocketTransport implements ITransport {
@@ -50,7 +50,7 @@ export class WebSocketTransport implements ITransport {
             let webSocket: WebSocket | undefined;
             const cookies = this.httpClient.getCookieString(url);
 
-            if (typeof window === "undefined" && cookies) {
+            if (Platform.isNode && cookies) {
                 // Only pass cookies when in non-browser environments
                 webSocket = new this.webSocketConstructor(url, undefined, {
                     headers: {
diff --git a/src/SignalR/clients/ts/signalr/webpack.config.js b/src/SignalR/clients/ts/signalr/webpack.config.js
index 54e3456aac6..75ce866f898 100644
--- a/src/SignalR/clients/ts/signalr/webpack.config.js
+++ b/src/SignalR/clients/ts/signalr/webpack.config.js
@@ -1,10 +1,11 @@
 // Copyright (c) .NET Foundation. All rights reserved.
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
 const baseConfig = require("../webpack.config.base");
-module.exports = baseConfig(__dirname, "signalr", {
+module.exports = env => baseConfig(__dirname, "signalr", {
     // These are only used in Node environments
     // so we tell webpack not to pull them in for the browser
+    target: env && env.platform ?  env.platform : undefined,
+    platformDist: env && env.platform ?  env.platform : undefined,
     externals: [
         "websocket",
         "eventsource",
diff --git a/src/SignalR/clients/ts/webpack.config.base.js b/src/SignalR/clients/ts/webpack.config.base.js
index 6779afc2ff3..8c8f3ac31f9 100644
--- a/src/SignalR/clients/ts/webpack.config.base.js
+++ b/src/SignalR/clients/ts/webpack.config.base.js
@@ -17,6 +17,7 @@ module.exports = function (modulePath, browserBaseName, options) {
             process: false,
             Buffer: false,
         },
+        target: options.target,
         resolveLoader: {
             // Special resolution rules for loaders (which are in the 'common' directory)
             modules: [ path.resolve(__dirname, "common", "node_modules") ],
@@ -43,7 +44,7 @@ module.exports = function (modulePath, browserBaseName, options) {
         },
         output: {
             filename: `${browserBaseName}.js`,
-            path: path.resolve(modulePath, "dist", "browser"),
+            path: path.resolve(modulePath, "dist", options.platformDist || "browser"),
             library: {
                 root: pkg.umd_name.split("."),
                 amd: pkg.umd_name,
-- 
GitLab