Repository: pmmmwh/react-refresh-webpack-plugin (js)

Issue:
webpack-dev-server 4.0.0-beta.3 causes react-refresh-webpack-plugin to crash
Not sure if it's a bug, or even if it's on your side, but just a heads-up, and hopefully some info that could come in handy if people stumble upon the same issue as me.

Updating webpack-dev-server from 4.0.0-beta.2 to 4.0.0-beta.3 caused several of my apps to crash because of react-refresh-webpack-plugin 0.4.3 with the following error:

```
Uncaught DOMException: Failed to construct 'WebSocket': The URL's scheme must be either 'ws' or 'wss'. 'https' is not allowed.
    at new WebsocketClient (webpack:///node_modules/webpack-dev-server/client/clients/WebsocketClient.js?:39:20)
    at Module.initWDSSocket (webpack:///node_modules/@pmmmwh/react-refresh-webpack-plugin/sockets/WDSSocket.js?:24:24)
    at setupOverlay (webpack:///node_modules/@pmmmwh/react-refresh-webpack-plugin/client/ErrorOverlayEntry.js?:86:32)
    at runWithPatchedUrl (webpack:///node_modules/@pmmmwh/react-refresh-webpack-plugin/client/utils/patchUrl.js?:36:3)
    at eval (webpack:///node_modules/@pmmmwh/react-refresh-webpack-plugin/client/ErrorOverlayEntry.js?:82:62)
    at Module./node_modules/@pmmmwh/react-refresh-webpack-plugin/client/ErrorOverlayEntry.js?sockHost=0.0.0.0&sockPort=3001&sockProtocol=https (https://localhost:3001/vendors-node_modules_pmmmwh_react-refresh-webpack-plugin_client_ErrorOverlayEntry_js_sockHost-9da5dc.b56e57f2.js:18:1)
```

Updating react-refresh-webpack-plugin to 0.5.0-beta.7 (to get #242) and configuring it like so:

```diff
    new ReactRefreshWebpackPlugin({
+      overlay: {
+        sockProtocol: 'wss',
+        sockHost: 'localhost',
+      },
    }),
```

causes the app to load again without any errors. 

Hardcoding `sockHost`, however, had negative consequences, because now I couldn't use react-refresh on other hosts e.g. for mobile testing. If I didn't hardcode `sockHost` it was resolved to `0.0.0.0` which I have set in `devServer.host`.

Finally, removing `devServer.host` altogether and removing `sockHost` from ReactRefreshWebpackPlugin options cause it all to work again, both locally and remotely.

Pull request description:
feat: automatically override ws when wds use websockets
Fixes #391

WDS v4 beta 3 a breaking change was introduced to by default use WS client - and having this configured by default would also benefit people opting in to use WS even on WDS v3.

Patch:
diff --git a/sockets/WDSSocket.js b/sockets/WDSSocket.js
index 5c13700..d5508da 100644
--- a/sockets/WDSSocket.js
+++ b/sockets/WDSSocket.js
@@ -14,7 +14,13 @@ function initWDSSocket(messageHandler, resourceQuery) {
     const SocketClient = __webpack_dev_server_client__;
 
     const urlParts = getSocketUrlParts(resourceQuery);
-    const connection = new SocketClient(getUrlFromParts(urlParts));
+
+    let enforceWs = false;
+    if (SocketClient.name.toLowerCase().includes('websocket')) {
+      enforceWs = true;
+    }
+
+    const connection = new SocketClient(getUrlFromParts(urlParts, enforceWs));
 
     connection.onMessage(function onSocketMessage(data) {
       const message = JSON.parse(data);
diff --git a/sockets/utils/getUrlFromParts.js b/sockets/utils/getUrlFromParts.js
index 6008c10..74bef0a 100644
--- a/sockets/utils/getUrlFromParts.js
+++ b/sockets/utils/getUrlFromParts.js
@@ -1,10 +1,19 @@
 /**
  * Create a valid URL from parsed URL parts.
  * @param {import('./getSocketUrlParts').SocketUrlParts} urlParts The parsed URL parts.
+ * @param {boolean} [enforceWs] Enforce using the WebSocket protocols.
  * @returns {string} The generated URL.
  */
-function urlFromParts(urlParts) {
-  const fullProtocol = (urlParts.protocol || 'http:') + '//';
+function urlFromParts(urlParts, enforceWs) {
+  let fullProtocol = 'http:';
+  if (urlParts.protocol) {
+    fullProtocol = urlParts.protocol;
+  }
+  if (enforceWs) {
+    fullProtocol = fullProtocol.replace(/^(?:http|.+-extension|file)/i, 'ws');
+  }
+
+  fullProtocol = fullProtocol + '//';
 
   let fullHost = urlParts.hostname;
   if (urlParts.auth) {


Test patch:
diff --git a/test/unit/getUrlFromParts.test.js b/test/unit/getUrlFromParts.test.js
index 26d9bbb..f1bc436 100644
--- a/test/unit/getUrlFromParts.test.js
+++ b/test/unit/getUrlFromParts.test.js
@@ -88,4 +88,34 @@ describe('getUrlFromParts', () => {
       })
     ).toStrictEqual('http://username:password@localhost:8080/sockjs-node');
   });
+
+  it('should force WS when enforceWs is true and protocol is HTTP', () => {
+    expect(
+      getUrlFromParts(
+        {
+          auth: undefined,
+          hostname: 'localhost',
+          pathname: '/sockjs-node',
+          port: '8080',
+          protocol: 'http:',
+        },
+        true
+      )
+    ).toStrictEqual('ws://localhost:8080/sockjs-node');
+  });
+
+  it('should force WSS when enforceWs is true and protocol is HTTPS', () => {
+    expect(
+      getUrlFromParts(
+        {
+          auth: undefined,
+          hostname: 'localhost',
+          pathname: '/sockjs-node',
+          port: '8080',
+          protocol: 'https:',
+        },
+        true
+      )
+    ).toStrictEqual('wss://localhost:8080/sockjs-node');
+  });
 });