Skip to content

Commit

Permalink
feat: add support for Socket.IO v2 clients
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
darrachequesne committed Jan 14, 2021
1 parent de8dffd commit 9925746
Showing 6 changed files with 229 additions and 13 deletions.
11 changes: 10 additions & 1 deletion lib/index.ts
Original file line number Diff line number Diff line change
@@ -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;
}

15 changes: 10 additions & 5 deletions lib/namespace.ts
Original file line number Diff line number Diff line change
@@ -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);
13 changes: 11 additions & 2 deletions lib/socket.ts
Original file line number Diff line number Diff line change
@@ -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 } });
}
}

/**
142 changes: 139 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
58 changes: 57 additions & 1 deletion test/socket.io.ts
Original file line number Diff line number Diff line change
@@ -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<any> = 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);
});
});
});
});
});

0 comments on commit 9925746

Please # to comment.