Repository: websockets/ws
Pull request #1539 (2019-03-26 14:32:20)

Add summary with links to the doc
Thanks to The Nacho Man and his [nGitHubTOC](https://imthenachoman.github.io/nGitHubTOC/).


Patch:
diff --git a/doc/ws.md b/doc/ws.md
index c23f1d6c..d569d02b 100644
--- a/doc/ws.md
+++ b/doc/ws.md
@@ -1,5 +1,49 @@
 # ws
 
+## Table of Contents
+
+- [Class: WebSocket.Server](#class-websocketserver)
+  - [new WebSocket.Server(options[, callback])](#new-websocketserveroptions-callback)
+  - [Event: 'close'](#event-close)
+  - [Event: 'connection'](#event-connection)
+  - [Event: 'error'](#event-error)
+  - [Event: 'headers'](#event-headers)
+  - [Event: 'listening'](#event-listening)
+  - [server.address()](#serveraddress)
+  - [server.clients](#serverclients)
+  - [server.close([callback])](#serverclosecallback)
+  - [server.handleUpgrade(request, socket, head, callback)](#serverhandleupgraderequest-socket-head-callback)
+  - [server.shouldHandle(request)](#servershouldhandlerequest)
+- [Class: WebSocket](#class-websocket)
+  - [Ready state constants](#ready-state-constants)
+  - [new WebSocket(address[, protocols][, options])](#new-websocketaddress-protocols-options)
+    - [UNIX Domain Sockets](#unix-domain-sockets)
+  - [Event: 'close'](#event-close-1)
+  - [Event: 'error'](#event-error-1)
+  - [Event: 'message'](#event-message)
+  - [Event: 'open'](#event-open)
+  - [Event: 'ping'](#event-ping)
+  - [Event: 'pong'](#event-pong)
+  - [Event: 'unexpected-response'](#event-unexpected-response)
+  - [Event: 'upgrade'](#event-upgrade)
+  - [websocket.addEventListener(type, listener)](#websocketaddeventlistenertype-listener)
+  - [websocket.binaryType](#websocketbinarytype)
+  - [websocket.bufferedAmount](#websocketbufferedamount)
+  - [websocket.close([code[, reason]])](#websocketclosecode-reason)
+  - [websocket.extensions](#websocketextensions)
+  - [websocket.onclose](#websocketonclose)
+  - [websocket.onerror](#websocketonerror)
+  - [websocket.onmessage](#websocketonmessage)
+  - [websocket.onopen](#websocketonopen)
+  - [websocket.ping([data[, mask]][, callback])](#websocketpingdata-mask-callback)
+  - [websocket.pong([data[, mask]][, callback])](#websocketpongdata-mask-callback)
+  - [websocket.protocol](#websocketprotocol)
+  - [websocket.readyState](#websocketreadystate)
+  - [websocket.removeEventListener(type, listener)](#websocketremoveeventlistenertype-listener)
+  - [websocket.send(data[, options][, callback])](#websocketsenddata-options-callback)
+  - [websocket.terminate()](#websocketterminate)
+  - [websocket.url](#websocketurl)
+
 ## Class: WebSocket.Server
 
 This class represents a WebSocket server. It extends the `EventEmitter`.
@@ -129,13 +173,6 @@ handshake. This allows you to inspect/modify the headers before they are sent.
 
 Emitted when the underlying server has been bound.
 
-### server.clients
-
-- {Set}
-
-A set that stores all connected clients. Please note that this property is only
-added when the `clientTracking` is truthy.
-
 ### server.address()
 
 Returns an object with `port`, `family`, and `address` properties specifying the
@@ -143,6 +180,13 @@ bound address, the address family name, and port of the server as reported by
 the operating system if listening on an IP socket. If the server is listening on
 a pipe or UNIX domain socket, the name is returned as a string.
 
+### server.clients
+
+- {Set}
+
+A set that stores all connected clients. Please note that this property is only
+added when the `clientTracking` is truthy.
+
 ### server.close([callback])
 
 Close the HTTP server if created internally, terminate all clients and call
diff --git a/lib/websocket.js b/lib/websocket.js
index 32f092f8..af810840 100644
--- a/lib/websocket.js
+++ b/lib/websocket.js
@@ -24,7 +24,7 @@ const {
 
 const readyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'];
 const protocolVersions = [8, 13];
-const closeTimeout = 30 * 1000; // Allow 30 seconds to terminate the connection cleanly.
+const closeTimeout = 30 * 1000;
 
 /**
  * Class representing a WebSocket.
@@ -87,8 +87,9 @@ class WebSocket extends EventEmitter {
   }
 
   /**
-   * This deviates from the WHATWG interface since ws doesn't support the required
-   * default "blob" type (instead we define a custom "nodebuffer" type).
+   * This deviates from the WHATWG interface since ws doesn't support the
+   * required default "blob" type (instead we define a custom "nodebuffer"
+   * type).
    *
    * @type {String}
    */
@@ -230,20 +231,16 @@ class WebSocket extends EventEmitter {
       if (err) return;
 
       this._closeFrameSent = true;
-
-      if (this._socket.writable) {
-        if (this._closeFrameReceived) this._socket.end();
-
-        //
-        // Ensure that the connection is closed even if the closing handshake
-        // fails.
-        //
-        this._closeTimer = setTimeout(
-          this._socket.destroy.bind(this._socket),
-          closeTimeout
-        );
-      }
+      if (this._closeFrameReceived) this._socket.end();
     });
+
+    //
+    // Specify a timeout for the closing handshake to complete.
+    //
+    this._closeTimer = setTimeout(
+      this._socket.destroy.bind(this._socket),
+      closeTimeout
+    );
   }
 
   /**


Test patch:
diff --git a/test/websocket.test.js b/test/websocket.test.js
index 8e63d075..49edd1ce 100644
--- a/test/websocket.test.js
+++ b/test/websocket.test.js
@@ -1305,27 +1305,6 @@ describe('WebSocket', () => {
       });
     });
 
-    it('ends connection to the server', (done) => {
-      const wss = new WebSocket.Server(
-        {
-          clientTracking: false,
-          port: 0
-        },
-        () => {
-          const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
-
-          ws.on('open', () => {
-            ws.on('close', (code, reason) => {
-              assert.strictEqual(reason, 'some reason');
-              assert.strictEqual(code, 1000);
-              wss.close(done);
-            });
-            ws.close(1000, 'some reason');
-          });
-        }
-      );
-    });
-
     it('permits all buffered data to be delivered', (done) => {
       const wss = new WebSocket.Server(
         {
@@ -1383,6 +1362,37 @@ describe('WebSocket', () => {
 
       wss.on('connection', (ws) => ws.close());
     });
+
+    it('sets a timer for the closing handshake to complete', (done) => {
+      const wss = new WebSocket.Server({ port: 0 }, () => {
+        const ws = new WebSocket(`ws://localhost:${wss.address().port}`);
+
+        ws.on('close', (code, reason) => {
+          assert.strictEqual(code, 1000);
+          assert.strictEqual(reason, 'some reason');
+          wss.close(done);
+        });
+
+        ws.on('open', () => {
+          let callbackCalled = false;
+
+          assert.strictEqual(ws._closeTimer, null);
+
+          ws.send('foo', () => {
+            callbackCalled = true;
+          });
+
+          ws.close(1000, 'some reason');
+
+          //
+          // Check that the close timer is set even if the `Sender.close()`
+          // callback is not called.
+          //
+          assert.strictEqual(callbackCalled, false);
+          assert.strictEqual(ws._closeTimer._idleTimeout, 30000);
+        });
+      });
+    });
   });
 
   describe('#terminate', () => {