mirror of
https://github.com/socketio/socket.io.git
synced 2026-01-12 00:17:56 -05:00
Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8be95b3bd3 | ||
|
|
ba497ee3eb | ||
|
|
28038715cb | ||
|
|
134226e96c | ||
|
|
9890b036cf | ||
|
|
713a6b451b | ||
|
|
18f3fdab12 | ||
|
|
5ab8289c0a | ||
|
|
30430f0985 | ||
|
|
9b43c9167c | ||
|
|
8ecfcba5c1 | ||
|
|
572133a58d | ||
|
|
6e1bb62982 | ||
|
|
06e6838b18 | ||
|
|
1f03a44d1f | ||
|
|
be3d7f0f1f | ||
|
|
d12aab2d69 | ||
|
|
9f758689f6 | ||
|
|
0b35dc77c0 | ||
|
|
531104d332 | ||
|
|
8b204570a9 | ||
|
|
0b7d70ca42 | ||
|
|
2f96438952 | ||
|
|
02c87a8561 | ||
|
|
37b6d8fff0 | ||
|
|
af54565b2d | ||
|
|
aa5312a4b6 | ||
|
|
c82a4bdf1f | ||
|
|
770ee5949f | ||
|
|
3bf5d92735 | ||
|
|
fc82e44f73 | ||
|
|
c840bad43a | ||
|
|
f2b8de7191 | ||
|
|
51784d0305 | ||
|
|
c196689545 | ||
|
|
7a70f63499 | ||
|
|
e5897dd7dc | ||
|
|
2071a66c5a |
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -2,7 +2,7 @@
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: 'bug'
|
||||
labels: 'to triage'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@@ -6,6 +6,9 @@ on:
|
||||
schedule:
|
||||
- cron: '0 0 * * 0'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test-node:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
76
CHANGELOG.md
76
CHANGELOG.md
@@ -1,3 +1,79 @@
|
||||
## [4.5.2](https://github.com/socketio/socket.io/compare/4.5.1...4.5.2) (2022-09-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* prevent the socket from joining a room after disconnection ([18f3fda](https://github.com/socketio/socket.io/commit/18f3fdab12947a9fee3e9c37cfc1da97027d1473))
|
||||
* **uws:** prevent the server from crashing after upgrade ([ba497ee](https://github.com/socketio/socket.io/commit/ba497ee3eb52c4abf1464380d015d8c788714364))
|
||||
|
||||
|
||||
|
||||
## [4.5.1](https://github.com/socketio/socket.io/compare/4.5.0...4.5.1) (2022-05-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* forward the local flag to the adapter when using fetchSockets() ([30430f0](https://github.com/socketio/socket.io/commit/30430f0985f8e7c49394543d4c84913b6a15df60))
|
||||
* **typings:** add HTTPS server to accepted types ([#4351](https://github.com/socketio/socket.io/issues/4351)) ([9b43c91](https://github.com/socketio/socket.io/commit/9b43c9167cff817c60fa29dbda2ef7cd938aff51))
|
||||
|
||||
|
||||
|
||||
# [4.5.0](https://github.com/socketio/socket.io/compare/4.4.1...4.5.0) (2022-04-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **typings:** ensure compatibility with TypeScript 3.x ([#4259](https://github.com/socketio/socket.io/issues/4259)) ([02c87a8](https://github.com/socketio/socket.io/commit/02c87a85614e217b8e7b93753f315790ae9d99f6))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add support for catch-all listeners for outgoing packets ([531104d](https://github.com/socketio/socket.io/commit/531104d332690138b7aab84d5583d6204132c8b4))
|
||||
|
||||
This is similar to `onAny()`, but for outgoing packets.
|
||||
|
||||
Syntax:
|
||||
|
||||
```js
|
||||
socket.onAnyOutgoing((event, ...args) => {
|
||||
console.log(event);
|
||||
});
|
||||
```
|
||||
|
||||
* broadcast and expect multiple acks ([8b20457](https://github.com/socketio/socket.io/commit/8b204570a94979bbec307f23ca078f30f5cf07b0))
|
||||
|
||||
Syntax:
|
||||
|
||||
```js
|
||||
io.timeout(1000).emit("some-event", (err, responses) => {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
* add the "maxPayload" field in the handshake details ([088dcb4](https://github.com/socketio/engine.io/commit/088dcb4dff60df39785df13d0a33d3ceaa1dff38))
|
||||
|
||||
So that clients in HTTP long-polling can decide how many packets they have to send to stay under the maxHttpBufferSize
|
||||
value.
|
||||
|
||||
This is a backward compatible change which should not mandate a new major revision of the protocol (we stay in v4), as
|
||||
we only add a field in the JSON-encoded handshake data:
|
||||
|
||||
```
|
||||
0{"sid":"lv_VI97HAXpY6yYWAAAC","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":5000,"maxPayload":1000000}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## [4.4.1](https://github.com/socketio/socket.io/compare/4.4.0...4.4.1) (2022-01-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **types:** make `RemoteSocket.data` type safe ([#4234](https://github.com/socketio/socket.io/issues/4234)) ([770ee59](https://github.com/socketio/socket.io/commit/770ee5949fb47c2556876c622f06c862573657d6))
|
||||
* **types:** pass `SocketData` type to custom namespaces ([#4233](https://github.com/socketio/socket.io/issues/4233)) ([f2b8de7](https://github.com/socketio/socket.io/commit/f2b8de71919e1b4d3e57f15a459972c1d1064787))
|
||||
|
||||
|
||||
|
||||
# [4.4.0](https://github.com/socketio/socket.io/compare/4.3.2...4.4.0) (2021-11-18)
|
||||
|
||||
|
||||
|
||||
10
Readme.md
10
Readme.md
@@ -2,8 +2,6 @@
|
||||
[](https://replit.com/@socketio/socketio-minimal-example)
|
||||
[](#backers) [](#sponsors)
|
||||
[](https://github.com/socketio/socket.io/actions)
|
||||
[](https://david-dm.org/socketio/socket.io)
|
||||
[](https://david-dm.org/socketio/socket.io#info=devDependencies)
|
||||
[](https://www.npmjs.com/package/socket.io)
|
||||

|
||||
[](https://slackin-socketio.now.sh)
|
||||
@@ -115,6 +113,14 @@ io.on('connection', client => { ... });
|
||||
io.listen(3000);
|
||||
```
|
||||
|
||||
### Module syntax
|
||||
|
||||
```js
|
||||
import { Server } from "socket.io";
|
||||
const io = new Server(server);
|
||||
io.listen(3000);
|
||||
```
|
||||
|
||||
### In conjunction with Express
|
||||
|
||||
Starting with **3.0**, express applications have become request handler
|
||||
|
||||
6
client-dist/socket.io.esm.min.js
vendored
6
client-dist/socket.io.esm.min.js
vendored
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 it is too large
Load Diff
File diff suppressed because one or more lines are too long
6
client-dist/socket.io.min.js
vendored
6
client-dist/socket.io.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
6
client-dist/socket.io.msgpack.min.js
vendored
6
client-dist/socket.io.msgpack.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -2,6 +2,13 @@
|
||||
|
||||
Please read the related [guide](https://socket.io/get-started/basic-crud-application/).
|
||||
|
||||
This repository contains several implementations of the server:
|
||||
|
||||
| Directory | Language | Database | Cluster? |
|
||||
|----------------------------|------------|------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------|
|
||||
| `server/` | TypeScript | in-memory | No |
|
||||
| `server-postgres-cluster/` | JavaScript | Postgres, with the [Postgres adapter](https://socket.io/docs/v4/postgres-adapter/) | Yes, with the [`@socket.io/sticky`](https://github.com/socketio/socket.io-sticky) module) |
|
||||
|
||||
## Running the frontend
|
||||
|
||||
```
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"karma-chrome-launcher": "~3.1.0",
|
||||
"karma-coverage": "~2.0.3",
|
||||
"karma-jasmine": "~4.0.0",
|
||||
"karma-jasmine-html-reporter": "^1.5.0",
|
||||
"karma-jasmine-html-reporter": "~1.5.0",
|
||||
"protractor": "~7.0.0",
|
||||
"ts-node": "~8.3.0",
|
||||
"tslint": "~6.1.0",
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
A basic TODO project.
|
||||
|
||||
| Characteristic | |
|
||||
|----------------|-------------------------------------------------------------------------------------------|
|
||||
| Language | plain JavaScript |
|
||||
| Database | Postgres, with the [Postgres adapter](https://socket.io/docs/v4/postgres-adapter/) |
|
||||
| Cluster? | Yes, with the [`@socket.io/sticky`](https://github.com/socketio/socket.io-sticky) module) |
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
$ docker-compose up -d
|
||||
$ npm install
|
||||
$ npm start
|
||||
```
|
||||
@@ -0,0 +1,9 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:12
|
||||
ports:
|
||||
- "5432:5432"
|
||||
environment:
|
||||
POSTGRES_PASSWORD: "changeit"
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Server } from "socket.io";
|
||||
import createTodoHandlers from "./todo-management/todo.handlers.js";
|
||||
import { setupWorker } from "@socket.io/sticky";
|
||||
import { createAdapter } from "@socket.io/postgres-adapter";
|
||||
|
||||
export function createApplication(httpServer, components, serverOptions = {}) {
|
||||
const io = new Server(httpServer, serverOptions);
|
||||
|
||||
const { createTodo, readTodo, updateTodo, deleteTodo, listTodo } =
|
||||
createTodoHandlers(components);
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
socket.on("todo:create", createTodo);
|
||||
socket.on("todo:read", readTodo);
|
||||
socket.on("todo:update", updateTodo);
|
||||
socket.on("todo:delete", deleteTodo);
|
||||
socket.on("todo:list", listTodo);
|
||||
});
|
||||
|
||||
// enable sticky session in the cluster (to remove in standalone mode)
|
||||
setupWorker(io);
|
||||
|
||||
io.adapter(createAdapter(components.connectionPool));
|
||||
|
||||
return io;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import cluster from "cluster";
|
||||
import { createServer } from "http";
|
||||
import { setupMaster } from "@socket.io/sticky";
|
||||
import { cpus } from "os";
|
||||
|
||||
if (cluster.isMaster) {
|
||||
console.log(`Master ${process.pid} is running`);
|
||||
const httpServer = createServer();
|
||||
|
||||
setupMaster(httpServer, {
|
||||
loadBalancingMethod: "least-connection",
|
||||
});
|
||||
|
||||
httpServer.listen(3000);
|
||||
|
||||
for (let i = 0; i < cpus().length; i++) {
|
||||
cluster.fork();
|
||||
}
|
||||
|
||||
cluster.on("exit", (worker) => {
|
||||
console.log(`Worker ${worker.process.pid} died`);
|
||||
cluster.fork();
|
||||
});
|
||||
} else {
|
||||
console.log(`Worker ${process.pid} started`);
|
||||
|
||||
import("./index.js");
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { createServer } from "http";
|
||||
import { createApplication } from "./app.js";
|
||||
import { Sequelize } from "sequelize";
|
||||
import pg from "pg";
|
||||
import { PostgresTodoRepository } from "./todo-management/todo.repository.js";
|
||||
|
||||
const httpServer = createServer();
|
||||
|
||||
const sequelize = new Sequelize("postgres", "postgres", "changeit", {
|
||||
dialect: "postgres",
|
||||
});
|
||||
|
||||
const connectionPool = new pg.Pool({
|
||||
user: "postgres",
|
||||
host: "localhost",
|
||||
database: "postgres",
|
||||
password: "changeit",
|
||||
port: 5432,
|
||||
});
|
||||
|
||||
createApplication(
|
||||
httpServer,
|
||||
{
|
||||
connectionPool,
|
||||
todoRepository: new PostgresTodoRepository(sequelize),
|
||||
},
|
||||
{
|
||||
cors: {
|
||||
origin: ["http://localhost:4200"],
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const main = async () => {
|
||||
// create the tables if they do not exist already
|
||||
await sequelize.sync();
|
||||
|
||||
// create the table needed by the postgres adapter
|
||||
await connectionPool.query(`
|
||||
CREATE TABLE IF NOT EXISTS socket_io_attachments (
|
||||
id bigserial UNIQUE,
|
||||
created_at timestamptz DEFAULT NOW(),
|
||||
payload bytea
|
||||
);
|
||||
`);
|
||||
|
||||
// uncomment when running in standalone mode
|
||||
// httpServer.listen(3000);
|
||||
};
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,140 @@
|
||||
import { Errors, mapErrorDetails, sanitizeErrorMessage } from "../util.js";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import Joi from "joi";
|
||||
|
||||
const idSchema = Joi.string().guid({
|
||||
version: "uuidv4",
|
||||
});
|
||||
|
||||
const todoSchema = Joi.object({
|
||||
id: idSchema.alter({
|
||||
create: (schema) => schema.forbidden(),
|
||||
update: (schema) => schema.required(),
|
||||
}),
|
||||
title: Joi.string().max(256).required(),
|
||||
completed: Joi.boolean().required(),
|
||||
});
|
||||
|
||||
export default function (components) {
|
||||
const { todoRepository } = components;
|
||||
return {
|
||||
createTodo: async function (payload, callback) {
|
||||
const socket = this;
|
||||
|
||||
// validate the payload
|
||||
const { error, value } = todoSchema.tailor("create").validate(payload, {
|
||||
abortEarly: false,
|
||||
stripUnknown: true,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return callback({
|
||||
error: Errors.INVALID_PAYLOAD,
|
||||
errorDetails: mapErrorDetails(error.details),
|
||||
});
|
||||
}
|
||||
|
||||
value.id = uuid();
|
||||
|
||||
// persist the entity
|
||||
try {
|
||||
await todoRepository.save(value);
|
||||
} catch (e) {
|
||||
return callback({
|
||||
error: sanitizeErrorMessage(e),
|
||||
});
|
||||
}
|
||||
|
||||
// acknowledge the creation
|
||||
callback({
|
||||
data: value.id,
|
||||
});
|
||||
|
||||
// notify the other users
|
||||
socket.broadcast.emit("todo:created", value);
|
||||
},
|
||||
|
||||
readTodo: async function (id, callback) {
|
||||
const { error } = idSchema.validate(id);
|
||||
|
||||
if (error) {
|
||||
return callback({
|
||||
error: Errors.ENTITY_NOT_FOUND,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const todo = await todoRepository.findById(id);
|
||||
callback({
|
||||
data: todo,
|
||||
});
|
||||
} catch (e) {
|
||||
callback({
|
||||
error: sanitizeErrorMessage(e),
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
updateTodo: async function (payload, callback) {
|
||||
const socket = this;
|
||||
|
||||
const { error, value } = todoSchema.tailor("update").validate(payload, {
|
||||
abortEarly: false,
|
||||
stripUnknown: true,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
return callback({
|
||||
error: Errors.INVALID_PAYLOAD,
|
||||
errorDetails: mapErrorDetails(error.details),
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await todoRepository.save(value);
|
||||
} catch (e) {
|
||||
return callback({
|
||||
error: sanitizeErrorMessage(e),
|
||||
});
|
||||
}
|
||||
|
||||
callback();
|
||||
socket.broadcast.emit("todo:updated", value);
|
||||
},
|
||||
|
||||
deleteTodo: async function (id, callback) {
|
||||
const socket = this;
|
||||
|
||||
const { error } = idSchema.validate(id);
|
||||
|
||||
if (error) {
|
||||
return callback({
|
||||
error: Errors.ENTITY_NOT_FOUND,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await todoRepository.deleteById(id);
|
||||
} catch (e) {
|
||||
return callback({
|
||||
error: sanitizeErrorMessage(e),
|
||||
});
|
||||
}
|
||||
|
||||
callback();
|
||||
socket.broadcast.emit("todo:deleted", id);
|
||||
},
|
||||
|
||||
listTodo: async function (callback) {
|
||||
try {
|
||||
callback({
|
||||
data: await todoRepository.findAll(),
|
||||
});
|
||||
} catch (e) {
|
||||
callback({
|
||||
error: sanitizeErrorMessage(e),
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { Errors } from "../util.js";
|
||||
import { Model, DataTypes } from "sequelize";
|
||||
|
||||
class CrudRepository {
|
||||
findAll() {}
|
||||
findById(id) {}
|
||||
save(entity) {}
|
||||
deleteById(id) {}
|
||||
}
|
||||
|
||||
export class TodoRepository extends CrudRepository {}
|
||||
|
||||
class Todo extends Model {}
|
||||
|
||||
export class PostgresTodoRepository extends TodoRepository {
|
||||
constructor(sequelize) {
|
||||
super();
|
||||
this.sequelize = sequelize;
|
||||
|
||||
Todo.init(
|
||||
{
|
||||
id: {
|
||||
type: DataTypes.STRING,
|
||||
primaryKey: true,
|
||||
allowNull: false,
|
||||
},
|
||||
title: {
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
completed: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
},
|
||||
},
|
||||
{
|
||||
sequelize,
|
||||
tableName: "todos",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
findAll() {
|
||||
return this.sequelize.transaction((transaction) => {
|
||||
return Todo.findAll({ transaction });
|
||||
});
|
||||
}
|
||||
|
||||
async findById(id) {
|
||||
return this.sequelize.transaction(async (transaction) => {
|
||||
const todo = await Todo.findByPk(id, { transaction });
|
||||
|
||||
if (!todo) {
|
||||
throw Errors.ENTITY_NOT_FOUND;
|
||||
}
|
||||
|
||||
return todo;
|
||||
});
|
||||
}
|
||||
|
||||
save(entity) {
|
||||
return this.sequelize.transaction((transaction) => {
|
||||
return Todo.upsert(entity, { transaction });
|
||||
});
|
||||
}
|
||||
|
||||
async deleteById(id) {
|
||||
return this.sequelize.transaction(async (transaction) => {
|
||||
const count = await Todo.destroy({ where: { id }, transaction });
|
||||
|
||||
if (count === 0) {
|
||||
throw Errors.ENTITY_NOT_FOUND;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
export const Errors = {
|
||||
ENTITY_NOT_FOUND: "entity not found",
|
||||
INVALID_PAYLOAD: "invalid payload",
|
||||
};
|
||||
|
||||
const errorValues = Object.values(Errors);
|
||||
|
||||
export function sanitizeErrorMessage(message) {
|
||||
if (typeof message === "string" && errorValues.includes(message)) {
|
||||
return message;
|
||||
} else {
|
||||
return "an unknown error has occurred";
|
||||
}
|
||||
}
|
||||
|
||||
export function mapErrorDetails(details) {
|
||||
return details.map((item) => ({
|
||||
message: item.message,
|
||||
path: item.path,
|
||||
type: item.type,
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "basic-crud-server",
|
||||
"version": "0.0.1",
|
||||
"description": "Server for the Basic CRUD Socket.IO example (with Postgres and multiple Socket.IO servers)",
|
||||
"main": "lib/cluster.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node lib/cluster.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/socketio/socket.io.git"
|
||||
},
|
||||
"author": "Damien Arrachequesne <damien.arrachequesne@gmail.com>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/socketio/socket.io/issues"
|
||||
},
|
||||
"homepage": "https://github.com/socketio/socket.io#readme",
|
||||
"dependencies": {
|
||||
"@socket.io/postgres-adapter": "^0.2.0",
|
||||
"@socket.io/sticky": "^1.0.1",
|
||||
"joi": "^17.4.0",
|
||||
"pg": "^8.7.3",
|
||||
"pg-hstore": "^2.3.4",
|
||||
"sequelize": "^6.18.0",
|
||||
"socket.io": "^4.0.1",
|
||||
"uuid": "^8.3.2"
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,8 @@ export enum Errors {
|
||||
|
||||
const errorValues: string[] = Object.values(Errors);
|
||||
|
||||
export function sanitizeErrorMessage(message: string) {
|
||||
if (errorValues.includes(message)) {
|
||||
export function sanitizeErrorMessage(message: any) {
|
||||
if (typeof message === "string" && errorValues.includes(message)) {
|
||||
return message;
|
||||
} else {
|
||||
return "an unknown error has occurred";
|
||||
|
||||
@@ -6,7 +6,7 @@ A simple chat demo for Socket.IO
|
||||
## How to use
|
||||
|
||||
```
|
||||
$ npm ci
|
||||
$ npm i
|
||||
$ npm start
|
||||
```
|
||||
|
||||
|
||||
@@ -264,14 +264,14 @@ $(function() {
|
||||
log('you have been disconnected');
|
||||
});
|
||||
|
||||
socket.on('reconnect', () => {
|
||||
socket.io.on('reconnect', () => {
|
||||
log('you have been reconnected');
|
||||
if (username) {
|
||||
socket.emit('add user', username);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('reconnect_error', () => {
|
||||
socket.io.on('reconnect_error', () => {
|
||||
log('attempt to reconnect has failed');
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
|
||||
nginx:
|
||||
build: ./nginx
|
||||
image: nginx:alpine
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
links:
|
||||
- server-john
|
||||
- server-paul
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
|
||||
FROM nginx:alpine
|
||||
COPY nginx.conf /etc/nginx/nginx.conf
|
||||
@@ -1,15 +1,18 @@
|
||||
// Setup basic express server
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
var server = require('http').createServer(app);
|
||||
var io = require('socket.io')(server);
|
||||
var redis = require('socket.io-redis');
|
||||
var port = process.env.PORT || 3000;
|
||||
var serverName = process.env.NAME || 'Unknown';
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
const server = require('http').createServer(app);
|
||||
const io = require('socket.io')(server);
|
||||
const { createAdapter } = require('@socket.io/redis-adapter');
|
||||
const { createClient } = require('redis');
|
||||
const port = process.env.PORT || 3000;
|
||||
const serverName = process.env.NAME || 'Unknown';
|
||||
|
||||
io.adapter(redis({ host: 'redis', port: 6379 }));
|
||||
const pubClient = createClient({ host: 'redis', port: 6379 });
|
||||
const subClient = pubClient.duplicate();
|
||||
|
||||
server.listen(port, function () {
|
||||
io.adapter(createAdapter(pubClient, subClient));
|
||||
|
||||
server.listen(port, () => {
|
||||
console.log('Server listening at port %d', port);
|
||||
console.log('Hello, I\'m %s, how can I help?', serverName);
|
||||
});
|
||||
@@ -19,15 +22,15 @@ app.use(express.static(__dirname + '/public'));
|
||||
|
||||
// Chatroom
|
||||
|
||||
var numUsers = 0;
|
||||
let numUsers = 0;
|
||||
|
||||
io.on('connection', function (socket) {
|
||||
io.on('connection', socket => {
|
||||
socket.emit('my-name-is', serverName);
|
||||
|
||||
var addedUser = false;
|
||||
let addedUser = false;
|
||||
|
||||
// when the client emits 'new message', this listens and executes
|
||||
socket.on('new message', function (data) {
|
||||
socket.on('new message', data => {
|
||||
// we tell the client to execute 'new message'
|
||||
socket.broadcast.emit('new message', {
|
||||
username: socket.username,
|
||||
@@ -36,7 +39,7 @@ io.on('connection', function (socket) {
|
||||
});
|
||||
|
||||
// when the client emits 'add user', this listens and executes
|
||||
socket.on('add user', function (username) {
|
||||
socket.on('add user', username => {
|
||||
if (addedUser) return;
|
||||
|
||||
// we store the username in the socket session for this client
|
||||
@@ -54,21 +57,21 @@ io.on('connection', function (socket) {
|
||||
});
|
||||
|
||||
// when the client emits 'typing', we broadcast it to others
|
||||
socket.on('typing', function () {
|
||||
socket.on('typing', () => {
|
||||
socket.broadcast.emit('typing', {
|
||||
username: socket.username
|
||||
});
|
||||
});
|
||||
|
||||
// when the client emits 'stop typing', we broadcast it to others
|
||||
socket.on('stop typing', function () {
|
||||
socket.on('stop typing', () => {
|
||||
socket.broadcast.emit('stop typing', {
|
||||
username: socket.username
|
||||
});
|
||||
});
|
||||
|
||||
// when the user disconnects.. perform this
|
||||
socket.on('disconnect', function () {
|
||||
socket.on('disconnect', () => {
|
||||
if (addedUser) {
|
||||
--numUsers;
|
||||
|
||||
|
||||
@@ -7,9 +7,10 @@
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@socket.io/redis-adapter": "^7.0.1",
|
||||
"express": "4.13.4",
|
||||
"socket.io": "^4.0.0",
|
||||
"socket.io-redis": "^6.0.1"
|
||||
"redis": "^3.1.2",
|
||||
"socket.io": "^4.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import logo from './logo.svg';
|
||||
import './App.css';
|
||||
import io from 'socket.io-client';
|
||||
|
||||
@@ -23,7 +24,7 @@ function App() {
|
||||
socket.off('disconnect');
|
||||
socket.off('message');
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
const sendMessage = () => {
|
||||
socket.emit('hello!');
|
||||
@@ -32,9 +33,21 @@ function App() {
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<img src={logo} className="App-logo" alt="logo" />
|
||||
<p>Connected: { '' + isConnected }</p>
|
||||
<p>Last message: { lastMessage || '-' }</p>
|
||||
<button onClick={ sendMessage }>Say hello!</button>
|
||||
<p>
|
||||
Edit <code>src/App.js</code> and save to reload.
|
||||
</p>
|
||||
<a
|
||||
className="App-link"
|
||||
href="https://reactjs.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn React
|
||||
</a>
|
||||
</header>
|
||||
</div>
|
||||
);
|
||||
|
||||
16
examples/express-session-example/README.md
Normal file
16
examples/express-session-example/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Example with [express-session](https://www.npmjs.com/package/express-session)
|
||||
|
||||
This example shows how to share a session context between [Express](http://expressjs.com/) and [Socket.IO](https://socket.io/docs/v4/):
|
||||
|
||||

|
||||
|
||||
Please read the related guide: https://socket.io/how-to/use-with-express-session
|
||||
|
||||
## How to use
|
||||
|
||||
```
|
||||
$ npm install
|
||||
$ npm start
|
||||
```
|
||||
|
||||
And point your browser to `http://localhost:3000`. Optionally, specify a port by supplying the `PORT` env variable.
|
||||
BIN
examples/express-session-example/assets/demo.gif
Normal file
BIN
examples/express-session-example/assets/demo.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
57
examples/express-session-example/index.html
Normal file
57
examples/express-session-example/index.html
Normal file
@@ -0,0 +1,57 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Example with express-session</title>
|
||||
</head>
|
||||
<body>
|
||||
<button onclick="incrementWithFetch()">Increment with fetch()</button>
|
||||
<button onclick="logout()">Logout</button>
|
||||
<p>Count: <span id="httpCount">0</span></p>
|
||||
|
||||
<button onclick="incrementWithEmit()">
|
||||
Increment with Socket.IO emit()
|
||||
</button>
|
||||
<p>Status: <span id="ioStatus">disconnected</span></p>
|
||||
<p>Count: <span id="ioCount">0</span></p>
|
||||
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script>
|
||||
const httpCount = document.getElementById("httpCount");
|
||||
const ioStatus = document.getElementById("ioStatus");
|
||||
const ioCount = document.getElementById("ioCount");
|
||||
|
||||
const socket = io({
|
||||
// with WebSocket only
|
||||
// transports: ["websocket"],
|
||||
});
|
||||
|
||||
async function incrementWithFetch() {
|
||||
const response = await fetch("/incr", {
|
||||
method: "post",
|
||||
});
|
||||
httpCount.innerText = await response.text();
|
||||
}
|
||||
|
||||
function logout() {
|
||||
fetch("/logout", {
|
||||
method: "post",
|
||||
});
|
||||
}
|
||||
|
||||
async function incrementWithEmit() {
|
||||
socket.emit("incr", (count) => {
|
||||
ioCount.innerText = count;
|
||||
});
|
||||
}
|
||||
|
||||
socket.on("connect", () => {
|
||||
ioStatus.innerText = "connected";
|
||||
});
|
||||
|
||||
socket.on("disconnect", () => {
|
||||
ioStatus.innerText = "disconnected";
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
91
examples/express-session-example/index.js
Normal file
91
examples/express-session-example/index.js
Normal file
@@ -0,0 +1,91 @@
|
||||
import express from "express";
|
||||
import { createServer } from "http";
|
||||
import { Server } from "socket.io";
|
||||
import session from "express-session";
|
||||
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
const app = express();
|
||||
const httpServer = createServer(app);
|
||||
|
||||
const sessionMiddleware = session({
|
||||
secret: "changeit",
|
||||
resave: true,
|
||||
saveUninitialized: true,
|
||||
});
|
||||
|
||||
app.use(sessionMiddleware);
|
||||
|
||||
app.get("/", (req, res) => {
|
||||
res.sendFile("./index.html", { root: process.cwd() });
|
||||
});
|
||||
|
||||
app.post("/incr", (req, res) => {
|
||||
const session = req.session;
|
||||
session.count = (session.count || 0) + 1;
|
||||
res.status(200).end("" + session.count);
|
||||
});
|
||||
|
||||
app.post("/logout", (req, res) => {
|
||||
const sessionId = req.session.id;
|
||||
req.session.destroy(() => {
|
||||
// disconnect all Socket.IO connections linked to this session ID
|
||||
io.to(sessionId).disconnectSockets();
|
||||
res.status(204).end();
|
||||
});
|
||||
});
|
||||
|
||||
const io = new Server(httpServer, {
|
||||
allowRequest: (req, callback) => {
|
||||
// with HTTP long-polling, we have access to the HTTP response here, but this is not
|
||||
// the case with WebSocket, so we provide a dummy response object
|
||||
const fakeRes = {
|
||||
getHeader() {
|
||||
return [];
|
||||
},
|
||||
setHeader(key, values) {
|
||||
req.cookieHolder = values[0];
|
||||
},
|
||||
writeHead() {},
|
||||
};
|
||||
sessionMiddleware(req, fakeRes, () => {
|
||||
if (req.session) {
|
||||
// trigger the setHeader() above
|
||||
fakeRes.writeHead();
|
||||
// manually save the session (normally triggered by res.end())
|
||||
req.session.save();
|
||||
}
|
||||
callback(null, true);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
io.engine.on("initial_headers", (headers, req) => {
|
||||
if (req.cookieHolder) {
|
||||
headers["set-cookie"] = req.cookieHolder;
|
||||
delete req.cookieHolder;
|
||||
}
|
||||
});
|
||||
|
||||
io.on("connect", (socket) => {
|
||||
const req = socket.request;
|
||||
|
||||
socket.join(req.session.id);
|
||||
|
||||
socket.on("incr", (cb) => {
|
||||
req.session.reload((err) => {
|
||||
if (err) {
|
||||
// session has expired
|
||||
return socket.disconnect();
|
||||
}
|
||||
req.session.count = (req.session.count || 0) + 1;
|
||||
req.session.save(() => {
|
||||
cb(req.session.count);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
httpServer.listen(port, () => {
|
||||
console.log(`application is running at: http://localhost:${port}`);
|
||||
});
|
||||
15
examples/express-session-example/package.json
Normal file
15
examples/express-session-example/package.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "express-session-example",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Example with express-session (https://github.com/expressjs/session)",
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "~4.17.3",
|
||||
"express-session": "~1.17.2",
|
||||
"socket.io": "~4.4.1"
|
||||
}
|
||||
}
|
||||
1
examples/rollup-server-bundle/.gitignore
vendored
Normal file
1
examples/rollup-server-bundle/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
bundle.js
|
||||
3
examples/rollup-server-bundle/index.js
Normal file
3
examples/rollup-server-bundle/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import { Server } from "socket.io";
|
||||
|
||||
new Server(0);
|
||||
19
examples/rollup-server-bundle/package.json
Normal file
19
examples/rollup-server-bundle/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "rollup-server-bundle",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"author": "Damien Arrachequesne <damien.arrachequesne@gmail.com>",
|
||||
"license": "ISC",
|
||||
"scripts": {
|
||||
"build": "rollup --config rollup.config.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^21.0.3",
|
||||
"@rollup/plugin-json": "^4.1.0",
|
||||
"@rollup/plugin-node-resolve": "^13.1.3",
|
||||
"rollup": "^2.70.1",
|
||||
"socket.io": "^4.4.1"
|
||||
}
|
||||
}
|
||||
12
examples/rollup-server-bundle/rollup.config.js
Normal file
12
examples/rollup-server-bundle/rollup.config.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import resolve from "@rollup/plugin-node-resolve";
|
||||
import commonjs from "@rollup/plugin-commonjs";
|
||||
import json from "@rollup/plugin-json";
|
||||
|
||||
export default {
|
||||
input: "index.js",
|
||||
output: {
|
||||
file: "bundle.js",
|
||||
format: "esm",
|
||||
},
|
||||
plugins: [resolve(), commonjs(), json({ compact: true })],
|
||||
};
|
||||
@@ -7,14 +7,5 @@ A sample Webpack build for the browser.
|
||||
|
||||
```
|
||||
$ npm i
|
||||
$ npm run build-all
|
||||
$ npm run build
|
||||
```
|
||||
|
||||
There are two WebPack configuration:
|
||||
|
||||
- the minimal configuration, just bundling the application and its dependencies. The `app.js` file in the `dist` folder is the result of that build.
|
||||
|
||||
- a slimmer one, where:
|
||||
- the JSON polyfill needed for IE6/IE7 support has been removed.
|
||||
- the `debug` calls and import have been removed (the [debug](https://github.com/visionmedia/debug) library is included in the build by default).
|
||||
- the source has been uglified (dropping IE8 support), and an associated SourceMap has been generated.
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- <script src="dist/app.js"></script> -->
|
||||
<script src="dist/app.slim.js"></script>
|
||||
<script src="dist/bundle.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
15
examples/webpack-build/index.js
Normal file
15
examples/webpack-build/index.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { io } from "socket.io-client";
|
||||
|
||||
const socket = io("http://localhost:3000");
|
||||
|
||||
socket.on("connect", () => {
|
||||
console.log(`connect ${socket.id}`);
|
||||
});
|
||||
|
||||
socket.on("connect_error", (err) => {
|
||||
console.log(`connect_error due to ${err.message}`);
|
||||
});
|
||||
|
||||
socket.on("disconnect", (reason) => {
|
||||
console.log(`disconnect due to ${reason}`);
|
||||
});
|
||||
@@ -1,12 +0,0 @@
|
||||
|
||||
import io from 'socket.io-client';
|
||||
|
||||
const socket = io('http://localhost:3000');
|
||||
|
||||
console.log('init');
|
||||
|
||||
socket.on('connect', onConnect);
|
||||
|
||||
function onConnect(){
|
||||
console.log('connect ' + socket.id);
|
||||
}
|
||||
@@ -2,20 +2,15 @@
|
||||
"name": "webpack-build",
|
||||
"version": "1.0.0",
|
||||
"description": "A sample Webpack build",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "webpack --config ./support/webpack.config.js",
|
||||
"build-slim": "webpack --config ./support/webpack.config.slim.js",
|
||||
"build-json-parser": "webpack --config ./support/webpack.config.json-parser.js",
|
||||
"build-all": "npm run build && npm run build-slim && npm run build-json-parser"
|
||||
"build": "webpack"
|
||||
},
|
||||
"author": "Damien Arrachequesne",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"socket.io-client": "^2.0.2",
|
||||
"socket.io-json-parser": "^2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"strip-loader": "^0.1.2",
|
||||
"webpack": "^2.6.1"
|
||||
"socket.io-client": "^4.4.1",
|
||||
"webpack": "^5.72.0",
|
||||
"webpack-cli": "^4.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
|
||||
module.exports = function () { return function () {}; };
|
||||
@@ -1,8 +0,0 @@
|
||||
|
||||
module.exports = {
|
||||
entry: './lib/index.js',
|
||||
output: {
|
||||
path: require('path').join(__dirname, '../dist'),
|
||||
filename: 'app.js'
|
||||
}
|
||||
};
|
||||
@@ -1,33 +0,0 @@
|
||||
|
||||
var webpack = require('webpack');
|
||||
|
||||
module.exports = {
|
||||
entry: './lib/index.js',
|
||||
output: {
|
||||
path: require('path').join(__dirname, '../dist'),
|
||||
filename: 'app.json-parser.js'
|
||||
},
|
||||
// generate sourcemap
|
||||
devtool: 'source-map',
|
||||
plugins: [
|
||||
// replace require('debug')() with an noop function
|
||||
new webpack.NormalModuleReplacementPlugin(/debug/, process.cwd() + '/support/noop.js'),
|
||||
// replace socket.io-parser with socket.io-json-parser
|
||||
new webpack.NormalModuleReplacementPlugin(/socket\.io-parser/, 'socket.io-json-parser'),
|
||||
// use uglifyJS (IE9+ support)
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
warnings: false
|
||||
}
|
||||
})
|
||||
],
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
// strip `debug()` calls
|
||||
test: /\.js$/,
|
||||
loader: 'strip-loader?strip[]=debug'
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
|
||||
var webpack = require('webpack');
|
||||
|
||||
module.exports = {
|
||||
entry: './lib/index.js',
|
||||
output: {
|
||||
path: require('path').join(__dirname, '../dist'),
|
||||
filename: 'app.slim.js'
|
||||
},
|
||||
// generate sourcemap
|
||||
devtool: 'source-map',
|
||||
plugins: [
|
||||
// replace require('debug')() with an noop function
|
||||
new webpack.NormalModuleReplacementPlugin(/debug/, process.cwd() + '/support/noop.js'),
|
||||
// use uglifyJS (IE9+ support)
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
warnings: false
|
||||
}
|
||||
})
|
||||
],
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
// strip `debug()` calls
|
||||
test: /\.js$/,
|
||||
loader: 'strip-loader?strip[]=debug'
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
7
examples/webpack-build/webpack.config.js
Normal file
7
examples/webpack-build/webpack.config.js
Normal file
@@ -0,0 +1,7 @@
|
||||
export default {
|
||||
entry: "./index.js",
|
||||
mode: "production",
|
||||
output: {
|
||||
filename: "bundle.js",
|
||||
},
|
||||
};
|
||||
@@ -9,7 +9,7 @@ import type {
|
||||
TypedEventBroadcaster,
|
||||
} from "./typed-events";
|
||||
|
||||
export class BroadcastOperator<EmitEvents extends EventsMap>
|
||||
export class BroadcastOperator<EmitEvents extends EventsMap, SocketData>
|
||||
implements TypedEventBroadcaster<EmitEvents>
|
||||
{
|
||||
constructor(
|
||||
@@ -26,7 +26,7 @@ export class BroadcastOperator<EmitEvents extends EventsMap>
|
||||
* @return a new BroadcastOperator instance
|
||||
* @public
|
||||
*/
|
||||
public to(room: Room | Room[]): BroadcastOperator<EmitEvents> {
|
||||
public to(room: Room | Room[]): BroadcastOperator<EmitEvents, SocketData> {
|
||||
const rooms = new Set(this.rooms);
|
||||
if (Array.isArray(room)) {
|
||||
room.forEach((r) => rooms.add(r));
|
||||
@@ -48,7 +48,7 @@ export class BroadcastOperator<EmitEvents extends EventsMap>
|
||||
* @return a new BroadcastOperator instance
|
||||
* @public
|
||||
*/
|
||||
public in(room: Room | Room[]): BroadcastOperator<EmitEvents> {
|
||||
public in(room: Room | Room[]): BroadcastOperator<EmitEvents, SocketData> {
|
||||
return this.to(room);
|
||||
}
|
||||
|
||||
@@ -59,7 +59,9 @@ export class BroadcastOperator<EmitEvents extends EventsMap>
|
||||
* @return a new BroadcastOperator instance
|
||||
* @public
|
||||
*/
|
||||
public except(room: Room | Room[]): BroadcastOperator<EmitEvents> {
|
||||
public except(
|
||||
room: Room | Room[]
|
||||
): BroadcastOperator<EmitEvents, SocketData> {
|
||||
const exceptRooms = new Set(this.exceptRooms);
|
||||
if (Array.isArray(room)) {
|
||||
room.forEach((r) => exceptRooms.add(r));
|
||||
@@ -81,7 +83,9 @@ export class BroadcastOperator<EmitEvents extends EventsMap>
|
||||
* @return a new BroadcastOperator instance
|
||||
* @public
|
||||
*/
|
||||
public compress(compress: boolean): BroadcastOperator<EmitEvents> {
|
||||
public compress(
|
||||
compress: boolean
|
||||
): BroadcastOperator<EmitEvents, SocketData> {
|
||||
const flags = Object.assign({}, this.flags, { compress });
|
||||
return new BroadcastOperator(
|
||||
this.adapter,
|
||||
@@ -99,7 +103,7 @@ export class BroadcastOperator<EmitEvents extends EventsMap>
|
||||
* @return a new BroadcastOperator instance
|
||||
* @public
|
||||
*/
|
||||
public get volatile(): BroadcastOperator<EmitEvents> {
|
||||
public get volatile(): BroadcastOperator<EmitEvents, SocketData> {
|
||||
const flags = Object.assign({}, this.flags, { volatile: true });
|
||||
return new BroadcastOperator(
|
||||
this.adapter,
|
||||
@@ -115,7 +119,7 @@ export class BroadcastOperator<EmitEvents extends EventsMap>
|
||||
* @return a new BroadcastOperator instance
|
||||
* @public
|
||||
*/
|
||||
public get local(): BroadcastOperator<EmitEvents> {
|
||||
public get local(): BroadcastOperator<EmitEvents, SocketData> {
|
||||
const flags = Object.assign({}, this.flags, { local: true });
|
||||
return new BroadcastOperator(
|
||||
this.adapter,
|
||||
@@ -125,6 +129,29 @@ export class BroadcastOperator<EmitEvents extends EventsMap>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a timeout in milliseconds for the next operation
|
||||
*
|
||||
* <pre><code>
|
||||
*
|
||||
* io.timeout(1000).emit("some-event", (err, responses) => {
|
||||
* // ...
|
||||
* });
|
||||
*
|
||||
* </pre></code>
|
||||
*
|
||||
* @param timeout
|
||||
*/
|
||||
public timeout(timeout: number) {
|
||||
const flags = Object.assign({}, this.flags, { timeout });
|
||||
return new BroadcastOperator(
|
||||
this.adapter,
|
||||
this.rooms,
|
||||
this.exceptRooms,
|
||||
flags
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits to all clients.
|
||||
*
|
||||
@@ -136,7 +163,7 @@ export class BroadcastOperator<EmitEvents extends EventsMap>
|
||||
...args: EventParams<EmitEvents, Ev>
|
||||
): boolean {
|
||||
if (RESERVED_EVENTS.has(ev)) {
|
||||
throw new Error(`"${ev}" is a reserved event name`);
|
||||
throw new Error(`"${String(ev)}" is a reserved event name`);
|
||||
}
|
||||
// set up packet object
|
||||
const data = [ev, ...args];
|
||||
@@ -145,14 +172,65 @@ export class BroadcastOperator<EmitEvents extends EventsMap>
|
||||
data: data,
|
||||
};
|
||||
|
||||
if ("function" == typeof data[data.length - 1]) {
|
||||
throw new Error("Callbacks are not supported when broadcasting");
|
||||
const withAck = typeof data[data.length - 1] === "function";
|
||||
|
||||
if (!withAck) {
|
||||
this.adapter.broadcast(packet, {
|
||||
rooms: this.rooms,
|
||||
except: this.exceptRooms,
|
||||
flags: this.flags,
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
this.adapter.broadcast(packet, {
|
||||
rooms: this.rooms,
|
||||
except: this.exceptRooms,
|
||||
flags: this.flags,
|
||||
const ack = data.pop() as (...args: any[]) => void;
|
||||
let timedOut = false;
|
||||
let responses: any[] = [];
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
timedOut = true;
|
||||
ack.apply(this, [new Error("operation has timed out"), responses]);
|
||||
}, this.flags.timeout);
|
||||
|
||||
let expectedServerCount = -1;
|
||||
let actualServerCount = 0;
|
||||
let expectedClientCount = 0;
|
||||
|
||||
const checkCompleteness = () => {
|
||||
if (
|
||||
!timedOut &&
|
||||
expectedServerCount === actualServerCount &&
|
||||
responses.length === expectedClientCount
|
||||
) {
|
||||
clearTimeout(timer);
|
||||
ack.apply(this, [null, responses]);
|
||||
}
|
||||
};
|
||||
|
||||
this.adapter.broadcastWithAck(
|
||||
packet,
|
||||
{
|
||||
rooms: this.rooms,
|
||||
except: this.exceptRooms,
|
||||
flags: this.flags,
|
||||
},
|
||||
(clientCount) => {
|
||||
// each Socket.IO server in the cluster sends the number of clients that were notified
|
||||
expectedClientCount += clientCount;
|
||||
actualServerCount++;
|
||||
checkCompleteness();
|
||||
},
|
||||
(clientResponse) => {
|
||||
// each client sends an acknowledgement
|
||||
responses.push(clientResponse);
|
||||
checkCompleteness();
|
||||
}
|
||||
);
|
||||
|
||||
this.adapter.serverCount().then((serverCount) => {
|
||||
expectedServerCount = serverCount;
|
||||
checkCompleteness();
|
||||
});
|
||||
|
||||
return true;
|
||||
@@ -177,19 +255,25 @@ export class BroadcastOperator<EmitEvents extends EventsMap>
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
public fetchSockets(): Promise<RemoteSocket<EmitEvents>[]> {
|
||||
public fetchSockets<SocketData = any>(): Promise<
|
||||
RemoteSocket<EmitEvents, SocketData>[]
|
||||
> {
|
||||
return this.adapter
|
||||
.fetchSockets({
|
||||
rooms: this.rooms,
|
||||
except: this.exceptRooms,
|
||||
flags: this.flags,
|
||||
})
|
||||
.then((sockets) => {
|
||||
return sockets.map((socket) => {
|
||||
if (socket instanceof Socket) {
|
||||
// FIXME the TypeScript compiler complains about missing private properties
|
||||
return socket as unknown as RemoteSocket<EmitEvents>;
|
||||
return socket as unknown as RemoteSocket<EmitEvents, SocketData>;
|
||||
} else {
|
||||
return new RemoteSocket(this.adapter, socket as SocketDetails);
|
||||
return new RemoteSocket(
|
||||
this.adapter,
|
||||
socket as SocketDetails<SocketData>
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -206,6 +290,7 @@ export class BroadcastOperator<EmitEvents extends EventsMap>
|
||||
{
|
||||
rooms: this.rooms,
|
||||
except: this.exceptRooms,
|
||||
flags: this.flags,
|
||||
},
|
||||
Array.isArray(room) ? room : [room]
|
||||
);
|
||||
@@ -222,6 +307,7 @@ export class BroadcastOperator<EmitEvents extends EventsMap>
|
||||
{
|
||||
rooms: this.rooms,
|
||||
except: this.exceptRooms,
|
||||
flags: this.flags,
|
||||
},
|
||||
Array.isArray(room) ? room : [room]
|
||||
);
|
||||
@@ -238,6 +324,7 @@ export class BroadcastOperator<EmitEvents extends EventsMap>
|
||||
{
|
||||
rooms: this.rooms,
|
||||
except: this.exceptRooms,
|
||||
flags: this.flags,
|
||||
},
|
||||
close
|
||||
);
|
||||
@@ -247,27 +334,27 @@ export class BroadcastOperator<EmitEvents extends EventsMap>
|
||||
/**
|
||||
* Format of the data when the Socket instance exists on another Socket.IO server
|
||||
*/
|
||||
interface SocketDetails {
|
||||
interface SocketDetails<SocketData> {
|
||||
id: SocketId;
|
||||
handshake: Handshake;
|
||||
rooms: Room[];
|
||||
data: any;
|
||||
data: SocketData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expose of subset of the attributes and methods of the Socket class
|
||||
*/
|
||||
export class RemoteSocket<EmitEvents extends EventsMap>
|
||||
export class RemoteSocket<EmitEvents extends EventsMap, SocketData>
|
||||
implements TypedEventBroadcaster<EmitEvents>
|
||||
{
|
||||
public readonly id: SocketId;
|
||||
public readonly handshake: Handshake;
|
||||
public readonly rooms: Set<Room>;
|
||||
public readonly data: any;
|
||||
public readonly data: SocketData;
|
||||
|
||||
private readonly operator: BroadcastOperator<EmitEvents>;
|
||||
private readonly operator: BroadcastOperator<EmitEvents, SocketData>;
|
||||
|
||||
constructor(adapter: Adapter, details: SocketDetails) {
|
||||
constructor(adapter: Adapter, details: SocketDetails<SocketData>) {
|
||||
this.id = details.id;
|
||||
this.handshake = details.handshake;
|
||||
this.rooms = new Set(details.rooms);
|
||||
|
||||
@@ -248,6 +248,7 @@ export class Client<
|
||||
try {
|
||||
this.decoder.add(data);
|
||||
} catch (e) {
|
||||
debug("invalid packet format");
|
||||
this.onerror(e);
|
||||
}
|
||||
}
|
||||
@@ -258,22 +259,31 @@ export class Client<
|
||||
* @private
|
||||
*/
|
||||
private ondecoded(packet: Packet): void {
|
||||
if (PacketType.CONNECT === packet.type) {
|
||||
if (this.conn.protocol === 3) {
|
||||
const parsed = url.parse(packet.nsp, true);
|
||||
this.connect(parsed.pathname!, parsed.query);
|
||||
} else {
|
||||
this.connect(packet.nsp, packet.data);
|
||||
}
|
||||
let namespace: string;
|
||||
let authPayload;
|
||||
if (this.conn.protocol === 3) {
|
||||
const parsed = url.parse(packet.nsp, true);
|
||||
namespace = parsed.pathname!;
|
||||
authPayload = parsed.query;
|
||||
} else {
|
||||
const socket = this.nsps.get(packet.nsp);
|
||||
if (socket) {
|
||||
process.nextTick(function () {
|
||||
socket._onpacket(packet);
|
||||
});
|
||||
} else {
|
||||
debug("no socket for namespace %s", packet.nsp);
|
||||
}
|
||||
namespace = packet.nsp;
|
||||
authPayload = packet.data;
|
||||
}
|
||||
const socket = this.nsps.get(namespace);
|
||||
|
||||
if (!socket && packet.type === PacketType.CONNECT) {
|
||||
this.connect(namespace, authPayload);
|
||||
} else if (
|
||||
socket &&
|
||||
packet.type !== PacketType.CONNECT &&
|
||||
packet.type !== PacketType.CONNECT_ERROR
|
||||
) {
|
||||
process.nextTick(function () {
|
||||
socket._onpacket(packet);
|
||||
});
|
||||
} else {
|
||||
debug("invalid state (packet type: %s)", packet.type);
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
85
lib/index.ts
85
lib/index.ts
@@ -1,4 +1,5 @@
|
||||
import http = require("http");
|
||||
import type { Server as HTTPSServer } from "https";
|
||||
import { createReadStream } from "fs";
|
||||
import { createDeflate, createGzip, createBrotliCompress } from "zlib";
|
||||
import accepts = require("accepts");
|
||||
@@ -28,7 +29,7 @@ import {
|
||||
StrictEventEmitter,
|
||||
EventNames,
|
||||
} from "./typed-events";
|
||||
import { patchAdapter, restoreAdapter, serveFile } from "./uws.js";
|
||||
import { patchAdapter, restoreAdapter, serveFile } from "./uws";
|
||||
|
||||
const debug = debugModule("socket.io:server");
|
||||
|
||||
@@ -112,11 +113,13 @@ export class Server<
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_nsps: Map<string, Namespace<ListenEvents, EmitEvents, ServerSideEvents>> =
|
||||
new Map();
|
||||
_nsps: Map<
|
||||
string,
|
||||
Namespace<ListenEvents, EmitEvents, ServerSideEvents, SocketData>
|
||||
> = new Map();
|
||||
private parentNsps: Map<
|
||||
ParentNspNameMatchFn,
|
||||
ParentNamespace<ListenEvents, EmitEvents, ServerSideEvents>
|
||||
ParentNamespace<ListenEvents, EmitEvents, ServerSideEvents, SocketData>
|
||||
> = new Map();
|
||||
private _adapter?: AdapterConstructor;
|
||||
private _serveClient: boolean;
|
||||
@@ -129,7 +132,7 @@ export class Server<
|
||||
* @private
|
||||
*/
|
||||
_connectTimeout: number;
|
||||
private httpServer: http.Server;
|
||||
private httpServer: http.Server | HTTPSServer;
|
||||
|
||||
/**
|
||||
* Server constructor.
|
||||
@@ -139,13 +142,26 @@ export class Server<
|
||||
* @public
|
||||
*/
|
||||
constructor(opts?: Partial<ServerOptions>);
|
||||
constructor(srv?: http.Server | number, opts?: Partial<ServerOptions>);
|
||||
constructor(
|
||||
srv: undefined | Partial<ServerOptions> | http.Server | number,
|
||||
srv?: http.Server | HTTPSServer | number,
|
||||
opts?: Partial<ServerOptions>
|
||||
);
|
||||
constructor(
|
||||
srv: undefined | Partial<ServerOptions> | http.Server | number,
|
||||
srv:
|
||||
| undefined
|
||||
| Partial<ServerOptions>
|
||||
| http.Server
|
||||
| HTTPSServer
|
||||
| number,
|
||||
opts?: Partial<ServerOptions>
|
||||
);
|
||||
constructor(
|
||||
srv:
|
||||
| undefined
|
||||
| Partial<ServerOptions>
|
||||
| http.Server
|
||||
| HTTPSServer
|
||||
| number,
|
||||
opts: Partial<ServerOptions> = {}
|
||||
) {
|
||||
super();
|
||||
@@ -165,7 +181,8 @@ export class Server<
|
||||
this.adapter(opts.adapter || Adapter);
|
||||
this.sockets = this.of("/");
|
||||
this.opts = opts;
|
||||
if (srv || typeof srv == "number") this.attach(srv as http.Server | number);
|
||||
if (srv || typeof srv == "number")
|
||||
this.attach(srv as http.Server | HTTPSServer | number);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -197,7 +214,9 @@ export class Server<
|
||||
name: string,
|
||||
auth: { [key: string]: any },
|
||||
fn: (
|
||||
nsp: Namespace<ListenEvents, EmitEvents, ServerSideEvents> | false
|
||||
nsp:
|
||||
| Namespace<ListenEvents, EmitEvents, ServerSideEvents, SocketData>
|
||||
| false
|
||||
) => void
|
||||
): void {
|
||||
if (this.parentNsps.size === 0) return fn(false);
|
||||
@@ -296,7 +315,7 @@ export class Server<
|
||||
* @public
|
||||
*/
|
||||
public listen(
|
||||
srv: http.Server | number,
|
||||
srv: http.Server | HTTPSServer | number,
|
||||
opts: Partial<ServerOptions> = {}
|
||||
): this {
|
||||
return this.attach(srv, opts);
|
||||
@@ -311,7 +330,7 @@ export class Server<
|
||||
* @public
|
||||
*/
|
||||
public attach(
|
||||
srv: http.Server | number,
|
||||
srv: http.Server | HTTPSServer | number,
|
||||
opts: Partial<ServerOptions> = {}
|
||||
): this {
|
||||
if ("function" == typeof srv) {
|
||||
@@ -417,7 +436,7 @@ export class Server<
|
||||
* @private
|
||||
*/
|
||||
private initEngine(
|
||||
srv: http.Server,
|
||||
srv: http.Server | HTTPSServer,
|
||||
opts: EngineOptions & AttachOptions
|
||||
): void {
|
||||
// initialize engine
|
||||
@@ -440,7 +459,7 @@ export class Server<
|
||||
* @param srv http server
|
||||
* @private
|
||||
*/
|
||||
private attachServe(srv: http.Server): void {
|
||||
private attachServe(srv: http.Server | HTTPSServer): void {
|
||||
debug("attaching client serving req handler");
|
||||
|
||||
const evs = srv.listeners("request").slice(0);
|
||||
@@ -579,7 +598,7 @@ export class Server<
|
||||
fn?: (
|
||||
socket: Socket<ListenEvents, EmitEvents, ServerSideEvents, SocketData>
|
||||
) => void
|
||||
): Namespace<ListenEvents, EmitEvents, ServerSideEvents> {
|
||||
): Namespace<ListenEvents, EmitEvents, ServerSideEvents, SocketData> {
|
||||
if (typeof name === "function" || name instanceof RegExp) {
|
||||
const parentNsp = new ParentNamespace(this);
|
||||
debug("initializing parent namespace %s", parentNsp.name);
|
||||
@@ -660,7 +679,7 @@ export class Server<
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public to(room: Room | Room[]): BroadcastOperator<EmitEvents> {
|
||||
public to(room: Room | Room[]): BroadcastOperator<EmitEvents, SocketData> {
|
||||
return this.sockets.to(room);
|
||||
}
|
||||
|
||||
@@ -671,7 +690,7 @@ export class Server<
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public in(room: Room | Room[]): BroadcastOperator<EmitEvents> {
|
||||
public in(room: Room | Room[]): BroadcastOperator<EmitEvents, SocketData> {
|
||||
return this.sockets.in(room);
|
||||
}
|
||||
|
||||
@@ -682,7 +701,9 @@ export class Server<
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public except(name: Room | Room[]): BroadcastOperator<EmitEvents> {
|
||||
public except(
|
||||
name: Room | Room[]
|
||||
): BroadcastOperator<EmitEvents, SocketData> {
|
||||
return this.sockets.except(name);
|
||||
}
|
||||
|
||||
@@ -738,7 +759,9 @@ export class Server<
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public compress(compress: boolean): BroadcastOperator<EmitEvents> {
|
||||
public compress(
|
||||
compress: boolean
|
||||
): BroadcastOperator<EmitEvents, SocketData> {
|
||||
return this.sockets.compress(compress);
|
||||
}
|
||||
|
||||
@@ -750,7 +773,7 @@ export class Server<
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public get volatile(): BroadcastOperator<EmitEvents> {
|
||||
public get volatile(): BroadcastOperator<EmitEvents, SocketData> {
|
||||
return this.sockets.volatile;
|
||||
}
|
||||
|
||||
@@ -760,16 +783,33 @@ export class Server<
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public get local(): BroadcastOperator<EmitEvents> {
|
||||
public get local(): BroadcastOperator<EmitEvents, SocketData> {
|
||||
return this.sockets.local;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a timeout in milliseconds for the next operation
|
||||
*
|
||||
* <pre><code>
|
||||
*
|
||||
* io.timeout(1000).emit("some-event", (err, responses) => {
|
||||
* // ...
|
||||
* });
|
||||
*
|
||||
* </pre></code>
|
||||
*
|
||||
* @param timeout
|
||||
*/
|
||||
public timeout(timeout: number) {
|
||||
return this.sockets.timeout(timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the matching socket instances
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
public fetchSockets(): Promise<RemoteSocket<EmitEvents>[]> {
|
||||
public fetchSockets(): Promise<RemoteSocket<EmitEvents, SocketData>[]> {
|
||||
return this.sockets.fetchSockets();
|
||||
}
|
||||
|
||||
@@ -826,3 +866,4 @@ module.exports.Namespace = Namespace;
|
||||
module.exports.Socket = Socket;
|
||||
|
||||
export { Socket, ServerOptions, Namespace, BroadcastOperator, RemoteSocket };
|
||||
export { Event } from "./socket";
|
||||
|
||||
104
lib/namespace.ts
104
lib/namespace.ts
@@ -33,9 +33,9 @@ export interface NamespaceReservedEventsMap<
|
||||
}
|
||||
|
||||
export interface ServerReservedEventsMap<
|
||||
ListenEvents,
|
||||
EmitEvents,
|
||||
ServerSideEvents,
|
||||
ListenEvents extends EventsMap,
|
||||
EmitEvents extends EventsMap,
|
||||
ServerSideEvents extends EventsMap,
|
||||
SocketData
|
||||
> extends NamespaceReservedEventsMap<
|
||||
ListenEvents,
|
||||
@@ -175,7 +175,7 @@ export class Namespace<
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public to(room: Room | Room[]): BroadcastOperator<EmitEvents> {
|
||||
public to(room: Room | Room[]): BroadcastOperator<EmitEvents, SocketData> {
|
||||
return new BroadcastOperator(this.adapter).to(room);
|
||||
}
|
||||
|
||||
@@ -186,7 +186,7 @@ export class Namespace<
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public in(room: Room | Room[]): BroadcastOperator<EmitEvents> {
|
||||
public in(room: Room | Room[]): BroadcastOperator<EmitEvents, SocketData> {
|
||||
return new BroadcastOperator(this.adapter).in(room);
|
||||
}
|
||||
|
||||
@@ -197,7 +197,9 @@ export class Namespace<
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public except(room: Room | Room[]): BroadcastOperator<EmitEvents> {
|
||||
public except(
|
||||
room: Room | Room[]
|
||||
): BroadcastOperator<EmitEvents, SocketData> {
|
||||
return new BroadcastOperator(this.adapter).except(room);
|
||||
}
|
||||
|
||||
@@ -216,34 +218,38 @@ export class Namespace<
|
||||
const socket = new Socket(this, client, query);
|
||||
this.run(socket, (err) => {
|
||||
process.nextTick(() => {
|
||||
if ("open" == client.conn.readyState) {
|
||||
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);
|
||||
|
||||
// it's paramount that the internal `onconnect` logic
|
||||
// fires before user-set events to prevent state order
|
||||
// violations (such as a disconnection before the connection
|
||||
// logic is complete)
|
||||
socket._onconnect();
|
||||
if (fn) fn();
|
||||
|
||||
// fire user-set events
|
||||
this.emitReserved("connect", socket);
|
||||
this.emitReserved("connection", socket);
|
||||
} else {
|
||||
if ("open" !== client.conn.readyState) {
|
||||
debug("next called after client was closed - ignoring socket");
|
||||
socket._cleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
debug("middleware error, sending CONNECT_ERROR packet to the client");
|
||||
socket._cleanup();
|
||||
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);
|
||||
|
||||
// it's paramount that the internal `onconnect` logic
|
||||
// fires before user-set events to prevent state order
|
||||
// violations (such as a disconnection before the connection
|
||||
// logic is complete)
|
||||
socket._onconnect();
|
||||
if (fn) fn();
|
||||
|
||||
// fire user-set events
|
||||
this.emitReserved("connect", socket);
|
||||
this.emitReserved("connection", socket);
|
||||
});
|
||||
});
|
||||
return socket;
|
||||
@@ -274,7 +280,10 @@ export class Namespace<
|
||||
ev: Ev,
|
||||
...args: EventParams<EmitEvents, Ev>
|
||||
): boolean {
|
||||
return new BroadcastOperator<EmitEvents>(this.adapter).emit(ev, ...args);
|
||||
return new BroadcastOperator<EmitEvents, SocketData>(this.adapter).emit(
|
||||
ev,
|
||||
...args
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -311,7 +320,7 @@ export class Namespace<
|
||||
...args: EventParams<ServerSideEvents, Ev>
|
||||
): boolean {
|
||||
if (RESERVED_EVENTS.has(ev)) {
|
||||
throw new Error(`"${ev}" is a reserved event name`);
|
||||
throw new Error(`"${String(ev)}" is a reserved event name`);
|
||||
}
|
||||
args.unshift(ev);
|
||||
this.adapter.serverSideEmit(args);
|
||||
@@ -346,7 +355,9 @@ export class Namespace<
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public compress(compress: boolean): BroadcastOperator<EmitEvents> {
|
||||
public compress(
|
||||
compress: boolean
|
||||
): BroadcastOperator<EmitEvents, SocketData> {
|
||||
return new BroadcastOperator(this.adapter).compress(compress);
|
||||
}
|
||||
|
||||
@@ -358,7 +369,7 @@ export class Namespace<
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public get volatile(): BroadcastOperator<EmitEvents> {
|
||||
public get volatile(): BroadcastOperator<EmitEvents, SocketData> {
|
||||
return new BroadcastOperator(this.adapter).volatile;
|
||||
}
|
||||
|
||||
@@ -368,16 +379,33 @@ export class Namespace<
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public get local(): BroadcastOperator<EmitEvents> {
|
||||
public get local(): BroadcastOperator<EmitEvents, SocketData> {
|
||||
return new BroadcastOperator(this.adapter).local;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a timeout in milliseconds for the next operation
|
||||
*
|
||||
* <pre><code>
|
||||
*
|
||||
* io.timeout(1000).emit("some-event", (err, responses) => {
|
||||
* // ...
|
||||
* });
|
||||
*
|
||||
* </pre></code>
|
||||
*
|
||||
* @param timeout
|
||||
*/
|
||||
public timeout(timeout: number) {
|
||||
return new BroadcastOperator(this.adapter).timeout(timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the matching socket instances
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
public fetchSockets(): Promise<RemoteSocket<EmitEvents>[]> {
|
||||
public fetchSockets(): Promise<RemoteSocket<EmitEvents, SocketData>[]> {
|
||||
return new BroadcastOperator(this.adapter).fetchSockets();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Namespace } from "./namespace";
|
||||
import type { Server } from "./index";
|
||||
import type { Server, RemoteSocket } from "./index";
|
||||
import type {
|
||||
EventParams,
|
||||
EventNames,
|
||||
@@ -64,4 +64,13 @@ export class ParentNamespace<
|
||||
this.server._nsps.set(name, namespace);
|
||||
return namespace;
|
||||
}
|
||||
|
||||
fetchSockets(): Promise<RemoteSocket<EmitEvents, SocketData>[]> {
|
||||
// note: we could make the fetchSockets() method work for dynamic namespaces created with a regex (by sending the
|
||||
// regex to the other Socket.IO servers, and returning the sockets of each matching namespace for example), but
|
||||
// the behavior for namespaces created with a function is less clear
|
||||
// note²: we cannot loop over each children namespace, because with multiple Socket.IO servers, a given namespace
|
||||
// may exist on one node but not exist on another (since it is created upon client connection)
|
||||
throw new Error("fetchSockets() is not supported on parent namespaces");
|
||||
}
|
||||
}
|
||||
|
||||
160
lib/socket.ts
160
lib/socket.ts
@@ -107,7 +107,12 @@ export interface Handshake {
|
||||
auth: { [key: string]: any };
|
||||
}
|
||||
|
||||
type Event = [eventName: string, ...args: any[]];
|
||||
/**
|
||||
* `[eventName, ...args]`
|
||||
*/
|
||||
export type Event = [string, ...any[]];
|
||||
|
||||
function noop() {}
|
||||
|
||||
export class Socket<
|
||||
ListenEvents extends EventsMap = DefaultEventsMap,
|
||||
@@ -137,8 +142,9 @@ export class Socket<
|
||||
private readonly adapter: Adapter;
|
||||
private acks: Map<number, () => void> = new Map();
|
||||
private fns: Array<(event: Event, next: (err?: Error) => void) => void> = [];
|
||||
private flags: BroadcastFlags & { timeout?: number } = {};
|
||||
private flags: BroadcastFlags = {};
|
||||
private _anyListeners?: Array<(...args: any[]) => void>;
|
||||
private _anyOutgoingListeners?: Array<(...args: any[]) => void>;
|
||||
|
||||
/**
|
||||
* Interface to a `Client` for a given `Namespace`.
|
||||
@@ -197,7 +203,7 @@ export class Socket<
|
||||
...args: EventParams<EmitEvents, Ev>
|
||||
): boolean {
|
||||
if (RESERVED_EVENTS.has(ev)) {
|
||||
throw new Error(`"${ev}" is a reserved event name`);
|
||||
throw new Error(`"${String(ev)}" is a reserved event name`);
|
||||
}
|
||||
const data: any[] = [ev, ...args];
|
||||
const packet: any = {
|
||||
@@ -217,6 +223,7 @@ export class Socket<
|
||||
const flags = Object.assign({}, this.flags);
|
||||
this.flags = {};
|
||||
|
||||
this.notifyOutgoingListeners(packet);
|
||||
this.packet(packet, flags);
|
||||
|
||||
return true;
|
||||
@@ -251,7 +258,7 @@ export class Socket<
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public to(room: Room | Room[]): BroadcastOperator<EmitEvents> {
|
||||
public to(room: Room | Room[]): BroadcastOperator<EmitEvents, SocketData> {
|
||||
return this.newBroadcastOperator().to(room);
|
||||
}
|
||||
|
||||
@@ -262,7 +269,7 @@ export class Socket<
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public in(room: Room | Room[]): BroadcastOperator<EmitEvents> {
|
||||
public in(room: Room | Room[]): BroadcastOperator<EmitEvents, SocketData> {
|
||||
return this.newBroadcastOperator().in(room);
|
||||
}
|
||||
|
||||
@@ -273,7 +280,9 @@ export class Socket<
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public except(room: Room | Room[]): BroadcastOperator<EmitEvents> {
|
||||
public except(
|
||||
room: Room | Room[]
|
||||
): BroadcastOperator<EmitEvents, SocketData> {
|
||||
return this.newBroadcastOperator().except(room);
|
||||
}
|
||||
|
||||
@@ -400,9 +409,6 @@ export class Socket<
|
||||
case PacketType.DISCONNECT:
|
||||
this.ondisconnect();
|
||||
break;
|
||||
|
||||
case PacketType.CONNECT_ERROR:
|
||||
this._onerror(new Error(packet.data));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -507,7 +513,7 @@ export class Socket<
|
||||
if (!this.connected) return this;
|
||||
debug("closing socket - reason %s", reason);
|
||||
this.emitReserved("disconnecting", reason);
|
||||
this.leaveAll();
|
||||
this._cleanup();
|
||||
this.nsp._remove(this);
|
||||
this.client._remove(this);
|
||||
this.connected = false;
|
||||
@@ -515,6 +521,16 @@ export class Socket<
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the socket leave all the rooms it was part of and prevents it from joining any other room
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_cleanup() {
|
||||
this.leaveAll();
|
||||
this.join = noop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces an `error` packet.
|
||||
*
|
||||
@@ -577,7 +593,7 @@ export class Socket<
|
||||
* @return {Socket} self
|
||||
* @public
|
||||
*/
|
||||
public get broadcast(): BroadcastOperator<EmitEvents> {
|
||||
public get broadcast(): BroadcastOperator<EmitEvents, SocketData> {
|
||||
return this.newBroadcastOperator();
|
||||
}
|
||||
|
||||
@@ -587,7 +603,7 @@ export class Socket<
|
||||
* @return {Socket} self
|
||||
* @public
|
||||
*/
|
||||
public get local(): BroadcastOperator<EmitEvents> {
|
||||
public get local(): BroadcastOperator<EmitEvents, SocketData> {
|
||||
return this.newBroadcastOperator().local;
|
||||
}
|
||||
|
||||
@@ -705,8 +721,8 @@ export class Socket<
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the
|
||||
* callback.
|
||||
* Adds a listener that will be fired when any event is received. The event name is passed as the first argument to
|
||||
* the callback.
|
||||
*
|
||||
* @param listener
|
||||
* @public
|
||||
@@ -718,8 +734,8 @@ export class Socket<
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the
|
||||
* callback. The listener is added to the beginning of the listeners array.
|
||||
* Adds a listener that will be fired when any event is received. The event name is passed as the first argument to
|
||||
* the callback. The listener is added to the beginning of the listeners array.
|
||||
*
|
||||
* @param listener
|
||||
* @public
|
||||
@@ -731,7 +747,7 @@ export class Socket<
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener that will be fired when any event is emitted.
|
||||
* Removes the listener that will be fired when any event is received.
|
||||
*
|
||||
* @param listener
|
||||
* @public
|
||||
@@ -764,7 +780,115 @@ export class Socket<
|
||||
return this._anyListeners || [];
|
||||
}
|
||||
|
||||
private newBroadcastOperator(): BroadcastOperator<EmitEvents> {
|
||||
/**
|
||||
* Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the
|
||||
* callback.
|
||||
*
|
||||
* @param listener
|
||||
*
|
||||
* <pre><code>
|
||||
*
|
||||
* socket.onAnyOutgoing((event, ...args) => {
|
||||
* console.log(event);
|
||||
* });
|
||||
*
|
||||
* </pre></code>
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
public onAnyOutgoing(listener: (...args: any[]) => void): this {
|
||||
this._anyOutgoingListeners = this._anyOutgoingListeners || [];
|
||||
this._anyOutgoingListeners.push(listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the
|
||||
* callback. The listener is added to the beginning of the listeners array.
|
||||
*
|
||||
* @param listener
|
||||
*
|
||||
* <pre><code>
|
||||
*
|
||||
* socket.prependAnyOutgoing((event, ...args) => {
|
||||
* console.log(event);
|
||||
* });
|
||||
*
|
||||
* </pre></code>
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
public prependAnyOutgoing(listener: (...args: any[]) => void): this {
|
||||
this._anyOutgoingListeners = this._anyOutgoingListeners || [];
|
||||
this._anyOutgoingListeners.unshift(listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the listener that will be fired when any event is emitted.
|
||||
*
|
||||
* @param listener
|
||||
*
|
||||
* <pre><code>
|
||||
*
|
||||
* const handler = (event, ...args) => {
|
||||
* console.log(event);
|
||||
* }
|
||||
*
|
||||
* socket.onAnyOutgoing(handler);
|
||||
*
|
||||
* // then later
|
||||
* socket.offAnyOutgoing(handler);
|
||||
*
|
||||
* </pre></code>
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
public offAnyOutgoing(listener?: (...args: any[]) => void): this {
|
||||
if (!this._anyOutgoingListeners) {
|
||||
return this;
|
||||
}
|
||||
if (listener) {
|
||||
const listeners = this._anyOutgoingListeners;
|
||||
for (let i = 0; i < listeners.length; i++) {
|
||||
if (listener === listeners[i]) {
|
||||
listeners.splice(i, 1);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this._anyOutgoingListeners = [];
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of listeners that are listening for any event that is specified. This array can be manipulated,
|
||||
* e.g. to remove listeners.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
public listenersAnyOutgoing() {
|
||||
return this._anyOutgoingListeners || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the listeners for each packet sent (emit or broadcast)
|
||||
*
|
||||
* @param packet
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private notifyOutgoingListeners(packet: Packet) {
|
||||
if (this._anyOutgoingListeners && this._anyOutgoingListeners.length) {
|
||||
const listeners = this._anyOutgoingListeners.slice();
|
||||
for (const listener of listeners) {
|
||||
listener.apply(this, packet.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private newBroadcastOperator(): BroadcastOperator<EmitEvents, SocketData> {
|
||||
const flags = Object.assign({}, this.flags);
|
||||
this.flags = {};
|
||||
return new BroadcastOperator(
|
||||
|
||||
@@ -25,7 +25,9 @@ export function patchAdapter(app /* : TemplatedApp */) {
|
||||
if (isNew) {
|
||||
socket.conn.on("upgrade", () => {
|
||||
const rooms = this.sids.get(id);
|
||||
subscribe(this.nsp.name, socket, isNew, rooms);
|
||||
if (rooms) {
|
||||
subscribe(this.nsp.name, socket, isNew, rooms);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
6915
package-lock.json
generated
6915
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
21
package.json
21
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "socket.io",
|
||||
"version": "4.4.0",
|
||||
"version": "4.5.2",
|
||||
"description": "node.js realtime framework server",
|
||||
"keywords": [
|
||||
"realtime",
|
||||
@@ -27,7 +27,8 @@
|
||||
"main": "./dist/index.js",
|
||||
"exports": {
|
||||
"import": "./wrapper.mjs",
|
||||
"require": "./dist/index.js"
|
||||
"require": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
},
|
||||
"types": "./dist/index.d.ts",
|
||||
"license": "MIT",
|
||||
@@ -39,7 +40,7 @@
|
||||
"compile": "rimraf ./dist && tsc",
|
||||
"test": "npm run format:check && npm run compile && npm run test:types && npm run test:unit",
|
||||
"test:types": "tsd",
|
||||
"test:unit": "nyc mocha --require ts-node/register --reporter spec --slow 200 --bail --timeout 10000 test/socket.io.ts",
|
||||
"test:unit": "nyc mocha --require ts-node/register --reporter spec --slow 200 --bail --timeout 10000 test/index.ts",
|
||||
"format:check": "prettier --check \"lib/**/*.ts\" \"test/**/*.ts\"",
|
||||
"format:fix": "prettier --write \"lib/**/*.ts\" \"test/**/*.ts\"",
|
||||
"prepack": "npm run compile"
|
||||
@@ -48,23 +49,23 @@
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "~2.0.0",
|
||||
"debug": "~4.3.2",
|
||||
"engine.io": "~6.1.0",
|
||||
"socket.io-adapter": "~2.3.3",
|
||||
"socket.io-parser": "~4.0.4"
|
||||
"engine.io": "~6.2.0",
|
||||
"socket.io-adapter": "~2.4.0",
|
||||
"socket.io-parser": "~4.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^9.0.0",
|
||||
"expect.js": "0.3.1",
|
||||
"mocha": "^3.5.3",
|
||||
"mocha": "^10.0.0",
|
||||
"nyc": "^15.1.0",
|
||||
"prettier": "^2.3.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"socket.io-client": "4.4.0",
|
||||
"socket.io-client": "4.5.2",
|
||||
"socket.io-client-v2": "npm:socket.io-client@^2.4.0",
|
||||
"superagent": "^6.1.0",
|
||||
"superagent": "^8.0.0",
|
||||
"supertest": "^6.1.6",
|
||||
"ts-node": "^10.2.1",
|
||||
"tsd": "^0.17.0",
|
||||
"tsd": "^0.21.0",
|
||||
"typescript": "^4.4.2",
|
||||
"uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.0.0"
|
||||
},
|
||||
|
||||
181
test/close.ts
Normal file
181
test/close.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
import { createServer } from "http";
|
||||
import { io as ioc } from "socket.io-client";
|
||||
import { join } from "path";
|
||||
import { exec } from "child_process";
|
||||
import { Server } from "..";
|
||||
import expect from "expect.js";
|
||||
import { createClient, getPort } from "./support/util";
|
||||
import request from "supertest";
|
||||
|
||||
// TODO: update superagent as latest release now supports promises
|
||||
const eioHandshake = (httpServer): Promise<string> => {
|
||||
return new Promise((resolve) => {
|
||||
request(httpServer)
|
||||
.get("/socket.io/")
|
||||
.query({ transport: "polling", EIO: 4 })
|
||||
.end((err, res) => {
|
||||
const sid = JSON.parse(res.text.substring(1)).sid;
|
||||
resolve(sid);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const eioPush = (httpServer, sid: string, body: string): Promise<void> => {
|
||||
return new Promise((resolve) => {
|
||||
request(httpServer)
|
||||
.post("/socket.io/")
|
||||
.send(body)
|
||||
.query({ transport: "polling", EIO: 4, sid })
|
||||
.expect(200)
|
||||
.end(() => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const eioPoll = (httpServer, sid): Promise<string> => {
|
||||
return new Promise((resolve) => {
|
||||
request(httpServer)
|
||||
.get("/socket.io/")
|
||||
.query({ transport: "polling", EIO: 4, sid })
|
||||
.expect(200)
|
||||
.end((err, res) => {
|
||||
resolve(res.text);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
describe("close", () => {
|
||||
it("should be able to close sio sending a srv", (done) => {
|
||||
const httpServer = createServer().listen(0);
|
||||
const io = new Server(httpServer);
|
||||
const port = getPort(io);
|
||||
const net = require("net");
|
||||
const server = net.createServer();
|
||||
|
||||
const clientSocket = createClient(io, "/", { reconnection: false });
|
||||
|
||||
clientSocket.on("disconnect", () => {
|
||||
expect(io.sockets.sockets.size).to.equal(0);
|
||||
server.listen(port);
|
||||
});
|
||||
|
||||
clientSocket.on("connect", () => {
|
||||
expect(io.sockets.sockets.size).to.equal(1);
|
||||
io.close();
|
||||
});
|
||||
|
||||
server.once("listening", () => {
|
||||
// PORT should be free
|
||||
server.close((error) => {
|
||||
expect(error).to.be(undefined);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should be able to close sio sending a srv", (done) => {
|
||||
const io = new Server(0);
|
||||
const port = getPort(io);
|
||||
const net = require("net");
|
||||
const server = net.createServer();
|
||||
|
||||
const clientSocket = ioc("ws://0.0.0.0:" + port, {
|
||||
reconnection: false,
|
||||
});
|
||||
|
||||
clientSocket.on("disconnect", () => {
|
||||
expect(io.sockets.sockets.size).to.equal(0);
|
||||
server.listen(port);
|
||||
});
|
||||
|
||||
clientSocket.on("connect", () => {
|
||||
expect(io.sockets.sockets.size).to.equal(1);
|
||||
io.close();
|
||||
});
|
||||
|
||||
server.once("listening", () => {
|
||||
// PORT should be free
|
||||
server.close((error) => {
|
||||
expect(error).to.be(undefined);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("graceful close", () => {
|
||||
function fixture(filename) {
|
||||
return (
|
||||
'"' +
|
||||
process.execPath +
|
||||
'" "' +
|
||||
join(__dirname, "fixtures", filename) +
|
||||
'"'
|
||||
);
|
||||
}
|
||||
|
||||
it("should stop socket and timers", (done) => {
|
||||
exec(fixture("server-close.ts"), done);
|
||||
});
|
||||
});
|
||||
|
||||
describe("protocol violations", () => {
|
||||
it("should close the connection when receiving several CONNECT packets", async () => {
|
||||
const httpServer = createServer();
|
||||
const io = new Server(httpServer);
|
||||
|
||||
httpServer.listen(0);
|
||||
|
||||
const sid = await eioHandshake(httpServer);
|
||||
// send a first CONNECT packet
|
||||
await eioPush(httpServer, sid, "40");
|
||||
// send another CONNECT packet
|
||||
await eioPush(httpServer, sid, "40");
|
||||
// session is cleanly closed (not discarded, see 'client.close()')
|
||||
// first, we receive the Socket.IO handshake response
|
||||
await eioPoll(httpServer, sid);
|
||||
// then a close packet
|
||||
const body = await eioPoll(httpServer, sid);
|
||||
expect(body).to.be("6\u001e1");
|
||||
|
||||
io.close();
|
||||
});
|
||||
|
||||
it("should close the connection when receiving an EVENT packet while not connected", async () => {
|
||||
const httpServer = createServer();
|
||||
const io = new Server(httpServer);
|
||||
|
||||
httpServer.listen(0);
|
||||
|
||||
const sid = await eioHandshake(httpServer);
|
||||
// send an EVENT packet
|
||||
await eioPush(httpServer, sid, '42["some event"]');
|
||||
// session is cleanly closed, we receive a close packet
|
||||
const body = await eioPoll(httpServer, sid);
|
||||
expect(body).to.be("6\u001e1");
|
||||
|
||||
io.close();
|
||||
});
|
||||
|
||||
it("should close the connection when receiving an invalid packet", async () => {
|
||||
const httpServer = createServer();
|
||||
const io = new Server(httpServer);
|
||||
|
||||
httpServer.listen(0);
|
||||
|
||||
const sid = await eioHandshake(httpServer);
|
||||
// send a CONNECT packet
|
||||
await eioPush(httpServer, sid, "40");
|
||||
// send an invalid packet
|
||||
await eioPush(httpServer, sid, "4abc");
|
||||
// session is cleanly closed (not discarded, see 'client.close()')
|
||||
// first, we receive the Socket.IO handshake response
|
||||
await eioPoll(httpServer, sid);
|
||||
// then a close packet
|
||||
const body = await eioPoll(httpServer, sid);
|
||||
expect(body).to.be("6\u001e1");
|
||||
|
||||
io.close();
|
||||
});
|
||||
});
|
||||
});
|
||||
87
test/handshake.ts
Normal file
87
test/handshake.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { Server } from "..";
|
||||
import expect from "expect.js";
|
||||
import { getPort, success } from "./support/util";
|
||||
|
||||
describe("handshake", () => {
|
||||
const request = require("superagent");
|
||||
|
||||
it("should send the Access-Control-Allow-xxx headers on OPTIONS request", (done) => {
|
||||
const io = new Server(0, {
|
||||
cors: {
|
||||
origin: "http://localhost:54023",
|
||||
methods: ["GET", "POST"],
|
||||
allowedHeaders: ["content-type"],
|
||||
credentials: true,
|
||||
},
|
||||
});
|
||||
request
|
||||
.options(`http://localhost:${getPort(io)}/socket.io/default/`)
|
||||
.query({ transport: "polling", EIO: 4 })
|
||||
.set("Origin", "http://localhost:54023")
|
||||
.end((err, res) => {
|
||||
expect(res.status).to.be(204);
|
||||
|
||||
expect(res.headers["access-control-allow-origin"]).to.be(
|
||||
"http://localhost:54023"
|
||||
);
|
||||
expect(res.headers["access-control-allow-methods"]).to.be("GET,POST");
|
||||
expect(res.headers["access-control-allow-headers"]).to.be(
|
||||
"content-type"
|
||||
);
|
||||
expect(res.headers["access-control-allow-credentials"]).to.be("true");
|
||||
success(done, io);
|
||||
});
|
||||
});
|
||||
|
||||
it("should send the Access-Control-Allow-xxx headers on GET request", (done) => {
|
||||
const io = new Server(0, {
|
||||
cors: {
|
||||
origin: "http://localhost:54024",
|
||||
methods: ["GET", "POST"],
|
||||
allowedHeaders: ["content-type"],
|
||||
credentials: true,
|
||||
},
|
||||
});
|
||||
request
|
||||
.get(`http://localhost:${getPort(io)}/socket.io/default/`)
|
||||
.query({ transport: "polling", EIO: 4 })
|
||||
.set("Origin", "http://localhost:54024")
|
||||
.end((err, res) => {
|
||||
expect(res.status).to.be(200);
|
||||
|
||||
expect(res.headers["access-control-allow-origin"]).to.be(
|
||||
"http://localhost:54024"
|
||||
);
|
||||
expect(res.headers["access-control-allow-credentials"]).to.be("true");
|
||||
success(done, io);
|
||||
});
|
||||
});
|
||||
|
||||
it("should allow request if custom function in opts.allowRequest returns true", (done) => {
|
||||
const io = new Server(0, {
|
||||
allowRequest: (req, callback) => callback(null, true),
|
||||
});
|
||||
|
||||
request
|
||||
.get(`http://localhost:${getPort(io)}/socket.io/default/`)
|
||||
.query({ transport: "polling", EIO: 4 })
|
||||
.end((err, res) => {
|
||||
expect(res.status).to.be(200);
|
||||
success(done, io);
|
||||
});
|
||||
});
|
||||
|
||||
it("should disallow request if custom function in opts.allowRequest returns false", (done) => {
|
||||
const io = new Server(0, {
|
||||
allowRequest: (req, callback) => callback(null, false),
|
||||
});
|
||||
request
|
||||
.get(`http://localhost:${getPort(io)}/socket.io/default/`)
|
||||
.set("origin", "http://foo.example")
|
||||
.query({ transport: "polling", EIO: 4 })
|
||||
.end((err, res) => {
|
||||
expect(res.status).to.be(403);
|
||||
success(done, io);
|
||||
});
|
||||
});
|
||||
});
|
||||
23
test/index.ts
Normal file
23
test/index.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
"use strict";
|
||||
|
||||
import expect from "expect.js";
|
||||
|
||||
describe("socket.io", () => {
|
||||
it("should be the same version as client", () => {
|
||||
const version = require("../package").version;
|
||||
expect(version).to.be(require("socket.io-client/package.json").version);
|
||||
});
|
||||
|
||||
require("./server-attachment");
|
||||
require("./handshake");
|
||||
require("./close");
|
||||
require("./namespaces");
|
||||
require("./socket");
|
||||
require("./messaging-many");
|
||||
require("./middleware");
|
||||
require("./socket-middleware");
|
||||
require("./v2-compatibility");
|
||||
require("./socket-timeout");
|
||||
require("./uws");
|
||||
require("./utility-methods");
|
||||
});
|
||||
501
test/messaging-many.ts
Normal file
501
test/messaging-many.ts
Normal file
@@ -0,0 +1,501 @@
|
||||
import { Server } from "..";
|
||||
import expect from "expect.js";
|
||||
import {
|
||||
createClient,
|
||||
createPartialDone,
|
||||
success,
|
||||
successFn,
|
||||
waitFor,
|
||||
} from "./support/util";
|
||||
|
||||
describe("messaging many", () => {
|
||||
it("emits to a namespace", (done) => {
|
||||
const io = new Server(0);
|
||||
const socket1 = createClient(io, "/", { multiplex: false });
|
||||
const socket2 = createClient(io, "/", { multiplex: false });
|
||||
const socket3 = createClient(io, "/test");
|
||||
|
||||
const partialDone = createPartialDone(
|
||||
2,
|
||||
successFn(done, io, socket1, socket2, socket3)
|
||||
);
|
||||
|
||||
socket1.on("a", (a) => {
|
||||
expect(a).to.be("b");
|
||||
partialDone();
|
||||
});
|
||||
socket2.on("a", (a) => {
|
||||
expect(a).to.be("b");
|
||||
partialDone();
|
||||
});
|
||||
socket3.on("a", () => {
|
||||
done(new Error("not"));
|
||||
});
|
||||
|
||||
let sockets = 3;
|
||||
io.on("connection", () => {
|
||||
--sockets || emit();
|
||||
});
|
||||
io.of("/test", () => {
|
||||
--sockets || emit();
|
||||
});
|
||||
|
||||
function emit() {
|
||||
io.emit("a", "b");
|
||||
}
|
||||
});
|
||||
|
||||
it("emits binary data to a namespace", (done) => {
|
||||
const io = new Server(0);
|
||||
const socket1 = createClient(io, "/", { multiplex: false });
|
||||
const socket2 = createClient(io, "/", { multiplex: false });
|
||||
const socket3 = createClient(io, "/test");
|
||||
|
||||
const partialDone = createPartialDone(
|
||||
2,
|
||||
successFn(done, io, socket1, socket2, socket3)
|
||||
);
|
||||
|
||||
socket1.on("bin", (a) => {
|
||||
expect(Buffer.isBuffer(a)).to.be(true);
|
||||
partialDone();
|
||||
});
|
||||
socket2.on("bin", (a) => {
|
||||
expect(Buffer.isBuffer(a)).to.be(true);
|
||||
partialDone();
|
||||
});
|
||||
socket3.on("bin", () => {
|
||||
done(new Error("not"));
|
||||
});
|
||||
|
||||
let sockets = 3;
|
||||
io.on("connection", () => {
|
||||
--sockets || emit();
|
||||
});
|
||||
io.of("/test", () => {
|
||||
--sockets || emit();
|
||||
});
|
||||
|
||||
function emit() {
|
||||
io.emit("bin", Buffer.alloc(10));
|
||||
}
|
||||
});
|
||||
|
||||
it("emits to the rest", (done) => {
|
||||
const io = new Server(0);
|
||||
const socket1 = createClient(io, "/", { multiplex: false });
|
||||
const socket2 = createClient(io, "/", { multiplex: false });
|
||||
const socket3 = createClient(io, "/test");
|
||||
|
||||
socket1.on("a", (a) => {
|
||||
expect(a).to.be("b");
|
||||
socket1.emit("finish");
|
||||
});
|
||||
socket2.emit("broadcast");
|
||||
socket2.on("a", () => {
|
||||
done(new Error("done"));
|
||||
});
|
||||
socket3.on("a", () => {
|
||||
done(new Error("not"));
|
||||
});
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
socket.on("broadcast", () => {
|
||||
socket.broadcast.emit("a", "b");
|
||||
});
|
||||
socket.on("finish", () => {
|
||||
success(done, io, socket1, socket2, socket3);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("emits to rooms", (done) => {
|
||||
const io = new Server(0);
|
||||
const socket1 = createClient(io, "/", { multiplex: false });
|
||||
const socket2 = createClient(io, "/", { multiplex: false });
|
||||
|
||||
socket2.on("a", () => {
|
||||
done(new Error("not"));
|
||||
});
|
||||
socket1.on("a", () => {
|
||||
success(done, io, socket1, socket2);
|
||||
});
|
||||
socket1.emit("join", "woot");
|
||||
socket1.emit("emit", "woot");
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
socket.on("join", (room, fn) => {
|
||||
socket.join(room);
|
||||
fn && fn();
|
||||
});
|
||||
|
||||
socket.on("emit", (room) => {
|
||||
io.in(room).emit("a");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("emits to rooms avoiding dupes", (done) => {
|
||||
const io = new Server(0);
|
||||
const socket1 = createClient(io, "/", { multiplex: false });
|
||||
const socket2 = createClient(io, "/", { multiplex: false });
|
||||
|
||||
const partialDone = createPartialDone(
|
||||
2,
|
||||
successFn(done, io, socket1, socket2)
|
||||
);
|
||||
|
||||
socket2.on("a", () => {
|
||||
done(new Error("not"));
|
||||
});
|
||||
socket1.on("a", partialDone);
|
||||
socket2.on("b", partialDone);
|
||||
|
||||
socket1.emit("join", "woot");
|
||||
socket1.emit("join", "test");
|
||||
socket2.emit("join", "third", () => {
|
||||
socket2.emit("emit");
|
||||
});
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
socket.on("join", (room, fn) => {
|
||||
socket.join(room);
|
||||
fn && fn();
|
||||
});
|
||||
|
||||
socket.on("emit", () => {
|
||||
io.in("woot").in("test").emit("a");
|
||||
io.in("third").emit("b");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("broadcasts to rooms", (done) => {
|
||||
const io = new Server(0);
|
||||
const socket1 = createClient(io, "/", { multiplex: false });
|
||||
const socket2 = createClient(io, "/", { multiplex: false });
|
||||
const socket3 = createClient(io, "/", { multiplex: false });
|
||||
|
||||
const partialDone = createPartialDone(
|
||||
2,
|
||||
successFn(done, io, socket1, socket2, socket3)
|
||||
);
|
||||
|
||||
socket1.emit("join", "woot");
|
||||
socket2.emit("join", "test");
|
||||
socket3.emit("join", "test", () => {
|
||||
socket3.emit("broadcast");
|
||||
});
|
||||
|
||||
socket1.on("a", () => {
|
||||
done(new Error("not"));
|
||||
});
|
||||
socket2.on("a", () => {
|
||||
partialDone();
|
||||
});
|
||||
socket3.on("a", () => {
|
||||
done(new Error("not"));
|
||||
});
|
||||
socket3.on("b", () => {
|
||||
partialDone();
|
||||
});
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
socket.on("join", (room, fn) => {
|
||||
socket.join(room);
|
||||
fn && fn();
|
||||
});
|
||||
|
||||
socket.on("broadcast", () => {
|
||||
socket.broadcast.to("test").emit("a");
|
||||
socket.emit("b");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("broadcasts binary data to rooms", (done) => {
|
||||
const io = new Server(0);
|
||||
const socket1 = createClient(io, "/", { multiplex: false });
|
||||
const socket2 = createClient(io, "/", { multiplex: false });
|
||||
const socket3 = createClient(io, "/", { multiplex: false });
|
||||
|
||||
const partialDone = createPartialDone(
|
||||
2,
|
||||
successFn(done, io, socket1, socket2, socket3)
|
||||
);
|
||||
|
||||
socket1.emit("join", "woot");
|
||||
socket2.emit("join", "test");
|
||||
socket3.emit("join", "test", () => {
|
||||
socket3.emit("broadcast");
|
||||
});
|
||||
|
||||
socket1.on("bin", (data) => {
|
||||
throw new Error("got bin in socket1");
|
||||
});
|
||||
socket2.on("bin", (data) => {
|
||||
expect(Buffer.isBuffer(data)).to.be(true);
|
||||
partialDone();
|
||||
});
|
||||
socket2.on("bin2", (data) => {
|
||||
throw new Error("socket2 got bin2");
|
||||
});
|
||||
socket3.on("bin", (data) => {
|
||||
throw new Error("socket3 got bin");
|
||||
});
|
||||
socket3.on("bin2", (data) => {
|
||||
expect(Buffer.isBuffer(data)).to.be(true);
|
||||
partialDone();
|
||||
});
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
socket.on("join", (room, fn) => {
|
||||
socket.join(room);
|
||||
fn && fn();
|
||||
});
|
||||
socket.on("broadcast", () => {
|
||||
socket.broadcast.to("test").emit("bin", Buffer.alloc(5));
|
||||
socket.emit("bin2", Buffer.alloc(5));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps track of rooms", (done) => {
|
||||
const io = new Server(0);
|
||||
const socket = createClient(io);
|
||||
|
||||
io.on("connection", (s) => {
|
||||
s.join("a");
|
||||
expect(s.rooms).to.contain(s.id, "a");
|
||||
s.join("b");
|
||||
expect(s.rooms).to.contain(s.id, "a", "b");
|
||||
s.join("c");
|
||||
expect(s.rooms).to.contain(s.id, "a", "b", "c");
|
||||
s.leave("b");
|
||||
expect(s.rooms).to.contain(s.id, "a", "c");
|
||||
(s as any).leaveAll();
|
||||
expect(s.rooms.size).to.eql(0);
|
||||
|
||||
success(done, io, socket);
|
||||
});
|
||||
});
|
||||
|
||||
it("deletes empty rooms", (done) => {
|
||||
const io = new Server(0);
|
||||
const socket = createClient(io);
|
||||
|
||||
io.on("connection", (s) => {
|
||||
s.join("a");
|
||||
expect(s.nsp.adapter.rooms).to.contain("a");
|
||||
s.leave("a");
|
||||
expect(s.nsp.adapter.rooms).to.not.contain("a");
|
||||
|
||||
success(done, io, socket);
|
||||
});
|
||||
});
|
||||
|
||||
it("should properly cleanup left rooms", (done) => {
|
||||
const io = new Server(0);
|
||||
const socket = createClient(io);
|
||||
|
||||
io.on("connection", (s) => {
|
||||
s.join("a");
|
||||
expect(s.rooms).to.contain(s.id, "a");
|
||||
s.join("b");
|
||||
expect(s.rooms).to.contain(s.id, "a", "b");
|
||||
s.leave("unknown");
|
||||
expect(s.rooms).to.contain(s.id, "a", "b");
|
||||
(s as any).leaveAll();
|
||||
expect(s.rooms.size).to.eql(0);
|
||||
|
||||
success(done, io, socket);
|
||||
});
|
||||
});
|
||||
|
||||
it("allows to join several rooms at once", (done) => {
|
||||
const io = new Server(0);
|
||||
const socket = createClient(io);
|
||||
|
||||
io.on("connection", (s) => {
|
||||
s.join(["a", "b", "c"]);
|
||||
expect(s.rooms).to.contain(s.id, "a", "b", "c");
|
||||
success(done, io, socket);
|
||||
});
|
||||
});
|
||||
|
||||
it("should exclude specific sockets when broadcasting", (done) => {
|
||||
const io = new Server(0);
|
||||
const socket1 = createClient(io, "/", { multiplex: false });
|
||||
const socket2 = createClient(io, "/", { multiplex: false });
|
||||
const socket3 = createClient(io, "/", { multiplex: false });
|
||||
|
||||
socket2.on("a", () => {
|
||||
done(new Error("not"));
|
||||
});
|
||||
socket3.on("a", () => {
|
||||
done(new Error("not"));
|
||||
});
|
||||
socket1.on("a", successFn(done, io, socket1, socket2, socket3));
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
socket.on("exclude", (id) => {
|
||||
socket.broadcast.except(id).emit("a");
|
||||
});
|
||||
});
|
||||
|
||||
socket2.on("connect", () => {
|
||||
socket3.emit("exclude", socket2.id);
|
||||
});
|
||||
});
|
||||
|
||||
it("should exclude a specific room when broadcasting", (done) => {
|
||||
const io = new Server(0);
|
||||
const socket1 = createClient(io, "/", { multiplex: false });
|
||||
const socket2 = createClient(io, "/", { multiplex: false });
|
||||
const socket3 = createClient(io, "/", { multiplex: false });
|
||||
|
||||
socket2.on("a", () => {
|
||||
done(new Error("not"));
|
||||
});
|
||||
socket3.on("a", () => {
|
||||
done(new Error("not"));
|
||||
});
|
||||
socket1.on("a", successFn(done, io, socket1, socket2, socket3));
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
socket.on("join", (room, cb) => {
|
||||
socket.join(room);
|
||||
cb();
|
||||
});
|
||||
socket.on("broadcast", () => {
|
||||
socket.broadcast.except("room1").emit("a");
|
||||
});
|
||||
});
|
||||
|
||||
socket2.emit("join", "room1", () => {
|
||||
socket3.emit("broadcast");
|
||||
});
|
||||
});
|
||||
|
||||
it("should return an immutable broadcast operator", (done) => {
|
||||
const io = new Server(0);
|
||||
const clientSocket = createClient(io);
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
const operator = socket.local
|
||||
.compress(false)
|
||||
.to(["room1", "room2"])
|
||||
.except("room3");
|
||||
operator.compress(true).emit("hello");
|
||||
operator.volatile.emit("hello");
|
||||
operator.to("room4").emit("hello");
|
||||
operator.except("room5").emit("hello");
|
||||
socket.emit("hello");
|
||||
socket.to("room6").emit("hello");
|
||||
// @ts-ignore
|
||||
expect(operator.rooms).to.contain("room1", "room2");
|
||||
// @ts-ignore
|
||||
expect(operator.rooms).to.not.contain("room4", "room5", "room6");
|
||||
// @ts-ignore
|
||||
expect(operator.exceptRooms).to.contain("room3");
|
||||
// @ts-ignore
|
||||
expect(operator.flags).to.eql({ local: true, compress: false });
|
||||
|
||||
success(done, io, clientSocket);
|
||||
});
|
||||
});
|
||||
|
||||
it("should broadcast and expect multiple acknowledgements", (done) => {
|
||||
const io = new Server(0);
|
||||
const socket1 = createClient(io, "/", { multiplex: false });
|
||||
const socket2 = createClient(io, "/", { multiplex: false });
|
||||
const socket3 = createClient(io, "/", { multiplex: false });
|
||||
|
||||
socket1.on("some event", (cb) => {
|
||||
cb(1);
|
||||
});
|
||||
|
||||
socket2.on("some event", (cb) => {
|
||||
cb(2);
|
||||
});
|
||||
|
||||
socket3.on("some event", (cb) => {
|
||||
cb(3);
|
||||
});
|
||||
|
||||
Promise.all([
|
||||
waitFor(socket1, "connect"),
|
||||
waitFor(socket2, "connect"),
|
||||
waitFor(socket3, "connect"),
|
||||
]).then(() => {
|
||||
io.timeout(2000).emit("some event", (err, responses) => {
|
||||
expect(err).to.be(null);
|
||||
expect(responses).to.have.length(3);
|
||||
expect(responses).to.contain(1, 2, 3);
|
||||
|
||||
success(done, io, socket1, socket2, socket3);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail when a client does not acknowledge the event in the given delay", (done) => {
|
||||
const io = new Server(0);
|
||||
const socket1 = createClient(io, "/", { multiplex: false });
|
||||
const socket2 = createClient(io, "/", { multiplex: false });
|
||||
const socket3 = createClient(io, "/", { multiplex: false });
|
||||
|
||||
socket1.on("some event", (cb) => {
|
||||
cb(1);
|
||||
});
|
||||
|
||||
socket2.on("some event", (cb) => {
|
||||
cb(2);
|
||||
});
|
||||
|
||||
socket3.on("some event", () => {
|
||||
// timeout
|
||||
});
|
||||
|
||||
Promise.all([
|
||||
waitFor(socket1, "connect"),
|
||||
waitFor(socket2, "connect"),
|
||||
waitFor(socket3, "connect"),
|
||||
]).then(() => {
|
||||
io.timeout(200).emit("some event", (err, responses) => {
|
||||
expect(err).to.be.an(Error);
|
||||
expect(responses).to.have.length(2);
|
||||
expect(responses).to.contain(1, 2);
|
||||
|
||||
success(done, io, socket1, socket2, socket3);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should broadcast and return if the packet is sent to 0 client", (done) => {
|
||||
const io = new Server(0);
|
||||
const socket1 = createClient(io, "/", { multiplex: false });
|
||||
const socket2 = createClient(io, "/", { multiplex: false });
|
||||
const socket3 = createClient(io, "/", { multiplex: false });
|
||||
|
||||
socket1.on("some event", () => {
|
||||
done(new Error("should not happen"));
|
||||
});
|
||||
|
||||
socket2.on("some event", () => {
|
||||
done(new Error("should not happen"));
|
||||
});
|
||||
|
||||
socket3.on("some event", () => {
|
||||
done(new Error("should not happen"));
|
||||
});
|
||||
|
||||
io.to("room123")
|
||||
.timeout(200)
|
||||
.emit("some event", (err, responses) => {
|
||||
expect(err).to.be(null);
|
||||
expect(responses).to.have.length(0);
|
||||
|
||||
success(done, io, socket1, socket2, socket3);
|
||||
});
|
||||
});
|
||||
});
|
||||
210
test/middleware.ts
Normal file
210
test/middleware.ts
Normal file
@@ -0,0 +1,210 @@
|
||||
import { Server, Socket } from "..";
|
||||
import expect from "expect.js";
|
||||
import {
|
||||
success,
|
||||
createClient,
|
||||
successFn,
|
||||
createPartialDone,
|
||||
} from "./support/util";
|
||||
|
||||
describe("middleware", () => {
|
||||
it("should call functions", (done) => {
|
||||
const io = new Server(0);
|
||||
|
||||
let run = 0;
|
||||
io.use((socket, next) => {
|
||||
expect(socket).to.be.a(Socket);
|
||||
run++;
|
||||
next();
|
||||
});
|
||||
io.use((socket, next) => {
|
||||
expect(socket).to.be.a(Socket);
|
||||
run++;
|
||||
next();
|
||||
});
|
||||
|
||||
const socket = createClient(io);
|
||||
socket.on("connect", () => {
|
||||
expect(run).to.be(2);
|
||||
|
||||
success(done, io, socket);
|
||||
});
|
||||
});
|
||||
|
||||
it("should pass errors", (done) => {
|
||||
const io = new Server(0);
|
||||
|
||||
io.use((socket, next) => {
|
||||
next(new Error("Authentication error"));
|
||||
});
|
||||
io.use((socket, next) => {
|
||||
done(new Error("nope"));
|
||||
});
|
||||
|
||||
const socket = createClient(io);
|
||||
socket.on("connect", () => {
|
||||
done(new Error("nope"));
|
||||
});
|
||||
socket.on("connect_error", (err) => {
|
||||
expect(err.message).to.be("Authentication error");
|
||||
|
||||
success(done, io, socket);
|
||||
});
|
||||
});
|
||||
|
||||
it("should pass an object", (done) => {
|
||||
const io = new Server(0);
|
||||
|
||||
io.use((socket, next) => {
|
||||
const err = new Error("Authentication error");
|
||||
// @ts-ignore
|
||||
err.data = { a: "b", c: 3 };
|
||||
next(err);
|
||||
});
|
||||
|
||||
const socket = createClient(io);
|
||||
socket.on("connect", () => {
|
||||
done(new Error("nope"));
|
||||
});
|
||||
socket.on("connect_error", (err) => {
|
||||
expect(err).to.be.an(Error);
|
||||
expect(err.message).to.eql("Authentication error");
|
||||
// @ts-ignore
|
||||
expect(err.data).to.eql({ a: "b", c: 3 });
|
||||
|
||||
success(done, io, socket);
|
||||
});
|
||||
});
|
||||
|
||||
it("should only call connection after fns", (done) => {
|
||||
const io = new Server(0);
|
||||
|
||||
io.use((socket: any, next) => {
|
||||
socket.name = "guillermo";
|
||||
next();
|
||||
});
|
||||
|
||||
const clientSocket = createClient(io);
|
||||
io.on("connection", (socket) => {
|
||||
expect((socket as any).name).to.be("guillermo");
|
||||
|
||||
success(done, io, clientSocket);
|
||||
});
|
||||
});
|
||||
|
||||
it("should only call connection after (lengthy) fns", (done) => {
|
||||
const io = new Server(0);
|
||||
|
||||
let authenticated = false;
|
||||
|
||||
io.use((socket, next) => {
|
||||
setTimeout(() => {
|
||||
authenticated = true;
|
||||
next();
|
||||
}, 300);
|
||||
});
|
||||
|
||||
const socket = createClient(io);
|
||||
socket.on("connect", () => {
|
||||
expect(authenticated).to.be(true);
|
||||
|
||||
success(done, io, socket);
|
||||
});
|
||||
});
|
||||
|
||||
it("should be ignored if socket gets closed", (done) => {
|
||||
const io = new Server(0);
|
||||
|
||||
let socket;
|
||||
io.use((s, next) => {
|
||||
socket.io.engine.close();
|
||||
s.client.conn.on("close", () => {
|
||||
process.nextTick(next);
|
||||
setTimeout(() => {
|
||||
success(done, io, socket);
|
||||
}, 50);
|
||||
});
|
||||
});
|
||||
|
||||
socket = createClient(io);
|
||||
io.on("connection", (socket) => {
|
||||
done(new Error("should not fire"));
|
||||
});
|
||||
});
|
||||
|
||||
it("should call functions in expected order", (done) => {
|
||||
const io = new Server(0);
|
||||
|
||||
const result: number[] = [];
|
||||
|
||||
io.use(() => {
|
||||
done(new Error("should not fire"));
|
||||
});
|
||||
io.of("/chat").use((socket, next) => {
|
||||
result.push(1);
|
||||
setTimeout(next, 50);
|
||||
});
|
||||
io.of("/chat").use((socket, next) => {
|
||||
result.push(2);
|
||||
setTimeout(next, 50);
|
||||
});
|
||||
io.of("/chat").use((socket, next) => {
|
||||
result.push(3);
|
||||
setTimeout(next, 50);
|
||||
});
|
||||
|
||||
const chat = createClient(io, "/chat");
|
||||
chat.on("connect", () => {
|
||||
expect(result).to.eql([1, 2, 3]);
|
||||
|
||||
success(done, io, chat);
|
||||
});
|
||||
});
|
||||
|
||||
it("should disable the merge of handshake packets", (done) => {
|
||||
const io = new Server(0);
|
||||
|
||||
io.use((socket, next) => {
|
||||
next();
|
||||
});
|
||||
|
||||
const socket = createClient(io);
|
||||
socket.on("connect", successFn(done, io, socket));
|
||||
});
|
||||
|
||||
it("should work with a custom namespace", (done) => {
|
||||
const io = new Server(0);
|
||||
const socket1 = createClient(io, "/");
|
||||
const socket2 = createClient(io, "/chat");
|
||||
|
||||
const partialDone = createPartialDone(
|
||||
2,
|
||||
successFn(done, io, socket1, socket2)
|
||||
);
|
||||
|
||||
io.of("/chat").use((socket, next) => {
|
||||
next();
|
||||
});
|
||||
|
||||
socket1.on("connect", partialDone);
|
||||
socket2.on("connect", partialDone);
|
||||
});
|
||||
|
||||
it("should only set `connected` to true after the middleware execution", (done) => {
|
||||
const io = new Server(0);
|
||||
const clientSocket = createClient(io, "/");
|
||||
|
||||
io.use((socket, next) => {
|
||||
expect(socket.connected).to.be(false);
|
||||
expect(socket.disconnected).to.be(true);
|
||||
next();
|
||||
});
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
expect(socket.connected).to.be(true);
|
||||
expect(socket.disconnected).to.be(false);
|
||||
|
||||
success(done, io, clientSocket);
|
||||
});
|
||||
});
|
||||
});
|
||||
575
test/namespaces.ts
Normal file
575
test/namespaces.ts
Normal file
@@ -0,0 +1,575 @@
|
||||
import type { SocketId } from "socket.io-adapter";
|
||||
import { Server, Namespace, Socket } from "..";
|
||||
import expect from "expect.js";
|
||||
import {
|
||||
success,
|
||||
createClient,
|
||||
successFn,
|
||||
createPartialDone,
|
||||
} from "./support/util";
|
||||
|
||||
describe("namespaces", () => {
|
||||
it("should be accessible through .sockets", () => {
|
||||
const io = new Server();
|
||||
expect(io.sockets).to.be.a(Namespace);
|
||||
});
|
||||
|
||||
it("should be aliased", () => {
|
||||
const io = new Server();
|
||||
expect(io.use).to.be.a("function");
|
||||
expect(io.to).to.be.a("function");
|
||||
expect(io["in"]).to.be.a("function");
|
||||
expect(io.emit).to.be.a("function");
|
||||
expect(io.send).to.be.a("function");
|
||||
expect(io.write).to.be.a("function");
|
||||
expect(io.allSockets).to.be.a("function");
|
||||
expect(io.compress).to.be.a("function");
|
||||
});
|
||||
|
||||
it("should return an immutable broadcast operator", () => {
|
||||
const io = new Server();
|
||||
const operator = io.local.to(["room1", "room2"]).except("room3");
|
||||
operator.compress(true).emit("hello");
|
||||
operator.volatile.emit("hello");
|
||||
operator.to("room4").emit("hello");
|
||||
operator.except("room5").emit("hello");
|
||||
io.to("room6").emit("hello");
|
||||
// @ts-ignore
|
||||
expect(operator.rooms).to.contain("room1", "room2");
|
||||
// @ts-ignore
|
||||
expect(operator.exceptRooms).to.contain("room3");
|
||||
// @ts-ignore
|
||||
expect(operator.flags).to.eql({ local: true });
|
||||
});
|
||||
|
||||
it("should automatically connect", (done) => {
|
||||
const io = new Server(0);
|
||||
const socket = createClient(io);
|
||||
socket.on("connect", successFn(done, io, socket));
|
||||
});
|
||||
|
||||
it("should fire a `connection` event", (done) => {
|
||||
const io = new Server(0);
|
||||
const clientSocket = createClient(io);
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
expect(socket).to.be.a(Socket);
|
||||
success(done, io, clientSocket);
|
||||
});
|
||||
});
|
||||
|
||||
it("should fire a `connect` event", (done) => {
|
||||
const io = new Server(0);
|
||||
const clientSocket = createClient(io);
|
||||
|
||||
io.on("connect", (socket) => {
|
||||
expect(socket).to.be.a(Socket);
|
||||
success(done, io, clientSocket);
|
||||
});
|
||||
});
|
||||
|
||||
it("should work with many sockets", (done) => {
|
||||
const io = new Server(0);
|
||||
io.of("/chat");
|
||||
io.of("/news");
|
||||
const chat = createClient(io, "/chat");
|
||||
const news = createClient(io, "/news");
|
||||
|
||||
let total = 2;
|
||||
chat.on("connect", () => {
|
||||
--total || success(done, io, chat, news);
|
||||
});
|
||||
news.on("connect", () => {
|
||||
--total || success(done, io, chat, news);
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to equivalently start with "" or "/" on server', (done) => {
|
||||
const io = new Server(0);
|
||||
const c1 = createClient(io, "/");
|
||||
const c2 = createClient(io, "/abc");
|
||||
|
||||
let total = 2;
|
||||
io.of("").on("connection", () => {
|
||||
--total || success(done, io, c1, c2);
|
||||
});
|
||||
io.of("abc").on("connection", () => {
|
||||
--total || success(done, io, c1, c2);
|
||||
});
|
||||
});
|
||||
|
||||
it('should be equivalent for "" and "/" on client', (done) => {
|
||||
const io = new Server(0);
|
||||
const c1 = createClient(io, "");
|
||||
|
||||
io.of("/").on("connection", successFn(done, io, c1));
|
||||
});
|
||||
|
||||
it("should work with `of` and many sockets", (done) => {
|
||||
const io = new Server(0);
|
||||
const chat = createClient(io, "/chat");
|
||||
const news = createClient(io, "/news");
|
||||
|
||||
let total = 2;
|
||||
io.of("/news").on("connection", (socket) => {
|
||||
expect(socket).to.be.a(Socket);
|
||||
--total || success(done, io, chat, news);
|
||||
});
|
||||
io.of("/news").on("connection", (socket) => {
|
||||
expect(socket).to.be.a(Socket);
|
||||
--total || success(done, io, chat, news);
|
||||
});
|
||||
});
|
||||
|
||||
it("should work with `of` second param", (done) => {
|
||||
const io = new Server(0);
|
||||
const chat = createClient(io, "/chat");
|
||||
const news = createClient(io, "/news");
|
||||
|
||||
let total = 2;
|
||||
io.of("/news", (socket) => {
|
||||
expect(socket).to.be.a(Socket);
|
||||
--total || success(done, io, chat, news);
|
||||
});
|
||||
io.of("/news", (socket) => {
|
||||
expect(socket).to.be.a(Socket);
|
||||
--total || success(done, io, chat, news);
|
||||
});
|
||||
});
|
||||
|
||||
it("should disconnect upon transport disconnection", (done) => {
|
||||
const io = new Server(0);
|
||||
const chat = createClient(io, "/chat");
|
||||
const news = createClient(io, "/news");
|
||||
|
||||
let total = 2;
|
||||
let totald = 2;
|
||||
let s;
|
||||
io.of("/news", (socket) => {
|
||||
socket.on("disconnect", (reason) => {
|
||||
--totald || success(done, io, chat, news);
|
||||
});
|
||||
--total || close();
|
||||
});
|
||||
io.of("/chat", (socket) => {
|
||||
s = socket;
|
||||
socket.on("disconnect", (reason) => {
|
||||
--totald || success(done, io, chat, news);
|
||||
});
|
||||
--total || close();
|
||||
});
|
||||
function close() {
|
||||
s.disconnect(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("should fire a `disconnecting` event just before leaving all rooms", (done) => {
|
||||
const io = new Server(0);
|
||||
const socket = createClient(io);
|
||||
|
||||
io.on("connection", (s) => {
|
||||
s.join("a");
|
||||
// FIXME not sure why process.nextTick() is needed here
|
||||
process.nextTick(() => s.disconnect());
|
||||
|
||||
let total = 2;
|
||||
s.on("disconnecting", (reason) => {
|
||||
expect(s.rooms).to.contain(s.id, "a");
|
||||
total--;
|
||||
});
|
||||
|
||||
s.on("disconnect", (reason) => {
|
||||
expect(s.rooms.size).to.eql(0);
|
||||
--total || success(done, io, socket);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should return error connecting to non-existent namespace", (done) => {
|
||||
const io = new Server(0);
|
||||
const socket = createClient(io, "/doesnotexist");
|
||||
|
||||
socket.on("connect_error", (err) => {
|
||||
expect(err.message).to.be("Invalid namespace");
|
||||
success(done, io);
|
||||
});
|
||||
});
|
||||
|
||||
it("should not reuse same-namespace connections", (done) => {
|
||||
const io = new Server(0);
|
||||
const clientSocket1 = createClient(io);
|
||||
const clientSocket2 = createClient(io);
|
||||
|
||||
let connections = 0;
|
||||
io.on("connection", () => {
|
||||
connections++;
|
||||
if (connections === 2) {
|
||||
success(done, io, clientSocket1, clientSocket2);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("should find all clients in a namespace", (done) => {
|
||||
const io = new Server(0);
|
||||
const chatSids: string[] = [];
|
||||
let otherSid: SocketId | null = null;
|
||||
|
||||
const c1 = createClient(io, "/chat");
|
||||
const c2 = createClient(io, "/chat", { forceNew: true });
|
||||
const c3 = createClient(io, "/other", { forceNew: true });
|
||||
|
||||
let total = 3;
|
||||
io.of("/chat").on("connection", (socket) => {
|
||||
chatSids.push(socket.id);
|
||||
--total || getSockets();
|
||||
});
|
||||
io.of("/other").on("connection", (socket) => {
|
||||
otherSid = socket.id;
|
||||
--total || getSockets();
|
||||
});
|
||||
|
||||
async function getSockets() {
|
||||
const sids = await io.of("/chat").allSockets();
|
||||
|
||||
expect(sids).to.contain(chatSids[0], chatSids[1]);
|
||||
expect(sids).to.not.contain(otherSid);
|
||||
success(done, io, c1, c2, c3);
|
||||
}
|
||||
});
|
||||
|
||||
it("should find all clients in a namespace room", (done) => {
|
||||
const io = new Server(0);
|
||||
let chatFooSid: SocketId | null = null;
|
||||
let chatBarSid: SocketId | null = null;
|
||||
let otherSid: SocketId | null = null;
|
||||
|
||||
const c1 = createClient(io, "/chat");
|
||||
const c2 = createClient(io, "/chat", { forceNew: true });
|
||||
const c3 = createClient(io, "/other", { forceNew: true });
|
||||
|
||||
let chatIndex = 0;
|
||||
let total = 3;
|
||||
io.of("/chat").on("connection", (socket) => {
|
||||
if (chatIndex++) {
|
||||
socket.join("foo");
|
||||
chatFooSid = socket.id;
|
||||
--total || getSockets();
|
||||
} else {
|
||||
socket.join("bar");
|
||||
chatBarSid = socket.id;
|
||||
--total || getSockets();
|
||||
}
|
||||
});
|
||||
io.of("/other").on("connection", (socket) => {
|
||||
socket.join("foo");
|
||||
otherSid = socket.id;
|
||||
--total || getSockets();
|
||||
});
|
||||
|
||||
async function getSockets() {
|
||||
const sids = await io.of("/chat").in("foo").allSockets();
|
||||
|
||||
expect(sids).to.contain(chatFooSid);
|
||||
expect(sids).to.not.contain(chatBarSid);
|
||||
expect(sids).to.not.contain(otherSid);
|
||||
success(done, io, c1, c2, c3);
|
||||
}
|
||||
});
|
||||
|
||||
it("should find all clients across namespace rooms", (done) => {
|
||||
const io = new Server(0);
|
||||
let chatFooSid: SocketId | null = null;
|
||||
let chatBarSid: SocketId | null = null;
|
||||
let otherSid: SocketId | null = null;
|
||||
|
||||
const c1 = createClient(io, "/chat");
|
||||
const c2 = createClient(io, "/chat", { forceNew: true });
|
||||
const c3 = createClient(io, "/other", { forceNew: true });
|
||||
|
||||
let chatIndex = 0;
|
||||
let total = 3;
|
||||
io.of("/chat").on("connection", (socket) => {
|
||||
if (chatIndex++) {
|
||||
socket.join("foo");
|
||||
chatFooSid = socket.id;
|
||||
--total || getSockets();
|
||||
} else {
|
||||
socket.join("bar");
|
||||
chatBarSid = socket.id;
|
||||
--total || getSockets();
|
||||
}
|
||||
});
|
||||
io.of("/other").on("connection", (socket) => {
|
||||
socket.join("foo");
|
||||
otherSid = socket.id;
|
||||
--total || getSockets();
|
||||
});
|
||||
|
||||
async function getSockets() {
|
||||
const sids = await io.of("/chat").allSockets();
|
||||
expect(sids).to.contain(chatFooSid, chatBarSid);
|
||||
expect(sids).to.not.contain(otherSid);
|
||||
success(done, io, c1, c2, c3);
|
||||
}
|
||||
});
|
||||
|
||||
it("should not emit volatile event after regular event", (done) => {
|
||||
const io = new Server(0);
|
||||
|
||||
let counter = 0;
|
||||
io.of("/chat").on("connection", (s) => {
|
||||
// Wait to make sure there are no packets being sent for opening the connection
|
||||
setTimeout(() => {
|
||||
io.of("/chat").emit("ev", "data");
|
||||
io.of("/chat").volatile.emit("ev", "data");
|
||||
}, 50);
|
||||
});
|
||||
|
||||
const socket = createClient(io, "/chat");
|
||||
socket.on("ev", () => {
|
||||
counter++;
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
expect(counter).to.be(1);
|
||||
success(done, io, socket);
|
||||
}, 500);
|
||||
});
|
||||
|
||||
it("should emit volatile event", (done) => {
|
||||
const io = new Server(0);
|
||||
|
||||
let counter = 0;
|
||||
io.of("/chat").on("connection", (s) => {
|
||||
// Wait to make sure there are no packets being sent for opening the connection
|
||||
setTimeout(() => {
|
||||
io.of("/chat").volatile.emit("ev", "data");
|
||||
}, 100);
|
||||
});
|
||||
|
||||
const socket = createClient(io, "/chat");
|
||||
socket.on("ev", () => {
|
||||
counter++;
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
expect(counter).to.be(1);
|
||||
success(done, io, socket);
|
||||
}, 500);
|
||||
});
|
||||
|
||||
it("should enable compression by default", (done) => {
|
||||
const io = new Server(0);
|
||||
const socket = createClient(io, "/chat");
|
||||
|
||||
io.of("/chat").on("connection", (s) => {
|
||||
s.conn.once("packetCreate", (packet) => {
|
||||
expect(packet.options.compress).to.be(true);
|
||||
success(done, io, socket);
|
||||
});
|
||||
io.of("/chat").emit("woot", "hi");
|
||||
});
|
||||
});
|
||||
|
||||
it("should disable compression", (done) => {
|
||||
const io = new Server(0);
|
||||
const socket = createClient(io, "/chat");
|
||||
|
||||
io.of("/chat").on("connection", (s) => {
|
||||
s.conn.once("packetCreate", (packet) => {
|
||||
expect(packet.options.compress).to.be(false);
|
||||
success(done, io, socket);
|
||||
});
|
||||
io.of("/chat").compress(false).emit("woot", "hi");
|
||||
});
|
||||
});
|
||||
|
||||
it("should throw on reserved event", () => {
|
||||
const io = new Server();
|
||||
|
||||
expect(() => io.emit("connect")).to.throwException(
|
||||
/"connect" is a reserved event name/
|
||||
);
|
||||
});
|
||||
|
||||
it("should close a client without namespace", (done) => {
|
||||
const io = new Server(0, {
|
||||
connectTimeout: 10,
|
||||
});
|
||||
|
||||
const socket = createClient(io);
|
||||
|
||||
// @ts-ignore
|
||||
socket.io.engine.write = () => {}; // prevent the client from sending a CONNECT packet
|
||||
|
||||
socket.on("disconnect", successFn(done, io, socket));
|
||||
});
|
||||
|
||||
it("should exclude a specific socket when emitting", (done) => {
|
||||
const io = new Server(0);
|
||||
|
||||
const socket1 = createClient(io, "/");
|
||||
const socket2 = createClient(io, "/");
|
||||
|
||||
socket2.on("a", () => {
|
||||
done(new Error("should not happen"));
|
||||
});
|
||||
socket1.on("a", successFn(done, io, socket1, socket2));
|
||||
|
||||
socket2.on("connect", () => {
|
||||
io.except(socket2.id).emit("a");
|
||||
});
|
||||
});
|
||||
|
||||
it("should exclude a specific socket when emitting (in a namespace)", (done) => {
|
||||
const io = new Server(0);
|
||||
|
||||
const nsp = io.of("/nsp");
|
||||
|
||||
const socket1 = createClient(io, "/nsp");
|
||||
const socket2 = createClient(io, "/nsp");
|
||||
|
||||
socket2.on("a", () => {
|
||||
done(new Error("not"));
|
||||
});
|
||||
socket1.on("a", successFn(done, io, socket1, socket2));
|
||||
|
||||
socket2.on("connect", () => {
|
||||
nsp.except(socket2.id).emit("a");
|
||||
});
|
||||
});
|
||||
|
||||
it("should exclude a specific room when emitting", (done) => {
|
||||
const io = new Server(0);
|
||||
|
||||
const nsp = io.of("/nsp");
|
||||
|
||||
const socket1 = createClient(io, "/nsp");
|
||||
const socket2 = createClient(io, "/nsp");
|
||||
|
||||
socket1.on("a", successFn(done, io, socket1, socket2));
|
||||
socket2.on("a", () => {
|
||||
done(new Error("not"));
|
||||
});
|
||||
|
||||
nsp.on("connection", (socket) => {
|
||||
socket.on("broadcast", () => {
|
||||
socket.join("room1");
|
||||
nsp.except("room1").emit("a");
|
||||
});
|
||||
});
|
||||
|
||||
socket2.emit("broadcast");
|
||||
});
|
||||
|
||||
it("should emit an 'new_namespace' event", (done) => {
|
||||
const io = new Server();
|
||||
|
||||
io.on("new_namespace", (namespace) => {
|
||||
expect(namespace.name).to.eql("/nsp");
|
||||
done();
|
||||
});
|
||||
|
||||
io.of("/nsp");
|
||||
});
|
||||
|
||||
describe("dynamic namespaces", () => {
|
||||
it("should allow connections to dynamic namespaces with a regex", (done) => {
|
||||
const io = new Server(0);
|
||||
const socket = createClient(io, "/dynamic-101");
|
||||
const partialDone = createPartialDone(4, successFn(done, io, socket));
|
||||
|
||||
let dynamicNsp = io
|
||||
.of(/^\/dynamic-\d+$/)
|
||||
.on("connect", (socket) => {
|
||||
expect(socket.nsp.name).to.be("/dynamic-101");
|
||||
dynamicNsp.emit("hello", 1, "2", { 3: "4" });
|
||||
partialDone();
|
||||
})
|
||||
.use((socket, next) => {
|
||||
next();
|
||||
partialDone();
|
||||
});
|
||||
socket.on("connect_error", (err) => {
|
||||
expect().fail();
|
||||
});
|
||||
socket.on("connect", () => {
|
||||
partialDone();
|
||||
});
|
||||
socket.on("hello", (a, b, c) => {
|
||||
expect(a).to.eql(1);
|
||||
expect(b).to.eql("2");
|
||||
expect(c).to.eql({ 3: "4" });
|
||||
partialDone();
|
||||
});
|
||||
});
|
||||
|
||||
it("should allow connections to dynamic namespaces with a function", (done) => {
|
||||
const io = new Server(0);
|
||||
const socket = createClient(io, "/dynamic-101");
|
||||
|
||||
io.of((name, query, next) => next(null, "/dynamic-101" === name));
|
||||
socket.on("connect", successFn(done, io, socket));
|
||||
});
|
||||
|
||||
it("should disallow connections when no dynamic namespace matches", (done) => {
|
||||
const io = new Server(0);
|
||||
const socket = createClient(io, "/abc");
|
||||
io.of(/^\/dynamic-\d+$/);
|
||||
io.of((name, query, next) => next(null, "/dynamic-101" === name));
|
||||
|
||||
socket.on("connect_error", (err) => {
|
||||
expect(err.message).to.be("Invalid namespace");
|
||||
success(done, io, socket);
|
||||
});
|
||||
});
|
||||
|
||||
it("should emit an 'new_namespace' event for a dynamic namespace", (done) => {
|
||||
const io = new Server(0);
|
||||
io.of(/^\/dynamic-\d+$/);
|
||||
const socket = createClient(io, "/dynamic-101");
|
||||
|
||||
io.on("new_namespace", (namespace) => {
|
||||
expect(namespace.name).to.be("/dynamic-101");
|
||||
|
||||
success(done, io, socket);
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle race conditions with dynamic namespaces (#4136)", (done) => {
|
||||
const io = new Server(0);
|
||||
const counters = {
|
||||
connected: 0,
|
||||
created: 0,
|
||||
events: 0,
|
||||
};
|
||||
const buffer: Function[] = [];
|
||||
io.on("new_namespace", (namespace) => {
|
||||
counters.created++;
|
||||
});
|
||||
|
||||
const handler = () => {
|
||||
if (++counters.events === 2) {
|
||||
expect(counters.created).to.equal(1);
|
||||
success(done, io, one, two);
|
||||
}
|
||||
};
|
||||
|
||||
io.of((name, query, next) => {
|
||||
buffer.push(next);
|
||||
if (buffer.length === 2) {
|
||||
buffer.forEach((next) => next(null, true));
|
||||
}
|
||||
}).on("connection", (socket) => {
|
||||
if (++counters.connected === 2) {
|
||||
io.of("/dynamic-101").emit("message");
|
||||
}
|
||||
});
|
||||
|
||||
let one = createClient(io, "/dynamic-101");
|
||||
let two = createClient(io, "/dynamic-101");
|
||||
one.on("message", handler);
|
||||
two.on("message", handler);
|
||||
});
|
||||
});
|
||||
});
|
||||
170
test/server-attachment.ts
Normal file
170
test/server-attachment.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
import { Server } from "..";
|
||||
import { createServer } from "http";
|
||||
import request from "supertest";
|
||||
import expect from "expect.js";
|
||||
import { getPort, successFn } from "./support/util";
|
||||
|
||||
describe("server attachment", () => {
|
||||
describe("http.Server", () => {
|
||||
const clientVersion = require("socket.io-client/package.json").version;
|
||||
|
||||
const testSource = (filename) => (done) => {
|
||||
const srv = createServer();
|
||||
new Server(srv);
|
||||
request(srv)
|
||||
.get("/socket.io/" + filename)
|
||||
.buffer(true)
|
||||
.end((err, res) => {
|
||||
if (err) return done(err);
|
||||
expect(res.headers["content-type"]).to.be("application/javascript");
|
||||
expect(res.headers.etag).to.be('"' + clientVersion + '"');
|
||||
expect(res.headers["x-sourcemap"]).to.be(undefined);
|
||||
expect(res.text).to.match(/engine\.io/);
|
||||
expect(res.status).to.be(200);
|
||||
done();
|
||||
});
|
||||
};
|
||||
|
||||
const testSourceMap = (filename) => (done) => {
|
||||
const srv = createServer();
|
||||
new Server(srv);
|
||||
request(srv)
|
||||
.get("/socket.io/" + filename)
|
||||
.buffer(true)
|
||||
.end((err, res) => {
|
||||
if (err) return done(err);
|
||||
expect(res.headers["content-type"]).to.be("application/json");
|
||||
expect(res.headers.etag).to.be('"' + clientVersion + '"');
|
||||
expect(res.text).to.match(/engine\.io/);
|
||||
expect(res.status).to.be(200);
|
||||
done();
|
||||
});
|
||||
};
|
||||
|
||||
it("should serve client", testSource("socket.io.js"));
|
||||
it(
|
||||
"should serve client with query string",
|
||||
testSource("socket.io.js?buster=" + Date.now())
|
||||
);
|
||||
it("should serve source map", testSourceMap("socket.io.js.map"));
|
||||
it("should serve client (min)", testSource("socket.io.min.js"));
|
||||
|
||||
it("should serve source map (min)", testSourceMap("socket.io.min.js.map"));
|
||||
|
||||
it("should serve client (gzip)", (done) => {
|
||||
const srv = createServer();
|
||||
new Server(srv);
|
||||
request(srv)
|
||||
.get("/socket.io/socket.io.js")
|
||||
.set("accept-encoding", "gzip,br,deflate")
|
||||
.buffer(true)
|
||||
.end((err, res) => {
|
||||
if (err) return done(err);
|
||||
expect(res.headers["content-encoding"]).to.be("gzip");
|
||||
expect(res.status).to.be(200);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it(
|
||||
"should serve bundle with msgpack parser",
|
||||
testSource("socket.io.msgpack.min.js")
|
||||
);
|
||||
|
||||
it(
|
||||
"should serve source map for bundle with msgpack parser",
|
||||
testSourceMap("socket.io.msgpack.min.js.map")
|
||||
);
|
||||
|
||||
it("should serve the ESM bundle", testSource("socket.io.esm.min.js"));
|
||||
|
||||
it(
|
||||
"should serve the source map for the ESM bundle",
|
||||
testSourceMap("socket.io.esm.min.js.map")
|
||||
);
|
||||
|
||||
it("should handle 304", (done) => {
|
||||
const srv = createServer();
|
||||
new Server(srv);
|
||||
request(srv)
|
||||
.get("/socket.io/socket.io.js")
|
||||
.set("If-None-Match", '"' + clientVersion + '"')
|
||||
.end((err, res) => {
|
||||
if (err) return done(err);
|
||||
expect(res.statusCode).to.be(304);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle 304", (done) => {
|
||||
const srv = createServer();
|
||||
new Server(srv);
|
||||
request(srv)
|
||||
.get("/socket.io/socket.io.js")
|
||||
.set("If-None-Match", 'W/"' + clientVersion + '"')
|
||||
.end((err, res) => {
|
||||
if (err) return done(err);
|
||||
expect(res.statusCode).to.be(304);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should not serve static files", (done) => {
|
||||
const srv = createServer();
|
||||
new Server(srv, { serveClient: false });
|
||||
request(srv).get("/socket.io/socket.io.js").expect(400, done);
|
||||
});
|
||||
|
||||
it("should work with #attach", (done) => {
|
||||
const srv = createServer((req, res) => {
|
||||
res.writeHead(404);
|
||||
res.end();
|
||||
});
|
||||
const sockets = new Server();
|
||||
sockets.attach(srv);
|
||||
request(srv)
|
||||
.get("/socket.io/socket.io.js")
|
||||
.end((err, res) => {
|
||||
if (err) return done(err);
|
||||
expect(res.status).to.be(200);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should work with #attach (and merge options)", () => {
|
||||
const srv = createServer((req, res) => {
|
||||
res.writeHead(404);
|
||||
res.end();
|
||||
});
|
||||
const server = new Server({
|
||||
pingTimeout: 6000,
|
||||
});
|
||||
server.attach(srv, {
|
||||
pingInterval: 24000,
|
||||
});
|
||||
// @ts-ignore
|
||||
expect(server.eio.opts.pingTimeout).to.eql(6000);
|
||||
// @ts-ignore
|
||||
expect(server.eio.opts.pingInterval).to.eql(24000);
|
||||
server.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe("port", () => {
|
||||
it("should be bound", (done) => {
|
||||
const io = new Server(0);
|
||||
|
||||
request(`http://localhost:${getPort(io)}`)
|
||||
.get("/socket.io/socket.io.js")
|
||||
.expect(200, successFn(done, io));
|
||||
});
|
||||
|
||||
it("with listen", (done) => {
|
||||
const io = new Server().listen(0);
|
||||
|
||||
request(`http://localhost:${getPort(io)}`)
|
||||
.get("/socket.io/socket.io.js")
|
||||
.expect(200, successFn(done, io));
|
||||
});
|
||||
});
|
||||
});
|
||||
60
test/socket-middleware.ts
Normal file
60
test/socket-middleware.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { Server } from "..";
|
||||
import expect from "expect.js";
|
||||
import { success, createClient } from "./support/util";
|
||||
|
||||
describe("socket middleware", () => {
|
||||
it("should call functions", (done) => {
|
||||
const io = new Server(0);
|
||||
const clientSocket = createClient(io, "/", { multiplex: false });
|
||||
|
||||
clientSocket.emit("join", "woot");
|
||||
|
||||
let run = 0;
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
socket.use((event, next) => {
|
||||
expect(event).to.eql(["join", "woot"]);
|
||||
event.unshift("wrap");
|
||||
run++;
|
||||
next();
|
||||
});
|
||||
socket.use((event, next) => {
|
||||
expect(event).to.eql(["wrap", "join", "woot"]);
|
||||
run++;
|
||||
next();
|
||||
});
|
||||
socket.on("wrap", (data1, data2) => {
|
||||
expect(data1).to.be("join");
|
||||
expect(data2).to.be("woot");
|
||||
expect(run).to.be(2);
|
||||
|
||||
success(done, io, clientSocket);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should pass errors", (done) => {
|
||||
const io = new Server(0);
|
||||
const clientSocket = createClient(io, "/", { multiplex: false });
|
||||
|
||||
clientSocket.emit("join", "woot");
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
socket.use((event, next) => {
|
||||
next(new Error("Authentication error"));
|
||||
});
|
||||
socket.use((event, next) => {
|
||||
done(new Error("should not happen"));
|
||||
});
|
||||
socket.on("join", () => {
|
||||
done(new Error("should not happen"));
|
||||
});
|
||||
socket.on("error", (err) => {
|
||||
expect(err).to.be.an(Error);
|
||||
expect(err.message).to.eql("Authentication error");
|
||||
|
||||
success(done, io, clientSocket);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
2870
test/socket.io.ts
2870
test/socket.io.ts
File diff suppressed because it is too large
Load Diff
1043
test/socket.ts
Normal file
1043
test/socket.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,7 @@ const i = expect.stringify;
|
||||
// add support for Set/Map
|
||||
const contain = expect.Assertion.prototype.contain;
|
||||
expect.Assertion.prototype.contain = function (...args) {
|
||||
if (typeof this.obj === "object") {
|
||||
if (this.obj instanceof Set || this.obj instanceof Map) {
|
||||
args.forEach((obj) => {
|
||||
this.assert(
|
||||
this.obj.has(obj),
|
||||
@@ -31,16 +31,50 @@ expect.Assertion.prototype.contain = function (...args) {
|
||||
|
||||
export function createClient(
|
||||
io: Server,
|
||||
nsp: string,
|
||||
opts?: ManagerOptions & SocketOptions
|
||||
nsp: string = "/",
|
||||
opts?: Partial<ManagerOptions & SocketOptions>
|
||||
): ClientSocket {
|
||||
// @ts-ignore
|
||||
const port = io.httpServer.address().port;
|
||||
return ioc(`http://localhost:${port}${nsp}`, opts);
|
||||
}
|
||||
|
||||
export function success(done: Function, io: Server, client: ClientSocket) {
|
||||
export function success(
|
||||
done: Function,
|
||||
io: Server,
|
||||
...clients: ClientSocket[]
|
||||
) {
|
||||
io.close();
|
||||
client.disconnect();
|
||||
clients.forEach((client) => client.disconnect());
|
||||
done();
|
||||
}
|
||||
|
||||
export function successFn(
|
||||
done: () => void,
|
||||
sio: Server,
|
||||
...clientSockets: ClientSocket[]
|
||||
) {
|
||||
return () => success(done, sio, ...clientSockets);
|
||||
}
|
||||
|
||||
export function getPort(io: Server): number {
|
||||
// @ts-ignore
|
||||
return io.httpServer.address().port;
|
||||
}
|
||||
|
||||
export function createPartialDone(count: number, done: (err?: Error) => void) {
|
||||
let i = 0;
|
||||
return () => {
|
||||
if (++i === count) {
|
||||
done();
|
||||
} else if (i > count) {
|
||||
done(new Error(`partialDone() called too many times: ${i} > ${count}`));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function waitFor(emitter, event) {
|
||||
return new Promise((resolve) => {
|
||||
emitter.once(event, resolve);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,26 +6,10 @@ import expect from "expect.js";
|
||||
import type { AddressInfo } from "net";
|
||||
|
||||
import "./support/util";
|
||||
import { createPartialDone } from "./support/util";
|
||||
|
||||
const SOCKETS_COUNT = 3;
|
||||
|
||||
const createPartialDone = (
|
||||
count: number,
|
||||
done: () => void,
|
||||
callback?: () => void
|
||||
) => {
|
||||
let i = 0;
|
||||
return () => {
|
||||
i++;
|
||||
if (i === count) {
|
||||
done();
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
class DummyAdapter extends Adapter {
|
||||
fetchSockets(opts: BroadcastOptions): Promise<any[]> {
|
||||
return Promise.resolve([
|
||||
@@ -49,7 +33,7 @@ class DummyAdapter extends Adapter {
|
||||
}
|
||||
}
|
||||
|
||||
describe("socket.io", () => {
|
||||
describe("utility methods", () => {
|
||||
let io: Server, clientSockets: ClientSocket[], serverSockets: Socket[];
|
||||
beforeEach((done) => {
|
||||
const srv = createServer();
|
||||
@@ -59,7 +43,12 @@ describe("socket.io", () => {
|
||||
|
||||
clientSockets = [];
|
||||
for (let i = 0; i < SOCKETS_COUNT; i++) {
|
||||
clientSockets.push(ioc(`http://localhost:${port}`));
|
||||
clientSockets.push(
|
||||
ioc(`http://localhost:${port}`, {
|
||||
// FIXME needed so that clients are properly closed
|
||||
transports: ["websocket"],
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
serverSockets = [];
|
||||
@@ -77,100 +66,99 @@ describe("socket.io", () => {
|
||||
clientSockets.forEach((socket) => socket.disconnect());
|
||||
});
|
||||
|
||||
describe("utility methods", () => {
|
||||
describe("fetchSockets", () => {
|
||||
it("returns all socket instances", async () => {
|
||||
const sockets = await io.fetchSockets();
|
||||
expect(sockets.length).to.eql(3);
|
||||
});
|
||||
describe("fetchSockets", () => {
|
||||
it("returns all socket instances", async () => {
|
||||
const sockets = await io.fetchSockets();
|
||||
expect(sockets.length).to.eql(3);
|
||||
});
|
||||
|
||||
it("returns all socket instances in the given room", async () => {
|
||||
serverSockets[0].join(["room1", "room2"]);
|
||||
serverSockets[1].join("room1");
|
||||
serverSockets[2].join("room2");
|
||||
const sockets = await io.in("room1").fetchSockets();
|
||||
expect(sockets.length).to.eql(2);
|
||||
});
|
||||
it("returns all socket instances in the given room", async () => {
|
||||
serverSockets[0].join(["room1", "room2"]);
|
||||
serverSockets[1].join("room1");
|
||||
serverSockets[2].join("room2");
|
||||
const sockets = await io.in("room1").fetchSockets();
|
||||
expect(sockets.length).to.eql(2);
|
||||
});
|
||||
|
||||
it("works with a custom adapter", async () => {
|
||||
io.adapter(DummyAdapter);
|
||||
const sockets = await io.fetchSockets();
|
||||
expect(sockets.length).to.eql(1);
|
||||
const remoteSocket = sockets[0];
|
||||
expect(remoteSocket.id).to.eql("42");
|
||||
expect(remoteSocket.rooms).to.contain("42", "room1");
|
||||
expect(remoteSocket.data).to.eql({ username: "john" });
|
||||
it("works with a custom adapter", async () => {
|
||||
io.adapter(DummyAdapter);
|
||||
const sockets = await io.fetchSockets();
|
||||
expect(sockets.length).to.eql(1);
|
||||
const remoteSocket = sockets[0];
|
||||
expect(remoteSocket.id).to.eql("42");
|
||||
expect(remoteSocket.rooms).to.contain("42", "room1");
|
||||
expect(remoteSocket.data).to.eql({ username: "john" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("socketsJoin", () => {
|
||||
it("makes all socket instances join the given room", () => {
|
||||
io.socketsJoin("room1");
|
||||
serverSockets.forEach((socket) => {
|
||||
expect(socket.rooms).to.contain("room1");
|
||||
});
|
||||
});
|
||||
|
||||
describe("socketsJoin", () => {
|
||||
it("makes all socket instances join the given room", () => {
|
||||
io.socketsJoin("room1");
|
||||
serverSockets.forEach((socket) => {
|
||||
expect(socket.rooms).to.contain("room1");
|
||||
});
|
||||
});
|
||||
it("makes all socket instances in a room join the given room", () => {
|
||||
serverSockets[0].join(["room1", "room2"]);
|
||||
serverSockets[1].join("room1");
|
||||
serverSockets[2].join("room2");
|
||||
io.in("room1").socketsJoin("room3");
|
||||
expect(serverSockets[0].rooms).to.contain("room3");
|
||||
expect(serverSockets[1].rooms).to.contain("room3");
|
||||
expect(serverSockets[2].rooms).to.not.contain("room3");
|
||||
});
|
||||
});
|
||||
|
||||
it("makes all socket instances in a room join the given room", () => {
|
||||
serverSockets[0].join(["room1", "room2"]);
|
||||
serverSockets[1].join("room1");
|
||||
serverSockets[2].join("room2");
|
||||
io.in("room1").socketsJoin("room3");
|
||||
expect(serverSockets[0].rooms).to.contain("room3");
|
||||
expect(serverSockets[1].rooms).to.contain("room3");
|
||||
expect(serverSockets[2].rooms).to.not.contain("room3");
|
||||
});
|
||||
describe("socketsLeave", () => {
|
||||
it("makes all socket instances leave the given room", () => {
|
||||
serverSockets[0].join(["room1", "room2"]);
|
||||
serverSockets[1].join("room1");
|
||||
serverSockets[2].join("room2");
|
||||
io.socketsLeave("room1");
|
||||
expect(serverSockets[0].rooms).to.contain("room2");
|
||||
expect(serverSockets[0].rooms).to.not.contain("room1");
|
||||
expect(serverSockets[1].rooms).to.not.contain("room1");
|
||||
});
|
||||
|
||||
describe("socketsLeave", () => {
|
||||
it("makes all socket instances leave the given room", () => {
|
||||
serverSockets[0].join(["room1", "room2"]);
|
||||
serverSockets[1].join("room1");
|
||||
serverSockets[2].join("room2");
|
||||
io.socketsLeave("room1");
|
||||
expect(serverSockets[0].rooms).to.contain("room2");
|
||||
expect(serverSockets[0].rooms).to.not.contain("room1");
|
||||
expect(serverSockets[1].rooms).to.not.contain("room1");
|
||||
});
|
||||
it("makes all socket instances in a room leave the given room", () => {
|
||||
serverSockets[0].join(["room1", "room2"]);
|
||||
serverSockets[1].join("room1");
|
||||
serverSockets[2].join("room2");
|
||||
io.in("room2").socketsLeave("room1");
|
||||
expect(serverSockets[0].rooms).to.contain("room2");
|
||||
expect(serverSockets[0].rooms).to.not.contain("room1");
|
||||
expect(serverSockets[1].rooms).to.contain("room1");
|
||||
});
|
||||
});
|
||||
|
||||
it("makes all socket instances in a room leave the given room", () => {
|
||||
serverSockets[0].join(["room1", "room2"]);
|
||||
serverSockets[1].join("room1");
|
||||
serverSockets[2].join("room2");
|
||||
io.in("room2").socketsLeave("room1");
|
||||
expect(serverSockets[0].rooms).to.contain("room2");
|
||||
expect(serverSockets[0].rooms).to.not.contain("room1");
|
||||
expect(serverSockets[1].rooms).to.contain("room1");
|
||||
});
|
||||
describe("disconnectSockets", () => {
|
||||
it("makes all socket instances disconnect", (done) => {
|
||||
io.disconnectSockets(true);
|
||||
|
||||
const partialDone = createPartialDone(3, done);
|
||||
|
||||
clientSockets[0].on("disconnect", partialDone);
|
||||
clientSockets[1].on("disconnect", partialDone);
|
||||
clientSockets[2].on("disconnect", partialDone);
|
||||
});
|
||||
|
||||
describe("disconnectSockets", () => {
|
||||
it("makes all socket instances disconnect", (done) => {
|
||||
io.disconnectSockets(true);
|
||||
it("makes all socket instances in a room disconnect", (done) => {
|
||||
serverSockets[0].join(["room1", "room2"]);
|
||||
serverSockets[1].join("room1");
|
||||
serverSockets[2].join("room2");
|
||||
io.in("room2").disconnectSockets(true);
|
||||
|
||||
const partialDone = createPartialDone(3, done);
|
||||
|
||||
clientSockets[0].on("disconnect", partialDone);
|
||||
clientSockets[1].on("disconnect", partialDone);
|
||||
clientSockets[2].on("disconnect", partialDone);
|
||||
const partialDone = createPartialDone(2, () => {
|
||||
clientSockets[1].off("disconnect");
|
||||
done();
|
||||
});
|
||||
|
||||
it("makes all socket instances in a room disconnect", (done) => {
|
||||
serverSockets[0].join(["room1", "room2"]);
|
||||
serverSockets[1].join("room1");
|
||||
serverSockets[2].join("room2");
|
||||
io.in("room2").disconnectSockets(true);
|
||||
|
||||
const partialDone = createPartialDone(2, done, () => {
|
||||
clientSockets[1].off("disconnect");
|
||||
});
|
||||
|
||||
clientSockets[0].on("disconnect", partialDone);
|
||||
clientSockets[1].on("disconnect", () => {
|
||||
done(new Error("should not happen"));
|
||||
});
|
||||
clientSockets[2].on("disconnect", partialDone);
|
||||
clientSockets[0].on("disconnect", partialDone);
|
||||
clientSockets[1].on("disconnect", () => {
|
||||
done(new Error("should not happen"));
|
||||
});
|
||||
clientSockets[2].on("disconnect", partialDone);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
22
test/uws.ts
22
test/uws.ts
@@ -1,4 +1,8 @@
|
||||
import { App, us_socket_local_port } from "uWebSockets.js";
|
||||
import {
|
||||
App,
|
||||
us_socket_local_port,
|
||||
us_listen_socket_close,
|
||||
} from "uWebSockets.js";
|
||||
import { Server } from "..";
|
||||
import { io as ioc, Socket as ClientSocket } from "socket.io-client";
|
||||
import request from "supertest";
|
||||
@@ -19,6 +23,7 @@ const shouldNotHappen = (done) => () => done(new Error("should not happen"));
|
||||
|
||||
describe("socket.io with uWebSocket.js-based engine", () => {
|
||||
let io: Server,
|
||||
uwsSocket: any,
|
||||
port: number,
|
||||
client: ClientSocket,
|
||||
clientWSOnly: ClientSocket,
|
||||
@@ -33,6 +38,7 @@ describe("socket.io with uWebSocket.js-based engine", () => {
|
||||
io.of("/custom");
|
||||
|
||||
app.listen(0, (listenSocket) => {
|
||||
uwsSocket = listenSocket;
|
||||
port = us_socket_local_port(listenSocket);
|
||||
|
||||
client = ioc(`http://localhost:${port}`);
|
||||
@@ -46,12 +52,16 @@ describe("socket.io with uWebSocket.js-based engine", () => {
|
||||
});
|
||||
|
||||
const partialDone = createPartialDone(done, 4);
|
||||
io.on("connection", partialDone);
|
||||
io.of("/custom").on("connection", partialDone);
|
||||
client.on("connect", partialDone);
|
||||
clientWSOnly.on("connect", partialDone);
|
||||
clientPollingOnly.on("connect", partialDone);
|
||||
clientCustomNamespace.on("connect", partialDone);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
io.close();
|
||||
us_listen_socket_close(uwsSocket);
|
||||
|
||||
client.disconnect();
|
||||
clientWSOnly.disconnect();
|
||||
clientPollingOnly.disconnect();
|
||||
@@ -176,6 +186,12 @@ describe("socket.io with uWebSocket.js-based engine", () => {
|
||||
io.to("room1").emit("hello");
|
||||
});
|
||||
|
||||
it("should not crash when socket is disconnected before the upgrade", (done) => {
|
||||
client.on("disconnect", () => done());
|
||||
|
||||
io.of("/").sockets.get(client.id)!.disconnect();
|
||||
});
|
||||
|
||||
it("should serve static files", (done) => {
|
||||
const clientVersion = require("socket.io-client/package.json").version;
|
||||
|
||||
|
||||
64
test/v2-compatibility.ts
Normal file
64
test/v2-compatibility.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { Server, Socket } from "..";
|
||||
import expect from "expect.js";
|
||||
import { success, getPort, waitFor } from "./support/util";
|
||||
import * as io_v2 from "socket.io-client-v2";
|
||||
|
||||
describe("v2 compatibility", () => {
|
||||
it("should connect if `allowEIO3` is true", (done) => {
|
||||
const io = new Server(0, {
|
||||
allowEIO3: true,
|
||||
});
|
||||
|
||||
const clientSocket = io_v2.connect(`http://localhost:${getPort(io)}`, {
|
||||
multiplex: false,
|
||||
});
|
||||
|
||||
Promise.all([
|
||||
waitFor(io, "connection"),
|
||||
waitFor(clientSocket, "connect"),
|
||||
]).then(([socket]) => {
|
||||
expect((socket as Socket).id).to.eql(clientSocket.id);
|
||||
|
||||
success(done, io, clientSocket);
|
||||
});
|
||||
});
|
||||
|
||||
it("should be able to connect to a namespace with a query", (done) => {
|
||||
const io = new Server(0, {
|
||||
allowEIO3: true,
|
||||
});
|
||||
|
||||
const clientSocket = io_v2.connect(
|
||||
`http://localhost:${getPort(io)}/the-namespace`,
|
||||
{
|
||||
multiplex: false,
|
||||
}
|
||||
);
|
||||
clientSocket.query = { test: "123" };
|
||||
|
||||
Promise.all([
|
||||
waitFor(io.of("/the-namespace"), "connection"),
|
||||
waitFor(clientSocket, "connect"),
|
||||
]).then(([socket]) => {
|
||||
expect((socket as Socket).handshake.auth).to.eql({ test: "123" });
|
||||
|
||||
success(done, io, clientSocket);
|
||||
});
|
||||
});
|
||||
|
||||
it("should not connect if `allowEIO3` is false (default)", (done) => {
|
||||
const io = new Server(0);
|
||||
|
||||
const clientSocket = io_v2.connect(`http://localhost:${getPort(io)}`, {
|
||||
multiplex: false,
|
||||
});
|
||||
|
||||
clientSocket.on("connect", () => {
|
||||
done(new Error("should not happen"));
|
||||
});
|
||||
|
||||
clientSocket.on("connect_error", () => {
|
||||
success(done, io, clientSocket);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user