From 9925746c8ee3a6522bd640b5d586c83f04f2f1ba Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Thu, 14 Jan 2021 23:23:47 +0100 Subject: [PATCH] feat: add support for Socket.IO v2 clients In order to ease the migration to Socket.IO v3, the Socket.IO server can now communicate with v2 clients. ```js const io = require("socket.io")({ allowEIO3: true }); ``` This feature is disabled by default. --- lib/index.ts | 11 +++- lib/namespace.ts | 15 +++-- lib/socket.ts | 13 ++++- package-lock.json | 142 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 3 +- test/socket.io.ts | 58 ++++++++++++++++++- 6 files changed, 229 insertions(+), 13 deletions(-) diff --git a/lib/index.ts b/lib/index.ts index 27f203801b..b8bb70998b 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -100,6 +100,11 @@ interface EngineOptions { * the options that will be forwarded to the cors module */ cors: CorsOptions; + /** + * whether to enable compatibility with Socket.IO v2 clients + * @default false + */ + allowEIO3: boolean; } interface AttachOptions { @@ -522,7 +527,11 @@ export class Server extends EventEmitter { */ private onconnection(conn): Server { debug("incoming connection with id %s", conn.id); - new Client(this, conn); + const client = new Client(this, conn); + if (conn.protocol === 3) { + // @ts-ignore + client.connect("/"); + } return this; } diff --git a/lib/namespace.ts b/lib/namespace.ts index 9c00ea8f92..96b64d2aa7 100644 --- a/lib/namespace.ts +++ b/lib/namespace.ts @@ -135,11 +135,16 @@ export class Namespace extends EventEmitter { this.run(socket, (err) => { process.nextTick(() => { if ("open" == client.conn.readyState) { - if (err) - return socket._error({ - message: err.message, - data: err.data, - }); + if (err) { + if (client.conn.protocol === 3) { + return socket._error(err.data || err.message); + } else { + return socket._error({ + message: err.message, + data: err.data, + }); + } + } // track socket this.sockets.set(socket.id, socket); diff --git a/lib/socket.ts b/lib/socket.ts index 0ce963bd87..6662271bad 100644 --- a/lib/socket.ts +++ b/lib/socket.ts @@ -105,7 +105,12 @@ export class Socket extends EventEmitter { super(); this.server = nsp.server; this.adapter = this.nsp.adapter; - this.id = base64id.generateId(); // don't reuse the Engine.IO id because it's sensitive information + if (client.conn.protocol === 3) { + // @ts-ignore + this.id = nsp.name !== "/" ? nsp.name + "#" + client.id : client.id; + } else { + this.id = base64id.generateId(); // don't reuse the Engine.IO id because it's sensitive information + } this.connected = true; this.disconnected = false; this.handshake = this.buildHandshake(auth); @@ -286,7 +291,11 @@ export class Socket extends EventEmitter { _onconnect(): void { debug("socket connected - writing packet"); this.join(this.id); - this.packet({ type: PacketType.CONNECT, data: { sid: this.id } }); + if (this.conn.protocol === 3) { + this.packet({ type: PacketType.CONNECT }); + } else { + this.packet({ type: PacketType.CONNECT, data: { sid: this.id } }); + } } /** diff --git a/package-lock.json b/package-lock.json index 521549f226..61157b535b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -462,6 +462,12 @@ "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", "dev": true }, + "after": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", + "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", + "dev": true + }, "aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -535,6 +541,12 @@ "sprintf-js": "~1.0.2" } }, + "arraybuffer.slice": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", + "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==", + "dev": true + }, "astral-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", @@ -584,6 +596,12 @@ "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" }, + "blob": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", + "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==", + "dev": true + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -722,11 +740,23 @@ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, + "component-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=", + "dev": true + }, "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" }, + "component-inherit": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=", + "dev": true + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -830,9 +860,9 @@ "dev": true }, "engine.io": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-4.0.6.tgz", - "integrity": "sha512-rf7HAVZpcRrcKEKddgIzYUnwg0g5HE1RvJaTLwkcfJmce4g+po8aMuE6vxzp6JwlK8FEq/vi0KWN6tA585DjaA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-4.1.0.tgz", + "integrity": "sha512-vW7EAtn0HDQ4MtT5QbmCHF17TaYLONv2/JwdYsq9USPRZVM4zG7WB3k0Nc321z8EuSOlhGokrYlYx4176QhD0A==", "requires": { "accepts": "~1.3.4", "base64id": "2.0.0", @@ -1314,6 +1344,15 @@ "function-bind": "^1.1.1" } }, + "has-binary2": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", + "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", + "dev": true, + "requires": { + "isarray": "2.0.1" + } + }, "has-cors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", @@ -1376,6 +1415,12 @@ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -1440,6 +1485,12 @@ "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true }, + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=", + "dev": true + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2221,6 +2272,85 @@ "socket.io-parser": "~4.0.3" } }, + "socket.io-client-v2": { + "version": "npm:socket.io-client@2.4.0", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.4.0.tgz", + "integrity": "sha512-M6xhnKQHuuZd4Ba9vltCLT9oa+YvTsP8j9NcEiLElfIg8KeYPyhWOes6x4t+LTAC8enQbE/995AdTem2uNyKKQ==", + "dev": true, + "requires": { + "backo2": "1.0.2", + "component-bind": "1.0.0", + "component-emitter": "~1.3.0", + "debug": "~3.1.0", + "engine.io-client": "~3.5.0", + "has-binary2": "~1.0.2", + "indexof": "0.0.1", + "parseqs": "0.0.6", + "parseuri": "0.0.6", + "socket.io-parser": "~3.3.0", + "to-array": "0.1.4" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "engine.io-client": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.5.0.tgz", + "integrity": "sha512-12wPRfMrugVw/DNyJk34GQ5vIVArEcVMXWugQGGuw2XxUSztFNmJggZmv8IZlLyEdnpO1QB9LkcjeWewO2vxtA==", + "dev": true, + "requires": { + "component-emitter": "~1.3.0", + "component-inherit": "0.0.3", + "debug": "~3.1.0", + "engine.io-parser": "~2.2.0", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "parseqs": "0.0.6", + "parseuri": "0.0.6", + "ws": "~7.4.2", + "xmlhttprequest-ssl": "~1.5.4", + "yeast": "0.1.2" + } + }, + "engine.io-parser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.1.tgz", + "integrity": "sha512-x+dN/fBH8Ro8TFwJ+rkB2AmuVw9Yu2mockR/p3W8f8YtExwFgDvBDi0GWyb4ZLkpahtDGZgtr3zLovanJghPqg==", + "dev": true, + "requires": { + "after": "0.8.2", + "arraybuffer.slice": "~0.0.7", + "base64-arraybuffer": "0.1.4", + "blob": "0.0.5", + "has-binary2": "~1.0.2" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "socket.io-parser": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.2.tgz", + "integrity": "sha512-FJvDBuOALxdCI9qwRrO/Rfp9yfndRtc1jSgVgV8FDraihmSP/MLGD5PEuJrNfjALvcQ+vMDM/33AWOYP/JSjDg==", + "dev": true, + "requires": { + "component-emitter": "~1.3.0", + "debug": "~3.1.0", + "isarray": "2.0.1" + } + } + } + }, "socket.io-parser": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.3.tgz", @@ -2408,6 +2538,12 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, + "to-array": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", + "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=", + "dev": true + }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", diff --git a/package.json b/package.json index 9ed1268a8e..1358bf51ca 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "accepts": "~1.3.4", "base64id": "~2.0.0", "debug": "~4.3.1", - "engine.io": "~4.0.6", + "engine.io": "~4.1.0", "socket.io-adapter": "~2.0.3", "socket.io-parser": "~4.0.3" }, @@ -64,6 +64,7 @@ "prettier": "^2.2.0", "rimraf": "^3.0.2", "socket.io-client": "3.0.5", + "socket.io-client-v2": "npm:socket.io-client@^2.4.0", "superagent": "^6.1.0", "supertest": "^6.0.1", "ts-node": "^9.0.0", diff --git a/test/socket.io.ts b/test/socket.io.ts index ba05df2aca..84081c4987 100644 --- a/test/socket.io.ts +++ b/test/socket.io.ts @@ -8,11 +8,11 @@ import { exec } from "child_process"; import request from "supertest"; import expect from "expect.js"; import type { AddressInfo } from "net"; +import * as io_v2 from "socket.io-client-v2"; const ioc = require("socket.io-client"); import "./support/util"; -import exp = require("constants"); // Creates a socket.io client for the given server function client(srv, nsp?: string | object, opts?: object) { @@ -26,6 +26,18 @@ function client(srv, nsp?: string | object, opts?: object) { return ioc(url, opts); } +const success = (sio, clientSocket, done) => { + sio.close(); + clientSocket.close(); + done(); +}; + +const waitFor = (emitter, event) => { + return new Promise((resolve) => { + emitter.once(event, resolve); + }); +}; + describe("socket.io", () => { it("should be the same version as client", () => { const version = require("../package").version; @@ -2430,4 +2442,48 @@ describe("socket.io", () => { }); }); }); + + describe("v2 compatibility", () => { + it("should connect if `allowEIO3` is true", (done) => { + const srv = createServer(); + const sio = new Server(srv, { + allowEIO3: true, + }); + + srv.listen(async () => { + const port = (srv.address() as AddressInfo).port; + const clientSocket = io_v2.connect(`http://localhost:${port}`, { + multiplex: false, + }); + + const [socket]: Array = await Promise.all([ + waitFor(sio, "connection"), + waitFor(clientSocket, "connect"), + ]); + + expect(socket.id).to.eql(clientSocket.id); + success(sio, clientSocket, done); + }); + }); + + it("should not connect if `allowEIO3` is false (default)", (done) => { + const srv = createServer(); + const sio = new Server(srv); + + srv.listen(() => { + const port = (srv.address() as AddressInfo).port; + const clientSocket = io_v2.connect(`http://localhost:${port}`, { + multiplex: false, + }); + + clientSocket.on("connect", () => { + done(new Error("should not happen")); + }); + + clientSocket.on("connect_error", () => { + success(sio, clientSocket, done); + }); + }); + }); + }); });