Compare commits

...

7 Commits
3.1.1 ... 3.1.2

32 changed files with 998 additions and 33 deletions

View File

@@ -1,3 +1,11 @@
## [3.1.2](https://github.com/socketio/socket.io/compare/3.1.1...3.1.2) (2021-02-26)
### Bug Fixes
* ignore packets received after disconnection ([494c64e](https://github.com/socketio/socket.io/commit/494c64e44f645cbd24c645f1186d203789e84af0))
## [3.1.1](https://github.com/socketio/socket.io/compare/3.1.0...3.1.1) (2021-02-03)

View File

@@ -1,5 +1,5 @@
/*!
* Socket.IO v3.1.1
* Socket.IO v3.1.2
* (c) 2014-2021 Guillermo Rauch
* Released under the MIT License.
*/
@@ -12,7 +12,7 @@
exports["io"] = factory();
else
root["io"] = factory();
})(window, function() {
})(this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
@@ -2433,6 +2433,17 @@ var Socket = /*#__PURE__*/function (_Emitter) {
_this.pingTimeoutTimer = null;
if (typeof addEventListener === "function") {
addEventListener("beforeunload", function () {
if (_this.transport) {
// silently close the transport
_this.transport.removeAllListeners();
_this.transport.close();
}
}, false);
}
_this.open();
return _this;
@@ -3280,11 +3291,6 @@ var rEscapedNewline = /\\n/g;
*/
var callbacks;
/**
* Noop.
*/
function empty() {}
var JSONPPolling = /*#__PURE__*/function (_Polling) {
_inherits(JSONPPolling, _Polling);
@@ -3320,14 +3326,7 @@ var JSONPPolling = /*#__PURE__*/function (_Polling) {
self.onData(msg);
}); // append to query string
_this.query.j = _this.index; // prevent spurious errors from being emitted when the window is unloaded
if (typeof addEventListener === "function") {
addEventListener("beforeunload", function () {
if (self.script) self.script.onerror = empty;
}, false);
}
_this.query.j = _this.index;
return _this;
}
/**
@@ -3345,6 +3344,9 @@ var JSONPPolling = /*#__PURE__*/function (_Polling) {
*/
value: function doClose() {
if (this.script) {
// prevent spurious errors from being emitted when the window is unloaded
this.script.onerror = function () {};
this.script.parentNode.removeChild(this.script);
this.script = null;
}
@@ -4426,6 +4428,7 @@ var WS = /*#__PURE__*/function (_Transport) {
value: function doClose() {
if (typeof this.ws !== "undefined") {
this.ws.close();
this.ws = null;
}
}
/**

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

24
examples/private-messaging/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
package-lock.json

View File

@@ -0,0 +1,23 @@
# Private messaging with Socket.IO
Please read the related guide:
- [Part I](https://socket.io/get-started/private-messaging-part-1/): initial implementation
- [Part II](https://socket.io/get-started/private-messaging-part-2/): persistent user ID
- [Part III](https://socket.io/get-started/private-messaging-part-3/): persistent messages
- [Part IV](https://socket.io/get-started/private-messaging-part-4/): scaling up
## Running the frontend
```
npm install
npm run serve
```
### Running the server
```
cd server
npm install
npm start
```

View File

@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

View File

@@ -0,0 +1,43 @@
{
"name": "private-messaging",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"core-js": "^3.6.5",
"socket.io-client": "^3.1.1",
"vue": "^2.6.11"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^6.2.2",
"vue-template-compiler": "^2.6.11"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>Private messaging with Socket.IO</title>
</head>
<body>
<noscript>
<strong>We're sorry but this application doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

View File

@@ -0,0 +1,31 @@
const cluster = require("cluster");
const http = require("http");
const { setupMaster } = require("@socket.io/sticky");
const WORKERS_COUNT = 4;
if (cluster.isMaster) {
console.log(`Master ${process.pid} is running`);
for (let i = 0; i < WORKERS_COUNT; i++) {
cluster.fork();
}
cluster.on("exit", (worker) => {
console.log(`Worker ${worker.process.pid} died`);
cluster.fork();
});
const httpServer = http.createServer();
setupMaster(httpServer, {
loadBalancingMethod: "least-connection", // either "random", "round-robin" or "least-connection"
});
const PORT = process.env.PORT || 3000;
httpServer.listen(PORT, () =>
console.log(`server listening at http://localhost:${PORT}`)
);
} else {
console.log(`Worker ${process.pid} started`);
require("./index");
}

View File

@@ -0,0 +1,7 @@
version: "3"
services:
redis:
image: redis:5
ports:
- "6379:6379"

View File

@@ -0,0 +1,125 @@
const httpServer = require("http").createServer();
const Redis = require("ioredis");
const redisClient = new Redis();
const io = require("socket.io")(httpServer, {
cors: {
origin: "http://localhost:8080",
},
adapter: require("socket.io-redis")({
pubClient: redisClient,
subClient: redisClient.duplicate(),
}),
});
const { setupWorker } = require("@socket.io/sticky");
const crypto = require("crypto");
const randomId = () => crypto.randomBytes(8).toString("hex");
const { RedisSessionStore } = require("./sessionStore");
const sessionStore = new RedisSessionStore(redisClient);
const { RedisMessageStore } = require("./messageStore");
const messageStore = new RedisMessageStore(redisClient);
io.use(async (socket, next) => {
const sessionID = socket.handshake.auth.sessionID;
if (sessionID) {
const session = await sessionStore.findSession(sessionID);
if (session) {
socket.sessionID = sessionID;
socket.userID = session.userID;
socket.username = session.username;
return next();
}
}
const username = socket.handshake.auth.username;
if (!username) {
return next(new Error("invalid username"));
}
socket.sessionID = randomId();
socket.userID = randomId();
socket.username = username;
next();
});
io.on("connection", async (socket) => {
// persist session
sessionStore.saveSession(socket.sessionID, {
userID: socket.userID,
username: socket.username,
connected: true,
});
// emit session details
socket.emit("session", {
sessionID: socket.sessionID,
userID: socket.userID,
});
// join the "userID" room
socket.join(socket.userID);
// fetch existing users
const users = [];
const [messages, sessions] = await Promise.all([
messageStore.findMessagesForUser(socket.userID),
sessionStore.findAllSessions(),
]);
const messagesPerUser = new Map();
messages.forEach((message) => {
const { from, to } = message;
const otherUser = socket.userID === from ? to : from;
if (messagesPerUser.has(otherUser)) {
messagesPerUser.get(otherUser).push(message);
} else {
messagesPerUser.set(otherUser, [message]);
}
});
sessions.forEach((session) => {
users.push({
userID: session.userID,
username: session.username,
connected: session.connected,
messages: messagesPerUser.get(session.userID) || [],
});
});
socket.emit("users", users);
// notify existing users
socket.broadcast.emit("user connected", {
userID: socket.userID,
username: socket.username,
connected: true,
messages: [],
});
// forward the private message to the right recipient (and to other tabs of the sender)
socket.on("private message", ({ content, to }) => {
const message = {
content,
from: socket.userID,
to,
};
socket.to(to).to(socket.userID).emit("private message", message);
messageStore.saveMessage(message);
});
// notify users upon disconnection
socket.on("disconnect", async () => {
const matchingSockets = await io.in(socket.userID).allSockets();
const isDisconnected = matchingSockets.size === 0;
if (isDisconnected) {
// notify other users
socket.broadcast.emit("user disconnected", socket.userID);
// update the connection status of the session
sessionStore.saveSession(socket.sessionID, {
userID: socket.userID,
username: socket.username,
connected: false,
});
}
});
});
setupWorker(io);

View File

@@ -0,0 +1,54 @@
/* abstract */ class MessageStore {
saveMessage(message) {}
findMessagesForUser(userID) {}
}
class InMemoryMessageStore extends MessageStore {
constructor() {
super();
this.messages = [];
}
saveMessage(message) {
this.messages.push(message);
}
findMessagesForUser(userID) {
return this.messages.filter(
({ from, to }) => from === userID || to === userID
);
}
}
const CONVERSATION_TTL = 24 * 60 * 60;
class RedisMessageStore extends MessageStore {
constructor(redisClient) {
super();
this.redisClient = redisClient;
}
saveMessage(message) {
const value = JSON.stringify(message);
this.redisClient
.multi()
.rpush(`messages:${message.from}`, value)
.rpush(`messages:${message.to}`, value)
.expire(`messages:${message.from}`, CONVERSATION_TTL)
.expire(`messages:${message.to}`, CONVERSATION_TTL)
.exec();
}
findMessagesForUser(userID) {
return this.redisClient
.lrange(`messages:${userID}`, 0, -1)
.then((results) => {
return results.map((result) => JSON.parse(result));
});
}
}
module.exports = {
InMemoryMessageStore,
RedisMessageStore,
};

View File

@@ -0,0 +1,17 @@
{
"name": "server",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node cluster.js"
},
"author": "Damien Arrachequesne <damien.arrachequesne@gmail.com>",
"license": "MIT",
"dependencies": {
"@socket.io/sticky": "^1.0.0",
"ioredis": "^4.22.0",
"socket.io": "^3.1.1",
"socket.io-redis": "^6.0.1"
}
}

View File

@@ -0,0 +1,89 @@
/* abstract */ class SessionStore {
findSession(id) {}
saveSession(id, session) {}
findAllSessions() {}
}
class InMemorySessionStore extends SessionStore {
constructor() {
super();
this.sessions = new Map();
}
findSession(id) {
return this.sessions.get(id);
}
saveSession(id, session) {
this.sessions.set(id, session);
}
findAllSessions() {
return [...this.sessions.values()];
}
}
const SESSION_TTL = 24 * 60 * 60;
const mapSession = ([userID, username, connected]) =>
userID ? { userID, username, connected: connected === "true" } : undefined;
class RedisSessionStore extends SessionStore {
constructor(redisClient) {
super();
this.redisClient = redisClient;
}
findSession(id) {
return this.redisClient
.hmget(`session:${id}`, "userID", "username", "connected")
.then(mapSession);
}
saveSession(id, { userID, username, connected }) {
this.redisClient
.multi()
.hset(
`session:${id}`,
"userID",
userID,
"username",
username,
"connected",
connected
)
.expire(`session:${id}`, SESSION_TTL)
.exec();
}
async findAllSessions() {
const keys = new Set();
let nextIndex = 0;
do {
const [nextIndexAsStr, results] = await this.redisClient.scan(
nextIndex,
"MATCH",
"session:*",
"COUNT",
"100"
);
nextIndex = parseInt(nextIndexAsStr, 10);
results.forEach((s) => keys.add(s));
} while (nextIndex !== 0);
const commands = [];
keys.forEach((key) => {
commands.push(["hmget", key, "userID", "username", "connected"]);
});
return this.redisClient
.multi(commands)
.exec()
.then((results) => {
return results
.map(([err, session]) => (err ? undefined : mapSession(session)))
.filter((v) => !!v);
});
}
}
module.exports = {
InMemorySessionStore,
RedisSessionStore,
};

View File

@@ -0,0 +1,78 @@
<template>
<div id="app">
<select-username
v-if="!usernameAlreadySelected"
@input="onUsernameSelection"
/>
<chat v-else />
</div>
</template>
<script>
import SelectUsername from "./components/SelectUsername";
import Chat from "./components/Chat";
import socket from "./socket";
export default {
name: "App",
components: {
Chat,
SelectUsername,
},
data() {
return {
usernameAlreadySelected: false,
};
},
methods: {
onUsernameSelection(username) {
this.usernameAlreadySelected = true;
socket.auth = { username };
socket.connect();
},
},
created() {
const sessionID = localStorage.getItem("sessionID");
if (sessionID) {
this.usernameAlreadySelected = true;
socket.auth = { sessionID };
socket.connect();
}
socket.on("session", ({ sessionID, userID }) => {
// attach the session ID to the next reconnection attempts
socket.auth = { sessionID };
// store it in the localStorage
localStorage.setItem("sessionID", sessionID);
// save the ID of the user
socket.userID = userID;
});
socket.on("connect_error", (err) => {
if (err.message === "invalid username") {
this.usernameAlreadySelected = false;
}
});
},
destroyed() {
socket.off("connect_error");
},
};
</script>
<style>
body {
margin: 0;
}
@font-face {
font-family: Lato;
src: url("/fonts/Lato-Regular.ttf");
}
#app {
font-family: Lato, Arial, sans-serif;
font-size: 14px;
}
</style>

View File

@@ -0,0 +1,165 @@
<template>
<div>
<div class="left-panel">
<user
v-for="user in users"
:key="user.userID"
:user="user"
:selected="selectedUser === user"
@select="onSelectUser(user)"
/>
</div>
<message-panel
v-if="selectedUser"
:user="selectedUser"
@input="onMessage"
class="right-panel"
/>
</div>
</template>
<script>
import socket from "../socket";
import User from "./User";
import MessagePanel from "./MessagePanel";
export default {
name: "Chat",
components: { User, MessagePanel },
data() {
return {
selectedUser: null,
users: [],
};
},
methods: {
onMessage(content) {
if (this.selectedUser) {
socket.emit("private message", {
content,
to: this.selectedUser.userID,
});
this.selectedUser.messages.push({
content,
fromSelf: true,
});
}
},
onSelectUser(user) {
this.selectedUser = user;
user.hasNewMessages = false;
},
},
created() {
socket.on("connect", () => {
this.users.forEach((user) => {
if (user.self) {
user.connected = true;
}
});
});
socket.on("disconnect", () => {
this.users.forEach((user) => {
if (user.self) {
user.connected = false;
}
});
});
const initReactiveProperties = (user) => {
user.hasNewMessages = false;
};
socket.on("users", (users) => {
users.forEach((user) => {
user.messages.forEach((message) => {
message.fromSelf = message.from === socket.userID;
});
for (let i = 0; i < this.users.length; i++) {
const existingUser = this.users[i];
if (existingUser.userID === user.userID) {
existingUser.connected = user.connected;
existingUser.messages = user.messages;
return;
}
}
user.self = user.userID === socket.userID;
initReactiveProperties(user);
this.users.push(user);
});
// put the current user first, and sort by username
this.users.sort((a, b) => {
if (a.self) return -1;
if (b.self) return 1;
if (a.username < b.username) return -1;
return a.username > b.username ? 1 : 0;
});
});
socket.on("user connected", (user) => {
for (let i = 0; i < this.users.length; i++) {
const existingUser = this.users[i];
if (existingUser.userID === user.userID) {
existingUser.connected = true;
return;
}
}
initReactiveProperties(user);
this.users.push(user);
});
socket.on("user disconnected", (id) => {
for (let i = 0; i < this.users.length; i++) {
const user = this.users[i];
if (user.userID === id) {
user.connected = false;
break;
}
}
});
socket.on("private message", ({ content, from, to }) => {
for (let i = 0; i < this.users.length; i++) {
const user = this.users[i];
const fromSelf = socket.userID === from;
if (user.userID === (fromSelf ? to : from)) {
user.messages.push({
content,
fromSelf,
});
if (user !== this.selectedUser) {
user.hasNewMessages = true;
}
break;
}
}
});
},
destroyed() {
socket.off("connect");
socket.off("disconnect");
socket.off("users");
socket.off("user connected");
socket.off("user disconnected");
socket.off("private message");
},
};
</script>
<style scoped>
.left-panel {
position: fixed;
left: 0;
top: 0;
bottom: 0;
width: 260px;
overflow-x: hidden;
background-color: #3f0e40;
color: white;
}
.right-panel {
margin-left: 260px;
}
</style>

View File

@@ -0,0 +1,101 @@
<template>
<div>
<div class="header">
<status-icon :connected="user.connected" />{{ user.username }}
</div>
<ul class="messages">
<li
v-for="(message, index) in user.messages"
:key="index"
class="message"
>
<div v-if="displaySender(message, index)" class="sender">
{{ message.fromSelf ? "(yourself)" : user.username }}
</div>
{{ message.content }}
</li>
</ul>
<form @submit.prevent="onSubmit" class="form">
<textarea v-model="input" placeholder="Your message..." class="input" />
<button :disabled="!isValid" class="send-button">Send</button>
</form>
</div>
</template>
<script>
import StatusIcon from "./StatusIcon";
export default {
name: "MessagePanel",
components: {
StatusIcon,
},
props: {
user: Object,
},
data() {
return {
input: "",
};
},
methods: {
onSubmit() {
this.$emit("input", this.input);
this.input = "";
},
displaySender(message, index) {
return (
index === 0 ||
this.user.messages[index - 1].fromSelf !==
this.user.messages[index].fromSelf
);
},
},
computed: {
isValid() {
return this.input.length > 0;
},
},
};
</script>
<style scoped>
.header {
line-height: 40px;
padding: 10px 20px;
border-bottom: 1px solid #dddddd;
}
.messages {
margin: 0;
padding: 20px;
}
.message {
list-style: none;
}
.sender {
font-weight: bold;
margin-top: 5px;
}
.form {
padding: 10px;
}
.input {
width: 80%;
resize: none;
padding: 10px;
line-height: 1.5;
border-radius: 5px;
border: 1px solid #000;
}
.send-button {
vertical-align: top;
}
</style>

View File

@@ -0,0 +1,36 @@
<template>
<div class="select-username">
<form @submit.prevent="onSubmit">
<input v-model="username" placeholder="Your username..." />
<button :disabled="!isValid">Send</button>
</form>
</div>
</template>
<script>
export default {
name: "SelectUsername",
data() {
return {
username: "",
};
},
computed: {
isValid() {
return this.username.length > 2;
},
},
methods: {
onSubmit() {
this.$emit("input", this.username);
},
},
};
</script>
<style scoped>
.select-username {
width: 300px;
margin: 200px auto 0;
}
</style>

View File

@@ -0,0 +1,27 @@
<template>
<i class="icon" :class="{ connected: connected }"></i>
</template>
<script>
export default {
name: "StatusIcon",
props: {
connected: Boolean,
},
};
</script>
<style scoped>
.icon {
height: 8px;
width: 8px;
border-radius: 50%;
display: inline-block;
background-color: #e38968;
margin-right: 6px;
}
.icon.connected {
background-color: #86bb71;
}
</style>

View File

@@ -0,0 +1,63 @@
<template>
<div class="user" @click="onClick" :class="{ selected: selected }">
<div class="description">
<div class="name">
{{ user.username }} {{ user.self ? " (yourself)" : "" }}
</div>
<div class="status">
<status-icon :connected="user.connected" />{{ status }}
</div>
</div>
<div v-if="user.hasNewMessages" class="new-messages">!</div>
</div>
</template>
<script>
import StatusIcon from "./StatusIcon";
export default {
name: "User",
components: { StatusIcon },
props: {
user: Object,
selected: Boolean,
},
methods: {
onClick() {
this.$emit("select");
},
},
computed: {
status() {
return this.user.connected ? "online" : "offline";
},
},
};
</script>
<style scoped>
.selected {
background-color: #1164a3;
}
.user {
padding: 10px;
}
.description {
display: inline-block;
}
.status {
color: #92959e;
}
.new-messages {
color: white;
background-color: red;
width: 20px;
border-radius: 5px;
text-align: center;
float: right;
margin-top: 10px;
}
</style>

View File

@@ -0,0 +1,8 @@
import Vue from "vue";
import App from "./App.vue";
Vue.config.productionTip = false;
new Vue({
render: (h) => h(App),
}).$mount("#app");

View File

@@ -0,0 +1,10 @@
import { io } from "socket.io-client";
const URL = "http://localhost:3000";
const socket = io(URL, { autoConnect: false });
socket.onAny((event, ...args) => {
console.log(event, args);
});
export default socket;

View File

@@ -537,7 +537,11 @@ export class Socket extends EventEmitter {
if (err) {
return this._onerror(err);
}
super.emit.apply(this, event);
if (this.connected) {
super.emit.apply(this, event);
} else {
debug("ignore packet received after disconnection");
}
});
});
}

14
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "socket.io",
"version": "3.1.1",
"version": "3.1.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -874,9 +874,9 @@
}
},
"engine.io-client": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-4.1.1.tgz",
"integrity": "sha512-iYasV/EttP/2pLrdowe9G3zwlNIFhwny8VSIh+vPlMnYZqSzLsTzSLa9hFy015OrH1s4fzoYxeHjVkO8hSFKwg==",
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-4.1.2.tgz",
"integrity": "sha512-1mwvwKYMa0AaCy+sPgvJ/SnKyO5MJZ1HEeXfA3Rm/KHkHGiYD5bQVq8QzvIrkI01FuVtOdZC5lWdRw1BGXB2NQ==",
"dev": true,
"requires": {
"base64-arraybuffer": "0.1.4",
@@ -2258,9 +2258,9 @@
"integrity": "sha512-+vDov/aTsLjViYTwS9fPy5pEtTkrbEKsw2M+oVSoFGw6OD1IpvlV1VPhUzNbofCQ8oyMbdYJqDtGdmHQK6TdPg=="
},
"socket.io-client": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-3.1.1.tgz",
"integrity": "sha512-BLgIuCjI7Sf3mDHunKddX9zKR/pbkP7IACM3sJS3jha+zJ6/pGKRV6Fz5XSBHCfUs9YzT8kYIqNwOOuFNLtnYA==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-3.1.2.tgz",
"integrity": "sha512-fXhF8plHrd7U14A7K0JPOmZzpmGkLpIS6623DzrBZqYzI/yvlP4fA3LnxwthEVgiHmn2uJ4KjdnQD8A03PuBWQ==",
"dev": true,
"requires": {
"@types/component-emitter": "^1.2.10",

View File

@@ -1,6 +1,6 @@
{
"name": "socket.io",
"version": "3.1.1",
"version": "3.1.2",
"description": "node.js realtime framework server",
"keywords": [
"realtime",
@@ -45,7 +45,7 @@
"dependencies": {
"@types/cookie": "^0.4.0",
"@types/cors": "^2.8.8",
"@types/node": "^14.14.10",
"@types/node": ">=10.0.0",
"accepts": "~1.3.4",
"base64id": "~2.0.0",
"debug": "~4.3.1",
@@ -63,7 +63,7 @@
"nyc": "^15.1.0",
"prettier": "^2.2.0",
"rimraf": "^3.0.2",
"socket.io-client": "3.1.1",
"socket.io-client": "3.1.2",
"socket.io-client-v2": "npm:socket.io-client@^2.4.0",
"superagent": "^6.1.0",
"supertest": "^6.0.1",

View File

@@ -1787,6 +1787,33 @@ describe("socket.io", () => {
});
});
it("should ignore a packet received after disconnection", (done) => {
const srv = createServer();
const sio = new Server(srv);
srv.listen(() => {
const clientSocket = client(srv);
const success = () => {
clientSocket.close();
sio.close();
done();
};
sio.on("connection", (socket) => {
socket.on("test", () => {
done(new Error("should not happen"));
});
socket.on("disconnect", success);
});
clientSocket.on("connect", () => {
clientSocket.emit("test", Buffer.alloc(10));
clientSocket.disconnect();
});
});
});
describe("onAny", () => {
it("should call listener", (done) => {
const srv = createServer();