Repository: websockets/ws
Pull request #1329 (2018-03-15 17:26:51)

[fix] Allow URL instances as URL for WebSocket constructors
Addresses https://github.com/websockets/ws/issues/1143

lpinca: https://github.com/websockets/ws/blob/57d3dbb9e1b6d81132bbf2c18a35f09eab783a87/lib/websocket.js#L513

No longer works when using a WHATWG URL.
lpinca: I think this can be merged.

Patch:
diff --git a/doc/ws.md b/doc/ws.md
index 18e707ec..cc634ad9 100644
--- a/doc/ws.md
+++ b/doc/ws.md
@@ -183,7 +183,7 @@ This class represents a WebSocket. It extends the `EventEmitter`.
 
 ### new WebSocket(address[, protocols][, options])
 
-- `address` {String} The URL to which to connect.
+- `address` {String|url.Url|url.URL} The URL to which to connect.
 - `protocols` {String|Array} The list of subprotocols.
 - `options` {Object}
   - `protocol` {String} Value of the `Sec-WebSocket-Protocol` header.
diff --git a/lib/websocket.js b/lib/websocket.js
index 87f3606f..53aa1ebc 100644
--- a/lib/websocket.js
+++ b/lib/websocket.js
@@ -27,7 +27,7 @@ class WebSocket extends EventEmitter {
   /**
    * Create a new `WebSocket`.
    *
-   * @param {String} address The URL to which to connect
+   * @param {(String|url.Url|url.URL)} address The URL to which to connect
    * @param {(String|String[])} protocols The subprotocols
    * @param {Object} options Connection options
    */
@@ -404,7 +404,7 @@ module.exports = WebSocket;
 /**
  * Initialize a WebSocket client.
  *
- * @param {String} address The URL to which to connect
+ * @param {(String|url.Url|url.URL)} address The URL to which to connect
  * @param {String[]} protocols The list of subprotocols
  * @param {Object} options Connection options
  * @param {String} options.protocol Value of the `Sec-WebSocket-Protocol` header
@@ -463,24 +463,35 @@ function initAsClient (address, protocols, options) {
   }
 
   this._isServer = false;
-  this.url = address;
 
-  const serverUrl = url.parse(address);
+  var serverUrl;
+
+  if (typeof address === 'object' && address.href !== undefined) {
+    serverUrl = address;
+    this.url = address.href;
+  } else {
+    serverUrl = url.parse(address);
+    this.url = address;
+  }
+
   const isUnixSocket = serverUrl.protocol === 'ws+unix:';
 
-  if (!serverUrl.host && (!isUnixSocket || !serverUrl.path)) {
-    throw new Error(`Invalid URL: ${address}`);
+  if (!serverUrl.host && (!isUnixSocket || !serverUrl.pathname)) {
+    throw new Error(`Invalid URL: ${this.url}`);
   }
 
   const isSecure = serverUrl.protocol === 'wss:' || serverUrl.protocol === 'https:';
   const key = crypto.randomBytes(16).toString('base64');
   const httpObj = isSecure ? https : http;
+  const path = serverUrl.search
+    ? `${serverUrl.pathname || '/'}${serverUrl.search}`
+    : serverUrl.pathname || '/';
   var perMessageDeflate;
 
   const requestOptions = {
     port: serverUrl.port || (isSecure ? 443 : 80),
     host: serverUrl.hostname,
-    path: '/',
+    path: path,
     headers: {
       'Sec-WebSocket-Version': options.protocolVersion,
       'Sec-WebSocket-Key': key,
@@ -511,24 +522,18 @@ function initAsClient (address, protocols, options) {
   }
   if (options.host) requestOptions.headers.Host = options.host;
   if (serverUrl.auth) requestOptions.auth = serverUrl.auth;
+  else if (serverUrl.username || serverUrl.password) {
+    requestOptions.auth = `${serverUrl.username}:${serverUrl.password}`;
+  }
 
   if (options.localAddress) requestOptions.localAddress = options.localAddress;
   if (options.family) requestOptions.family = options.family;
 
   if (isUnixSocket) {
-    const parts = serverUrl.path.split(':');
+    const parts = path.split(':');
 
     requestOptions.socketPath = parts[0];
     requestOptions.path = parts[1];
-  } else if (serverUrl.path) {
-    //
-    // Make sure that path starts with `/`.
-    //
-    if (serverUrl.path.charAt(0) !== '/') {
-      requestOptions.path = `/${serverUrl.path}`;
-    } else {
-      requestOptions.path = serverUrl.path;
-    }
   }
 
   var agent = options.agent;


Test patch:
diff --git a/test/websocket.test.js b/test/websocket.test.js
index b5edea86..79d0adde 100644
--- a/test/websocket.test.js
+++ b/test/websocket.test.js
@@ -7,6 +7,7 @@ const crypto = require('crypto');
 const https = require('https');
 const http = require('http');
 const dns = require('dns');
+const url = require('url');
 const fs = require('fs');
 const os = require('os');
 
@@ -26,6 +27,30 @@ describe('WebSocket', function () {
       );
     });
 
+    it('accepts `url.Url` objects as url', function (done) {
+      const agent = new CustomAgent();
+
+      agent.addRequest = (req) => {
+        assert.strictEqual(req.path, '/');
+        done();
+      };
+
+      const ws = new WebSocket(url.parse('ws://localhost'), { agent });
+    });
+
+    it('accepts `url.URL` objects as url', function (done) {
+      if (!url.URL) return this.skip();
+
+      const agent = new CustomAgent();
+
+      agent.addRequest = (req) => {
+        assert.strictEqual(req.path, '/');
+        done();
+      };
+
+      const ws = new WebSocket(new url.URL('ws://localhost'), { agent });
+    });
+
     describe('options', function () {
       it('accepts an `agent` option', function (done) {
         const agent = new CustomAgent();
@@ -1781,7 +1806,7 @@ describe('WebSocket', function () {
   });
 
   describe('Request headers', function () {
-    it('adds the authorization header if userinfo is present', function (done) {
+    it('adds the authorization header if the url has userinfo (1/2)', function (done) {
       const agent = new CustomAgent();
       const auth = 'test:testpass';
 
@@ -1796,6 +1821,25 @@ describe('WebSocket', function () {
       const ws = new WebSocket(`ws://${auth}@localhost`, { agent });
     });
 
+    it('adds the authorization header if the url has userinfo (2/2)', function (done) {
+      if (!url.URL) return this.skip();
+
+      const agent = new CustomAgent();
+      const auth = 'test:testpass';
+
+      agent.addRequest = (req) => {
+        assert.strictEqual(
+          req._headers.authorization,
+          `Basic ${Buffer.from(auth).toString('base64')}`
+        );
+        done();
+      };
+
+      const ws = new WebSocket(new url.URL(`ws://${auth}@localhost`), {
+        agent
+      });
+    });
+
     it('adds custom headers', function (done) {
       const agent = new CustomAgent();