mirror of
https://github.com/socketio/socket.io.git
synced 2026-01-11 16:08:24 -05:00
Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c82a4bdf1f | ||
|
|
770ee5949f | ||
|
|
3bf5d92735 | ||
|
|
fc82e44f73 | ||
|
|
c840bad43a | ||
|
|
f2b8de7191 | ||
|
|
51784d0305 | ||
|
|
c196689545 | ||
|
|
7a70f63499 | ||
|
|
e5897dd7dc | ||
|
|
2071a66c5a | ||
|
|
0f11c4745f | ||
|
|
b839a3b400 | ||
|
|
f0ed42f18c | ||
|
|
b7213e71e4 | ||
|
|
2da82103d2 | ||
|
|
02b0f73e2c | ||
|
|
c0d8c5ab23 | ||
|
|
fe8730ca0f | ||
|
|
ed8483da4d | ||
|
|
9d86397243 | ||
|
|
44e20ba5bf | ||
|
|
ccc5ec39a8 | ||
|
|
0ef2a4d02c | ||
|
|
95810aa62d | ||
|
|
60edecb3bd | ||
|
|
eb5fdbd03e | ||
|
|
4974e9077c | ||
|
|
033c5d399a | ||
|
|
7a74b66872 | ||
|
|
dc81fcf461 | ||
|
|
c100b7b61c | ||
|
|
f03eeca39a | ||
|
|
d8cc8aef7e | ||
|
|
ccfd8caba6 | ||
|
|
24fee27ba3 | ||
|
|
310f8557a7 | ||
|
|
dbd2a07cda | ||
|
|
94e27cd072 | ||
|
|
a4dffc6527 | ||
|
|
7c44893d78 | ||
|
|
b833f918c8 | ||
|
|
24d8d1f67f | ||
|
|
6f2a50b932 | ||
|
|
1633150b2b | ||
|
|
0cb6ac95b4 | ||
|
|
a2cf2486c3 | ||
|
|
995f38f4cc | ||
|
|
891b1870e9 | ||
|
|
b84ed1e41c | ||
|
|
fb6b0efec9 | ||
|
|
95d9e4a42f | ||
|
|
499c89250d | ||
|
|
93cce05fb3 | ||
|
|
dc381b72c6 |
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -12,12 +12,12 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [10.x, 12.x, 14.x, 15.x]
|
||||
node-version: [12, 14, 16]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: npm ci
|
||||
|
||||
112
CHANGELOG.md
112
CHANGELOG.md
@@ -1,3 +1,115 @@
|
||||
## [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)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* only set 'connected' to true after middleware execution ([02b0f73](https://github.com/socketio/socket.io/commit/02b0f73e2c64b09c72c5fbf7dc5f059557bdbe50))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add an implementation based on uWebSockets.js ([c0d8c5a](https://github.com/socketio/socket.io/commit/c0d8c5ab234d0d2bef0d0dec472973cc9662f647))
|
||||
* add timeout feature ([f0ed42f](https://github.com/socketio/socket.io/commit/f0ed42f18cabef20ad976aeec37077b6bf3837a5))
|
||||
* add type information to `socket.data` ([#4159](https://github.com/socketio/socket.io/issues/4159)) ([fe8730c](https://github.com/socketio/socket.io/commit/fe8730ca0f15bc92d5de81cf934c89c76d6af329))
|
||||
|
||||
|
||||
|
||||
## [4.3.2](https://github.com/socketio/socket.io/compare/4.3.1...4.3.2) (2021-11-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix race condition in dynamic namespaces ([#4137](https://github.com/socketio/socket.io/issues/4137)) ([9d86397](https://github.com/socketio/socket.io/commit/9d86397243bcbb5775a29d96e5ef03e17148a8e7))
|
||||
|
||||
|
||||
## [4.3.1](https://github.com/socketio/socket.io/compare/4.3.0...4.3.1) (2021-10-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix server attachment ([#4127](https://github.com/socketio/socket.io/issues/4127)) ([0ef2a4d](https://github.com/socketio/socket.io/commit/0ef2a4d02c9350aff163df9cb61aece89c4dac0f))
|
||||
|
||||
|
||||
# [4.3.0](https://github.com/socketio/socket.io/compare/4.2.0...4.3.0) (2021-10-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **typings:** add name field to cookie option ([#4099](https://github.com/socketio/socket.io/issues/4099)) ([033c5d3](https://github.com/socketio/socket.io/commit/033c5d399a2b985afad32c1e4b0c16d764e248cd))
|
||||
* send volatile packets with binary attachments ([dc81fcf](https://github.com/socketio/socket.io/commit/dc81fcf461cfdbb5b34b1a5a96b84373754047d5))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* serve ESM bundle ([60edecb](https://github.com/socketio/socket.io/commit/60edecb3bd33801803cdcba0aefbafa381a2abb3))
|
||||
|
||||
|
||||
# [4.2.0](https://github.com/socketio/socket.io/compare/4.1.3...4.2.0) (2021-08-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **typings:** allow async listener in typed events ([ccfd8ca](https://github.com/socketio/socket.io/commit/ccfd8caba6d38b7ba6c5114bd8179346ed07671c))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* ignore the query string when serving client JavaScript ([#4024](https://github.com/socketio/socket.io/issues/4024)) ([24fee27](https://github.com/socketio/socket.io/commit/24fee27ba36485308f8e995879c10931532c814e))
|
||||
|
||||
|
||||
## [4.1.3](https://github.com/socketio/socket.io/compare/4.1.2...4.1.3) (2021-07-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix io.except() method ([94e27cd](https://github.com/socketio/socket.io/commit/94e27cd072c8a4eeb9636f6ffbb7a21d382f36b0))
|
||||
* remove x-sourcemap header ([a4dffc6](https://github.com/socketio/socket.io/commit/a4dffc6527f412d51a786ae5bf2e9080fe1ca63c))
|
||||
|
||||
|
||||
## [4.1.2](https://github.com/socketio/socket.io/compare/4.1.1...4.1.2) (2021-05-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **typings:** ensure compatibility with TypeScript 3.x ([0cb6ac9](https://github.com/socketio/socket.io/commit/0cb6ac95b49a27483b6f1b6402fa54b35f82e36f))
|
||||
* ensure compatibility with previous versions of the adapter ([a2cf248](https://github.com/socketio/socket.io/commit/a2cf2486c366cb62293101c10520c57f6984a3fc))
|
||||
|
||||
|
||||
## [4.1.1](https://github.com/socketio/socket.io/compare/4.1.0...4.1.1) (2021-05-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **typings:** properly type server-side events ([b84ed1e](https://github.com/socketio/socket.io/commit/b84ed1e41c9053792caf58974c5de9395bfd509f))
|
||||
* **typings:** properly type the adapter attribute ([891b187](https://github.com/socketio/socket.io/commit/891b1870e92d1ec38910f03bb839817e2d6be65a))
|
||||
|
||||
|
||||
# [4.1.0](https://github.com/socketio/socket.io/compare/4.0.2...4.1.0) (2021-05-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add support for inter-server communication ([93cce05](https://github.com/socketio/socket.io/commit/93cce05fb3faf91f21fa71212275c776aa161107))
|
||||
* notify upon namespace creation ([499c892](https://github.com/socketio/socket.io/commit/499c89250d2db1ab7725ab2b74840e188c267c46))
|
||||
* add a "connection_error" event ([7096e98](https://github.com/socketio/engine.io/commit/7096e98a02295a62c8ea2aa56461d4875887092d), from `engine.io`)
|
||||
* add the "initial_headers" and "headers" events ([2527543](https://github.com/socketio/engine.io/commit/252754353a0e88eb036ebb3082e9d6a9a5f497db), from `engine.io`)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* add support for the "wsPreEncoded" writing option ([dc381b7](https://github.com/socketio/socket.io/commit/dc381b72c6b2f8172001dedd84116122e4cc95b3))
|
||||
|
||||
|
||||
## [4.0.2](https://github.com/socketio/socket.io/compare/4.0.1...4.0.2) (2021-05-06)
|
||||
|
||||
|
||||
|
||||
12
Readme.md
12
Readme.md
@@ -1,5 +1,5 @@
|
||||
# socket.io
|
||||
[](https://repl.it/github/socketio/socket.io)
|
||||
[](https://replit.com/@socketio/socketio-minimal-example)
|
||||
[](#backers) [](#sponsors)
|
||||
[](https://github.com/socketio/socket.io/actions)
|
||||
[](https://david-dm.org/socketio/socket.io)
|
||||
@@ -22,7 +22,7 @@ Some implementations in other languages are also available:
|
||||
- [Swift](https://github.com/socketio/socket.io-client-swift)
|
||||
- [Dart](https://github.com/rikulo/socket.io-client-dart)
|
||||
- [Python](https://github.com/miguelgrinberg/python-socketio)
|
||||
- [.Net](https://github.com/Quobject/SocketIoClientDotNet)
|
||||
- [.NET](https://github.com/doghappy/socket.io-client-csharp)
|
||||
|
||||
Its main features are:
|
||||
|
||||
@@ -115,6 +115,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
|
||||
|
||||
7
client-dist/socket.io.esm.min.js
vendored
Normal file
7
client-dist/socket.io.esm.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
client-dist/socket.io.esm.min.js.map
Normal file
1
client-dist/socket.io.esm.min.js.map
Normal file
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
8
client-dist/socket.io.min.js
vendored
8
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
8
client-dist/socket.io.msgpack.min.js
vendored
8
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
@@ -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";
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^8.2.3",
|
||||
"@types/chai": "^4.2.16",
|
||||
"@types/uuid": "^8.3.0",
|
||||
"chai": "^4.3.4",
|
||||
|
||||
@@ -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"
|
||||
|
||||
20
examples/webpack-build-server/index.js
Normal file
20
examples/webpack-build-server/index.js
Normal file
@@ -0,0 +1,20 @@
|
||||
const { Server } = require("socket.io");
|
||||
|
||||
const clientFile = require("./node_modules/socket.io/client-dist/socket.io.min?raw");
|
||||
const clientMap = require("./node_modules/socket.io/client-dist/socket.io.min.js.map?raw");
|
||||
|
||||
Server.sendFile = (filename, req, res) => {
|
||||
res.end(filename.endsWith(".map") ? clientMap : clientFile);
|
||||
};
|
||||
|
||||
const io = new Server();
|
||||
|
||||
io.on("connection", socket => {
|
||||
console.log(`connect ${socket.id}`);
|
||||
|
||||
socket.on("disconnect", (reason) => {
|
||||
console.log(`disconnect ${socket.id} due to ${reason}`);
|
||||
});
|
||||
});
|
||||
|
||||
io.listen(3000);
|
||||
@@ -1,15 +0,0 @@
|
||||
|
||||
const server = require('http').createServer();
|
||||
const io = require('socket.io')(server, {
|
||||
serveClient: false
|
||||
});
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
io.on('connect', onConnect);
|
||||
server.listen(port, () => console.log('server listening on port ' + port));
|
||||
|
||||
function onConnect(socket){
|
||||
console.log('connect ' + socket.id);
|
||||
|
||||
socket.on('disconnect', () => console.log('disconnect ' + socket.id));
|
||||
}
|
||||
@@ -4,13 +4,15 @@
|
||||
"description": "A sample Webpack build (for the server)",
|
||||
"scripts": {
|
||||
"start": "node dist/server.js",
|
||||
"build": "webpack --config ./support/webpack.config.js"
|
||||
"build": "webpack"
|
||||
},
|
||||
"author": "Damien Arrachequesne",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"bufferutil": "^4.0.3",
|
||||
"socket.io": "^4.0.0",
|
||||
"webpack": "~4.43.0",
|
||||
"webpack-cli": "~3.3.11"
|
||||
"utf-8-validate": "^5.0.5",
|
||||
"webpack": "^5.39.0",
|
||||
"webpack-cli": "^4.7.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
|
||||
module.exports = {
|
||||
entry: './lib/index.js',
|
||||
target: 'node',
|
||||
output: {
|
||||
path: require('path').join(__dirname, '../dist'),
|
||||
filename: 'server.js'
|
||||
},
|
||||
mode: 'production'
|
||||
};
|
||||
19
examples/webpack-build-server/webpack.config.js
Normal file
19
examples/webpack-build-server/webpack.config.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const path = require("path");
|
||||
|
||||
module.exports = {
|
||||
entry: "./index.js",
|
||||
target: "node",
|
||||
mode: "production",
|
||||
output: {
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
filename: "index.js",
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
resourceQuery: /raw/,
|
||||
type: "asset/source",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -9,8 +9,9 @@ import type {
|
||||
TypedEventBroadcaster,
|
||||
} from "./typed-events";
|
||||
|
||||
export class BroadcastOperator<EmitEvents extends EventsMap>
|
||||
implements TypedEventBroadcaster<EmitEvents> {
|
||||
export class BroadcastOperator<EmitEvents extends EventsMap, SocketData>
|
||||
implements TypedEventBroadcaster<EmitEvents>
|
||||
{
|
||||
constructor(
|
||||
private readonly adapter: Adapter,
|
||||
private readonly rooms: Set<Room> = new Set<Room>(),
|
||||
@@ -25,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));
|
||||
@@ -47,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);
|
||||
}
|
||||
|
||||
@@ -58,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));
|
||||
@@ -80,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,
|
||||
@@ -98,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,
|
||||
@@ -114,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,
|
||||
@@ -176,7 +181,9 @@ 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,
|
||||
@@ -186,9 +193,12 @@ export class BroadcastOperator<EmitEvents extends EventsMap>
|
||||
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>
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -246,26 +256,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>
|
||||
implements TypedEventBroadcaster<EmitEvents> {
|
||||
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);
|
||||
|
||||
@@ -7,21 +7,42 @@ import type { Namespace } from "./namespace";
|
||||
import type { EventsMap } from "./typed-events";
|
||||
import type { Socket } from "./socket";
|
||||
import type { SocketId } from "socket.io-adapter";
|
||||
import type { Socket as RawSocket } from "engine.io";
|
||||
|
||||
const debug = debugModule("socket.io:client");
|
||||
|
||||
interface WriteOptions {
|
||||
compress?: boolean;
|
||||
volatile?: boolean;
|
||||
preEncoded?: boolean;
|
||||
wsPreEncoded?: string;
|
||||
}
|
||||
|
||||
export class Client<
|
||||
ListenEvents extends EventsMap,
|
||||
EmitEvents extends EventsMap
|
||||
EmitEvents extends EventsMap,
|
||||
ServerSideEvents extends EventsMap,
|
||||
SocketData = any
|
||||
> {
|
||||
public readonly conn;
|
||||
public readonly conn: RawSocket;
|
||||
|
||||
private readonly id: string;
|
||||
private readonly server: Server<ListenEvents, EmitEvents>;
|
||||
private readonly server: Server<
|
||||
ListenEvents,
|
||||
EmitEvents,
|
||||
ServerSideEvents,
|
||||
SocketData
|
||||
>;
|
||||
private readonly encoder: Encoder;
|
||||
private readonly decoder: Decoder;
|
||||
private sockets: Map<SocketId, Socket<ListenEvents, EmitEvents>> = new Map();
|
||||
private nsps: Map<string, Socket<ListenEvents, EmitEvents>> = new Map();
|
||||
private sockets: Map<
|
||||
SocketId,
|
||||
Socket<ListenEvents, EmitEvents, ServerSideEvents, SocketData>
|
||||
> = new Map();
|
||||
private nsps: Map<
|
||||
string,
|
||||
Socket<ListenEvents, EmitEvents, ServerSideEvents, SocketData>
|
||||
> = new Map();
|
||||
private connectTimeout?: NodeJS.Timeout;
|
||||
|
||||
/**
|
||||
@@ -31,7 +52,10 @@ export class Client<
|
||||
* @param conn
|
||||
* @package
|
||||
*/
|
||||
constructor(server: Server<ListenEvents, EmitEvents>, conn: any) {
|
||||
constructor(
|
||||
server: Server<ListenEvents, EmitEvents, ServerSideEvents, SocketData>,
|
||||
conn: any
|
||||
) {
|
||||
this.server = server;
|
||||
this.conn = conn;
|
||||
this.encoder = server.encoder;
|
||||
@@ -92,9 +116,12 @@ export class Client<
|
||||
this.server._checkNamespace(
|
||||
name,
|
||||
auth,
|
||||
(dynamicNspName: Namespace<ListenEvents, EmitEvents> | false) => {
|
||||
(
|
||||
dynamicNspName:
|
||||
| Namespace<ListenEvents, EmitEvents, ServerSideEvents, SocketData>
|
||||
| false
|
||||
) => {
|
||||
if (dynamicNspName) {
|
||||
debug("dynamic namespace %s was created", dynamicNspName);
|
||||
this.doConnect(name, auth);
|
||||
} else {
|
||||
debug("creation of namespace %s was denied", name);
|
||||
@@ -150,7 +177,9 @@ export class Client<
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_remove(socket: Socket<ListenEvents, EmitEvents>): void {
|
||||
_remove(
|
||||
socket: Socket<ListenEvents, EmitEvents, ServerSideEvents, SocketData>
|
||||
): void {
|
||||
if (this.sockets.has(socket.id)) {
|
||||
const nsp = this.sockets.get(socket.id)!.nsp.name;
|
||||
this.sockets.delete(socket.id);
|
||||
@@ -180,30 +209,32 @@ export class Client<
|
||||
* @param {Object} opts
|
||||
* @private
|
||||
*/
|
||||
_packet(packet: Packet, opts?: any): void {
|
||||
opts = opts || {};
|
||||
const self = this;
|
||||
|
||||
// this writes to the actual connection
|
||||
function writeToEngine(encodedPackets: any) {
|
||||
// TODO clarify this.
|
||||
if (opts.volatile && !self.conn.transport.writable) return;
|
||||
for (let i = 0; i < encodedPackets.length; i++) {
|
||||
self.conn.write(encodedPackets[i], { compress: opts.compress });
|
||||
}
|
||||
}
|
||||
|
||||
if ("open" === this.conn.readyState) {
|
||||
debug("writing packet %j", packet);
|
||||
if (!opts.preEncoded) {
|
||||
// not broadcasting, need to encode
|
||||
writeToEngine(this.encoder.encode(packet)); // encode, then write results to engine
|
||||
} else {
|
||||
// a broadcast pre-encodes a packet
|
||||
writeToEngine(packet);
|
||||
}
|
||||
} else {
|
||||
_packet(packet: Packet | any[], opts: WriteOptions = {}): void {
|
||||
if (this.conn.readyState !== "open") {
|
||||
debug("ignoring packet write %j", packet);
|
||||
return;
|
||||
}
|
||||
const encodedPackets = opts.preEncoded
|
||||
? (packet as any[]) // previous versions of the adapter incorrectly used socket.packet() instead of writeToEngine()
|
||||
: this.encoder.encode(packet as Packet);
|
||||
this.writeToEngine(encodedPackets, opts);
|
||||
}
|
||||
|
||||
private writeToEngine(
|
||||
encodedPackets: Array<String | Buffer>,
|
||||
opts: WriteOptions
|
||||
): void {
|
||||
if (opts.volatile && !this.conn.transport.writable) {
|
||||
debug(
|
||||
"volatile packet is discarded since the transport is not currently writable"
|
||||
);
|
||||
return;
|
||||
}
|
||||
const packets = Array.isArray(encodedPackets)
|
||||
? encodedPackets
|
||||
: [encodedPackets];
|
||||
for (const encodedPacket of packets) {
|
||||
this.conn.write(encodedPacket, opts);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
312
lib/index.ts
312
lib/index.ts
@@ -4,143 +4,46 @@ import { createDeflate, createGzip, createBrotliCompress } from "zlib";
|
||||
import accepts = require("accepts");
|
||||
import { pipeline } from "stream";
|
||||
import path = require("path");
|
||||
import engine = require("engine.io");
|
||||
import {
|
||||
attach,
|
||||
Server as Engine,
|
||||
ServerOptions as EngineOptions,
|
||||
AttachOptions,
|
||||
uServer,
|
||||
} from "engine.io";
|
||||
import { Client } from "./client";
|
||||
import { EventEmitter } from "events";
|
||||
import {
|
||||
ExtendedError,
|
||||
Namespace,
|
||||
NamespaceReservedEventsMap,
|
||||
} from "./namespace";
|
||||
import { ExtendedError, Namespace, ServerReservedEventsMap } from "./namespace";
|
||||
import { ParentNamespace } from "./parent-namespace";
|
||||
import { Adapter, Room, SocketId } from "socket.io-adapter";
|
||||
import * as parser from "socket.io-parser";
|
||||
import type { Encoder } from "socket.io-parser";
|
||||
import debugModule from "debug";
|
||||
import { Socket } from "./socket";
|
||||
import type { CookieSerializeOptions } from "cookie";
|
||||
import type { CorsOptions } from "cors";
|
||||
import type { BroadcastOperator, RemoteSocket } from "./broadcast-operator";
|
||||
import {
|
||||
EventsMap,
|
||||
DefaultEventsMap,
|
||||
EventParams,
|
||||
StrictEventEmitter,
|
||||
EventNames,
|
||||
} from "./typed-events";
|
||||
import { patchAdapter, restoreAdapter, serveFile } from "./uws.js";
|
||||
|
||||
const debug = debugModule("socket.io:server");
|
||||
|
||||
const clientVersion = require("../package.json").version;
|
||||
const dotMapRegex = /\.map/;
|
||||
|
||||
type Transport = "polling" | "websocket";
|
||||
type ParentNspNameMatchFn = (
|
||||
name: string,
|
||||
auth: { [key: string]: any },
|
||||
fn: (err: Error | null, success: boolean) => void
|
||||
) => void;
|
||||
|
||||
interface EngineOptions {
|
||||
/**
|
||||
* how many ms without a pong packet to consider the connection closed
|
||||
* @default 20000
|
||||
*/
|
||||
pingTimeout: number;
|
||||
/**
|
||||
* how many ms before sending a new ping packet
|
||||
* @default 25000
|
||||
*/
|
||||
pingInterval: number;
|
||||
/**
|
||||
* how many ms before an uncompleted transport upgrade is cancelled
|
||||
* @default 10000
|
||||
*/
|
||||
upgradeTimeout: number;
|
||||
/**
|
||||
* how many bytes or characters a message can be, before closing the session (to avoid DoS).
|
||||
* @default 1e5 (100 KB)
|
||||
*/
|
||||
maxHttpBufferSize: number;
|
||||
/**
|
||||
* A function that receives a given handshake or upgrade request as its first parameter,
|
||||
* and can decide whether to continue or not. The second argument is a function that needs
|
||||
* to be called with the decided information: fn(err, success), where success is a boolean
|
||||
* value where false means that the request is rejected, and err is an error code.
|
||||
*/
|
||||
allowRequest: (
|
||||
req: http.IncomingMessage,
|
||||
fn: (err: string | null | undefined, success: boolean) => void
|
||||
) => void;
|
||||
/**
|
||||
* the low-level transports that are enabled
|
||||
* @default ["polling", "websocket"]
|
||||
*/
|
||||
transports: Transport[];
|
||||
/**
|
||||
* whether to allow transport upgrades
|
||||
* @default true
|
||||
*/
|
||||
allowUpgrades: boolean;
|
||||
/**
|
||||
* parameters of the WebSocket permessage-deflate extension (see ws module api docs). Set to false to disable.
|
||||
* @default false
|
||||
*/
|
||||
perMessageDeflate: boolean | object;
|
||||
/**
|
||||
* parameters of the http compression for the polling transports (see zlib api docs). Set to false to disable.
|
||||
* @default true
|
||||
*/
|
||||
httpCompression: boolean | object;
|
||||
/**
|
||||
* what WebSocket server implementation to use. Specified module must
|
||||
* conform to the ws interface (see ws module api docs).
|
||||
* An alternative c++ addon is also available by installing eiows module.
|
||||
*
|
||||
* @default `require("ws").Server`
|
||||
*/
|
||||
wsEngine: Function;
|
||||
/**
|
||||
* an optional packet which will be concatenated to the handshake packet emitted by Engine.IO.
|
||||
*/
|
||||
initialPacket: any;
|
||||
/**
|
||||
* configuration of the cookie that contains the client sid to send as part of handshake response headers. This cookie
|
||||
* might be used for sticky-session. Defaults to not sending any cookie.
|
||||
* @default false
|
||||
*/
|
||||
cookie: CookieSerializeOptions | boolean;
|
||||
/**
|
||||
* the options that will be forwarded to the cors module
|
||||
*/
|
||||
cors: CorsOptions;
|
||||
/**
|
||||
* whether to enable compatibility with Socket.IO v2 clients
|
||||
* @default false
|
||||
*/
|
||||
allowEIO3: boolean;
|
||||
}
|
||||
type AdapterConstructor = typeof Adapter | ((nsp: Namespace) => Adapter);
|
||||
|
||||
interface AttachOptions {
|
||||
/**
|
||||
* name of the path to capture
|
||||
* @default "/engine.io"
|
||||
*/
|
||||
path: string;
|
||||
/**
|
||||
* destroy unhandled upgrade requests
|
||||
* @default true
|
||||
*/
|
||||
destroyUpgrade: boolean;
|
||||
/**
|
||||
* milliseconds after which unhandled requests are ended
|
||||
* @default 1000
|
||||
*/
|
||||
destroyUpgradeTimeout: number;
|
||||
}
|
||||
|
||||
interface EngineAttachOptions extends EngineOptions, AttachOptions {}
|
||||
|
||||
interface ServerOptions extends EngineAttachOptions {
|
||||
interface ServerOptions extends EngineOptions, AttachOptions {
|
||||
/**
|
||||
* name of the path to capture
|
||||
* @default "/socket.io"
|
||||
@@ -155,7 +58,7 @@ interface ServerOptions extends EngineAttachOptions {
|
||||
* the adapter to use
|
||||
* @default the in-memory adapter (https://github.com/socketio/socket.io-adapter)
|
||||
*/
|
||||
adapter: any;
|
||||
adapter: AdapterConstructor;
|
||||
/**
|
||||
* the parser to use
|
||||
* @default the default parser (https://github.com/socketio/socket.io-parser)
|
||||
@@ -170,13 +73,25 @@ interface ServerOptions extends EngineAttachOptions {
|
||||
|
||||
export class Server<
|
||||
ListenEvents extends EventsMap = DefaultEventsMap,
|
||||
EmitEvents extends EventsMap = ListenEvents
|
||||
EmitEvents extends EventsMap = ListenEvents,
|
||||
ServerSideEvents extends EventsMap = DefaultEventsMap,
|
||||
SocketData = any
|
||||
> extends StrictEventEmitter<
|
||||
{},
|
||||
ServerSideEvents,
|
||||
EmitEvents,
|
||||
NamespaceReservedEventsMap<ListenEvents, EmitEvents>
|
||||
ServerReservedEventsMap<
|
||||
ListenEvents,
|
||||
EmitEvents,
|
||||
ServerSideEvents,
|
||||
SocketData
|
||||
>
|
||||
> {
|
||||
public readonly sockets: Namespace<ListenEvents, EmitEvents>;
|
||||
public readonly sockets: Namespace<
|
||||
ListenEvents,
|
||||
EmitEvents,
|
||||
ServerSideEvents,
|
||||
SocketData
|
||||
>;
|
||||
/**
|
||||
* A reference to the underlying Engine.IO server.
|
||||
*
|
||||
@@ -197,15 +112,18 @@ export class Server<
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_nsps: Map<string, Namespace<ListenEvents, EmitEvents>> = new Map();
|
||||
_nsps: Map<
|
||||
string,
|
||||
Namespace<ListenEvents, EmitEvents, ServerSideEvents, SocketData>
|
||||
> = new Map();
|
||||
private parentNsps: Map<
|
||||
ParentNspNameMatchFn,
|
||||
ParentNamespace<ListenEvents, EmitEvents>
|
||||
ParentNamespace<ListenEvents, EmitEvents, ServerSideEvents, SocketData>
|
||||
> = new Map();
|
||||
private _adapter?: typeof Adapter;
|
||||
private _adapter?: AdapterConstructor;
|
||||
private _serveClient: boolean;
|
||||
private opts: Partial<EngineOptions>;
|
||||
private eio;
|
||||
private eio: Engine;
|
||||
private _path: string;
|
||||
private clientPathRegex: RegExp;
|
||||
|
||||
@@ -249,7 +167,7 @@ export class Server<
|
||||
this.adapter(opts.adapter || Adapter);
|
||||
this.sockets = this.of("/");
|
||||
this.opts = opts;
|
||||
if (srv) this.attach(srv as http.Server);
|
||||
if (srv || typeof srv == "number") this.attach(srv as http.Server | number);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -280,7 +198,11 @@ export class Server<
|
||||
_checkNamespace(
|
||||
name: string,
|
||||
auth: { [key: string]: any },
|
||||
fn: (nsp: Namespace<ListenEvents, EmitEvents> | false) => void
|
||||
fn: (
|
||||
nsp:
|
||||
| Namespace<ListenEvents, EmitEvents, ServerSideEvents, SocketData>
|
||||
| false
|
||||
) => void
|
||||
): void {
|
||||
if (this.parentNsps.size === 0) return fn(false);
|
||||
|
||||
@@ -293,10 +215,18 @@ export class Server<
|
||||
}
|
||||
nextFn.value(name, auth, (err, allow) => {
|
||||
if (err || !allow) {
|
||||
run();
|
||||
} else {
|
||||
fn(this.parentNsps.get(nextFn.value)!.createChild(name));
|
||||
return run();
|
||||
}
|
||||
if (this._nsps.has(name)) {
|
||||
// the namespace was created in the meantime
|
||||
debug("dynamic namespace %s already exists", name);
|
||||
return fn(this._nsps.get(name) as Namespace);
|
||||
}
|
||||
const namespace = this.parentNsps.get(nextFn.value)!.createChild(name);
|
||||
debug("dynamic namespace %s was created", name);
|
||||
// @ts-ignore
|
||||
this.sockets.emitReserved("new_namespace", namespace);
|
||||
fn(namespace);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -322,7 +252,7 @@ export class Server<
|
||||
this.clientPathRegex = new RegExp(
|
||||
"^" +
|
||||
escapedPath +
|
||||
"/socket\\.io(\\.min|\\.msgpack\\.min)?\\.js(\\.map)?$"
|
||||
"/socket\\.io(\\.msgpack|\\.esm)?(\\.min)?\\.js(\\.map)?(?:\\?|$)"
|
||||
);
|
||||
return this;
|
||||
}
|
||||
@@ -348,10 +278,11 @@ export class Server<
|
||||
* @return self when setting or value when getting
|
||||
* @public
|
||||
*/
|
||||
public adapter(): typeof Adapter | undefined;
|
||||
public adapter(v: typeof Adapter): this;
|
||||
public adapter(v?: typeof Adapter): typeof Adapter | undefined | this;
|
||||
public adapter(v?: typeof Adapter): typeof Adapter | undefined | this {
|
||||
public adapter(): AdapterConstructor | undefined;
|
||||
public adapter(v: AdapterConstructor): this;
|
||||
public adapter(
|
||||
v?: AdapterConstructor
|
||||
): AdapterConstructor | undefined | this {
|
||||
if (!arguments.length) return this._adapter;
|
||||
this._adapter = v;
|
||||
for (const nsp of this._nsps.values()) {
|
||||
@@ -419,6 +350,69 @@ export class Server<
|
||||
return this;
|
||||
}
|
||||
|
||||
public attachApp(app /*: TemplatedApp */, opts: Partial<ServerOptions> = {}) {
|
||||
// merge the options passed to the Socket.IO server
|
||||
Object.assign(opts, this.opts);
|
||||
// set engine.io path to `/socket.io`
|
||||
opts.path = opts.path || this._path;
|
||||
|
||||
// initialize engine
|
||||
debug("creating uWebSockets.js-based engine with opts %j", opts);
|
||||
const engine = new uServer(opts);
|
||||
|
||||
engine.attach(app, opts);
|
||||
|
||||
// bind to engine events
|
||||
this.bind(engine);
|
||||
|
||||
if (this._serveClient) {
|
||||
// attach static file serving
|
||||
app.get(`${this._path}/*`, (res, req) => {
|
||||
if (!this.clientPathRegex.test(req.getUrl())) {
|
||||
req.setYield(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const filename = req
|
||||
.getUrl()
|
||||
.replace(this._path, "")
|
||||
.replace(/\?.*$/, "")
|
||||
.replace(/^\//, "");
|
||||
const isMap = dotMapRegex.test(filename);
|
||||
const type = isMap ? "map" : "source";
|
||||
|
||||
// Per the standard, ETags must be quoted:
|
||||
// https://tools.ietf.org/html/rfc7232#section-2.3
|
||||
const expectedEtag = '"' + clientVersion + '"';
|
||||
const weakEtag = "W/" + expectedEtag;
|
||||
|
||||
const etag = req.getHeader("if-none-match");
|
||||
if (etag) {
|
||||
if (expectedEtag === etag || weakEtag === etag) {
|
||||
debug("serve client %s 304", type);
|
||||
res.writeStatus("304 Not Modified");
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
debug("serve client %s", type);
|
||||
|
||||
res.writeHeader("cache-control", "public, max-age=0");
|
||||
res.writeHeader(
|
||||
"content-type",
|
||||
"application/" + (isMap ? "json" : "javascript")
|
||||
);
|
||||
res.writeHeader("etag", expectedEtag);
|
||||
|
||||
const filepath = path.join(__dirname, "../client-dist/", filename);
|
||||
serveFile(res, filepath);
|
||||
});
|
||||
}
|
||||
|
||||
patchAdapter(app);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize engine
|
||||
*
|
||||
@@ -428,11 +422,11 @@ export class Server<
|
||||
*/
|
||||
private initEngine(
|
||||
srv: http.Server,
|
||||
opts: Partial<EngineAttachOptions>
|
||||
opts: EngineOptions & AttachOptions
|
||||
): void {
|
||||
// initialize engine
|
||||
debug("creating engine.io instance with opts %j", opts);
|
||||
this.eio = engine.attach(srv, opts);
|
||||
this.eio = attach(srv, opts);
|
||||
|
||||
// attach static file serving
|
||||
if (this._serveClient) this.attachServe(srv);
|
||||
@@ -456,7 +450,7 @@ export class Server<
|
||||
const evs = srv.listeners("request").slice(0);
|
||||
srv.removeAllListeners("request");
|
||||
srv.on("request", (req, res) => {
|
||||
if (this.clientPathRegex.test(req.url)) {
|
||||
if (this.clientPathRegex.test(req.url!)) {
|
||||
this.serve(req, res);
|
||||
} else {
|
||||
for (let i = 0; i < evs.length; i++) {
|
||||
@@ -474,7 +468,7 @@ export class Server<
|
||||
* @private
|
||||
*/
|
||||
private serve(req: http.IncomingMessage, res: http.ServerResponse): void {
|
||||
const filename = req.url!.replace(this._path, "");
|
||||
const filename = req.url!.replace(this._path, "").replace(/\?.*$/, "");
|
||||
const isMap = dotMapRegex.test(filename);
|
||||
const type = isMap ? "map" : "source";
|
||||
|
||||
@@ -502,9 +496,6 @@ export class Server<
|
||||
);
|
||||
res.setHeader("ETag", expectedEtag);
|
||||
|
||||
if (!isMap) {
|
||||
res.setHeader("X-SourceMap", filename.substring(1) + ".map");
|
||||
}
|
||||
Server.sendFile(filename, req, res);
|
||||
}
|
||||
|
||||
@@ -589,8 +580,10 @@ export class Server<
|
||||
*/
|
||||
public of(
|
||||
name: string | RegExp | ParentNspNameMatchFn,
|
||||
fn?: (socket: Socket<ListenEvents, EmitEvents>) => void
|
||||
): Namespace<ListenEvents, EmitEvents> {
|
||||
fn?: (
|
||||
socket: Socket<ListenEvents, EmitEvents, ServerSideEvents, SocketData>
|
||||
) => void
|
||||
): Namespace<ListenEvents, EmitEvents, ServerSideEvents, SocketData> {
|
||||
if (typeof name === "function" || name instanceof RegExp) {
|
||||
const parentNsp = new ParentNamespace(this);
|
||||
debug("initializing parent namespace %s", parentNsp.name);
|
||||
@@ -616,6 +609,10 @@ export class Server<
|
||||
debug("initializing namespace %s", name);
|
||||
nsp = new Namespace(this, name);
|
||||
this._nsps.set(name, nsp);
|
||||
if (name !== "/") {
|
||||
// @ts-ignore
|
||||
this.sockets.emitReserved("new_namespace", nsp);
|
||||
}
|
||||
}
|
||||
if (fn) nsp.on("connect", fn);
|
||||
return nsp;
|
||||
@@ -634,6 +631,9 @@ export class Server<
|
||||
|
||||
this.engine.close();
|
||||
|
||||
// restore the Adapter prototype
|
||||
restoreAdapter();
|
||||
|
||||
if (this.httpServer) {
|
||||
this.httpServer.close(fn);
|
||||
} else {
|
||||
@@ -649,7 +649,7 @@ export class Server<
|
||||
*/
|
||||
public use(
|
||||
fn: (
|
||||
socket: Socket<ListenEvents, EmitEvents>,
|
||||
socket: Socket<ListenEvents, EmitEvents, ServerSideEvents, SocketData>,
|
||||
next: (err?: ExtendedError) => void
|
||||
) => void
|
||||
): this {
|
||||
@@ -664,7 +664,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);
|
||||
}
|
||||
|
||||
@@ -675,7 +675,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);
|
||||
}
|
||||
|
||||
@@ -686,9 +686,10 @@ export class Server<
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public except(name: Room | Room[]): Server<ListenEvents, EmitEvents> {
|
||||
this.sockets.except(name);
|
||||
return this;
|
||||
public except(
|
||||
name: Room | Room[]
|
||||
): BroadcastOperator<EmitEvents, SocketData> {
|
||||
return this.sockets.except(name);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -713,6 +714,20 @@ export class Server<
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a packet to other Socket.IO servers
|
||||
*
|
||||
* @param ev - the event name
|
||||
* @param args - an array of arguments, which may include an acknowledgement callback at the end
|
||||
* @public
|
||||
*/
|
||||
public serverSideEmit<Ev extends EventNames<ServerSideEvents>>(
|
||||
ev: Ev,
|
||||
...args: EventParams<ServerSideEvents, Ev>
|
||||
): boolean {
|
||||
return this.sockets.serverSideEmit(ev, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of socket ids.
|
||||
*
|
||||
@@ -729,7 +744,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);
|
||||
}
|
||||
|
||||
@@ -741,7 +758,7 @@ export class Server<
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public get volatile(): BroadcastOperator<EmitEvents> {
|
||||
public get volatile(): BroadcastOperator<EmitEvents, SocketData> {
|
||||
return this.sockets.volatile;
|
||||
}
|
||||
|
||||
@@ -751,7 +768,7 @@ export class Server<
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public get local(): BroadcastOperator<EmitEvents> {
|
||||
public get local(): BroadcastOperator<EmitEvents, SocketData> {
|
||||
return this.sockets.local;
|
||||
}
|
||||
|
||||
@@ -760,7 +777,7 @@ export class Server<
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
public fetchSockets(): Promise<RemoteSocket<EmitEvents>[]> {
|
||||
public fetchSockets(): Promise<RemoteSocket<EmitEvents, SocketData>[]> {
|
||||
return this.sockets.fetchSockets();
|
||||
}
|
||||
|
||||
@@ -817,3 +834,4 @@ module.exports.Namespace = Namespace;
|
||||
module.exports.Socket = Socket;
|
||||
|
||||
export { Socket, ServerOptions, Namespace, BroadcastOperator, RemoteSocket };
|
||||
export { Event } from "./socket";
|
||||
|
||||
127
lib/namespace.ts
127
lib/namespace.ts
@@ -20,35 +20,73 @@ export interface ExtendedError extends Error {
|
||||
|
||||
export interface NamespaceReservedEventsMap<
|
||||
ListenEvents extends EventsMap,
|
||||
EmitEvents extends EventsMap
|
||||
EmitEvents extends EventsMap,
|
||||
ServerSideEvents extends EventsMap,
|
||||
SocketData
|
||||
> {
|
||||
connect: (socket: Socket<ListenEvents, EmitEvents>) => void;
|
||||
connection: (socket: Socket<ListenEvents, EmitEvents>) => void;
|
||||
connect: (
|
||||
socket: Socket<ListenEvents, EmitEvents, ServerSideEvents, SocketData>
|
||||
) => void;
|
||||
connection: (
|
||||
socket: Socket<ListenEvents, EmitEvents, ServerSideEvents, SocketData>
|
||||
) => void;
|
||||
}
|
||||
|
||||
export interface ServerReservedEventsMap<
|
||||
ListenEvents,
|
||||
EmitEvents,
|
||||
ServerSideEvents,
|
||||
SocketData
|
||||
> extends NamespaceReservedEventsMap<
|
||||
ListenEvents,
|
||||
EmitEvents,
|
||||
ServerSideEvents,
|
||||
SocketData
|
||||
> {
|
||||
new_namespace: (
|
||||
namespace: Namespace<ListenEvents, EmitEvents, ServerSideEvents, SocketData>
|
||||
) => void;
|
||||
}
|
||||
|
||||
export const RESERVED_EVENTS: ReadonlySet<string | Symbol> = new Set<
|
||||
keyof ServerReservedEventsMap<never, never, never, never>
|
||||
>(<const>["connect", "connection", "new_namespace"]);
|
||||
|
||||
export class Namespace<
|
||||
ListenEvents extends EventsMap = DefaultEventsMap,
|
||||
EmitEvents extends EventsMap = ListenEvents
|
||||
EmitEvents extends EventsMap = ListenEvents,
|
||||
ServerSideEvents extends EventsMap = DefaultEventsMap,
|
||||
SocketData = any
|
||||
> extends StrictEventEmitter<
|
||||
{},
|
||||
ServerSideEvents,
|
||||
EmitEvents,
|
||||
NamespaceReservedEventsMap<ListenEvents, EmitEvents>
|
||||
NamespaceReservedEventsMap<
|
||||
ListenEvents,
|
||||
EmitEvents,
|
||||
ServerSideEvents,
|
||||
SocketData
|
||||
>
|
||||
> {
|
||||
public readonly name: string;
|
||||
public readonly sockets: Map<
|
||||
SocketId,
|
||||
Socket<ListenEvents, EmitEvents>
|
||||
Socket<ListenEvents, EmitEvents, ServerSideEvents, SocketData>
|
||||
> = new Map();
|
||||
|
||||
public adapter: Adapter;
|
||||
|
||||
/** @private */
|
||||
readonly server: Server<ListenEvents, EmitEvents>;
|
||||
readonly server: Server<
|
||||
ListenEvents,
|
||||
EmitEvents,
|
||||
ServerSideEvents,
|
||||
SocketData
|
||||
>;
|
||||
|
||||
/** @private */
|
||||
_fns: Array<
|
||||
(
|
||||
socket: Socket<ListenEvents, EmitEvents>,
|
||||
socket: Socket<ListenEvents, EmitEvents, ServerSideEvents, SocketData>,
|
||||
next: (err?: ExtendedError) => void
|
||||
) => void
|
||||
> = [];
|
||||
@@ -62,7 +100,10 @@ export class Namespace<
|
||||
* @param server instance
|
||||
* @param name
|
||||
*/
|
||||
constructor(server: Server<ListenEvents, EmitEvents>, name: string) {
|
||||
constructor(
|
||||
server: Server<ListenEvents, EmitEvents, ServerSideEvents, SocketData>,
|
||||
name: string
|
||||
) {
|
||||
super();
|
||||
this.server = server;
|
||||
this.name = name;
|
||||
@@ -77,6 +118,7 @@ export class Namespace<
|
||||
* @private
|
||||
*/
|
||||
_initAdapter(): void {
|
||||
// @ts-ignore
|
||||
this.adapter = new (this.server.adapter()!)(this);
|
||||
}
|
||||
|
||||
@@ -88,7 +130,7 @@ export class Namespace<
|
||||
*/
|
||||
public use(
|
||||
fn: (
|
||||
socket: Socket<ListenEvents, EmitEvents>,
|
||||
socket: Socket<ListenEvents, EmitEvents, ServerSideEvents, SocketData>,
|
||||
next: (err?: ExtendedError) => void
|
||||
) => void
|
||||
): this {
|
||||
@@ -104,7 +146,7 @@ export class Namespace<
|
||||
* @private
|
||||
*/
|
||||
private run(
|
||||
socket: Socket<ListenEvents, EmitEvents>,
|
||||
socket: Socket<ListenEvents, EmitEvents, ServerSideEvents, SocketData>,
|
||||
fn: (err: ExtendedError | null) => void
|
||||
) {
|
||||
const fns = this._fns.slice(0);
|
||||
@@ -133,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);
|
||||
}
|
||||
|
||||
@@ -144,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);
|
||||
}
|
||||
|
||||
@@ -155,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);
|
||||
}
|
||||
|
||||
@@ -166,10 +210,10 @@ export class Namespace<
|
||||
* @private
|
||||
*/
|
||||
_add(
|
||||
client: Client<ListenEvents, EmitEvents>,
|
||||
client: Client<ListenEvents, EmitEvents, ServerSideEvents>,
|
||||
query,
|
||||
fn?: () => void
|
||||
): Socket<ListenEvents, EmitEvents> {
|
||||
): Socket<ListenEvents, EmitEvents, ServerSideEvents, SocketData> {
|
||||
debug("adding socket to nsp %s", this.name);
|
||||
const socket = new Socket(this, client, query);
|
||||
this.run(socket, (err) => {
|
||||
@@ -212,7 +256,9 @@ export class Namespace<
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_remove(socket: Socket<ListenEvents, EmitEvents>): void {
|
||||
_remove(
|
||||
socket: Socket<ListenEvents, EmitEvents, ServerSideEvents, SocketData>
|
||||
): void {
|
||||
if (this.sockets.has(socket.id)) {
|
||||
this.sockets.delete(socket.id);
|
||||
} else {
|
||||
@@ -230,7 +276,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
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -255,6 +304,36 @@ export class Namespace<
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a packet to other Socket.IO servers
|
||||
*
|
||||
* @param ev - the event name
|
||||
* @param args - an array of arguments, which may include an acknowledgement callback at the end
|
||||
* @public
|
||||
*/
|
||||
public serverSideEmit<Ev extends EventNames<ServerSideEvents>>(
|
||||
ev: Ev,
|
||||
...args: EventParams<ServerSideEvents, Ev>
|
||||
): boolean {
|
||||
if (RESERVED_EVENTS.has(ev)) {
|
||||
throw new Error(`"${ev}" is a reserved event name`);
|
||||
}
|
||||
args.unshift(ev);
|
||||
this.adapter.serverSideEmit(args);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a packet is received from another Socket.IO server
|
||||
*
|
||||
* @param args - an array of arguments, which may include an acknowledgement callback at the end
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_onServerSideEmit(args: [string, ...any[]]) {
|
||||
super.emitUntyped.apply(this, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of clients.
|
||||
*
|
||||
@@ -272,7 +351,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);
|
||||
}
|
||||
|
||||
@@ -284,7 +365,7 @@ export class Namespace<
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public get volatile(): BroadcastOperator<EmitEvents> {
|
||||
public get volatile(): BroadcastOperator<EmitEvents, SocketData> {
|
||||
return new BroadcastOperator(this.adapter).volatile;
|
||||
}
|
||||
|
||||
@@ -294,7 +375,7 @@ export class Namespace<
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public get local(): BroadcastOperator<EmitEvents> {
|
||||
public get local(): BroadcastOperator<EmitEvents, SocketData> {
|
||||
return new BroadcastOperator(this.adapter).local;
|
||||
}
|
||||
|
||||
@@ -303,7 +384,7 @@ export class Namespace<
|
||||
*
|
||||
* @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,
|
||||
@@ -10,12 +10,18 @@ import type { BroadcastOptions } from "socket.io-adapter";
|
||||
|
||||
export class ParentNamespace<
|
||||
ListenEvents extends EventsMap = DefaultEventsMap,
|
||||
EmitEvents extends EventsMap = ListenEvents
|
||||
> extends Namespace<ListenEvents, EmitEvents> {
|
||||
EmitEvents extends EventsMap = ListenEvents,
|
||||
ServerSideEvents extends EventsMap = DefaultEventsMap,
|
||||
SocketData = any
|
||||
> extends Namespace<ListenEvents, EmitEvents, ServerSideEvents, SocketData> {
|
||||
private static count: number = 0;
|
||||
private children: Set<Namespace<ListenEvents, EmitEvents>> = new Set();
|
||||
private children: Set<
|
||||
Namespace<ListenEvents, EmitEvents, ServerSideEvents, SocketData>
|
||||
> = new Set();
|
||||
|
||||
constructor(server: Server<ListenEvents, EmitEvents>) {
|
||||
constructor(
|
||||
server: Server<ListenEvents, EmitEvents, ServerSideEvents, SocketData>
|
||||
) {
|
||||
super(server, "/_" + ParentNamespace.count++);
|
||||
}
|
||||
|
||||
@@ -43,7 +49,9 @@ export class ParentNamespace<
|
||||
return true;
|
||||
}
|
||||
|
||||
createChild(name: string): Namespace<ListenEvents, EmitEvents> {
|
||||
createChild(
|
||||
name: string
|
||||
): Namespace<ListenEvents, EmitEvents, ServerSideEvents, SocketData> {
|
||||
const namespace = new Namespace(this.server, name);
|
||||
namespace._fns = this._fns.slice(0);
|
||||
this.listeners("connect").forEach((listener) =>
|
||||
@@ -56,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");
|
||||
}
|
||||
}
|
||||
|
||||
120
lib/socket.ts
120
lib/socket.ts
@@ -1,5 +1,4 @@
|
||||
import { Packet, PacketType } from "socket.io-parser";
|
||||
import url = require("url");
|
||||
import debugModule from "debug";
|
||||
import type { Server } from "./index";
|
||||
import {
|
||||
@@ -46,7 +45,7 @@ export interface EventEmitterReservedEventsMap {
|
||||
|
||||
export const RESERVED_EVENTS: ReadonlySet<string | Symbol> = new Set<
|
||||
| ClientReservedEvents
|
||||
| keyof NamespaceReservedEventsMap<never, never>
|
||||
| keyof NamespaceReservedEventsMap<never, never, never, never>
|
||||
| keyof SocketReservedEventsMap
|
||||
| keyof EventEmitterReservedEventsMap
|
||||
>(<const>[
|
||||
@@ -108,9 +107,13 @@ export interface Handshake {
|
||||
auth: { [key: string]: any };
|
||||
}
|
||||
|
||||
export type Event = [eventName: string, ...args: any[]];
|
||||
|
||||
export class Socket<
|
||||
ListenEvents extends EventsMap = DefaultEventsMap,
|
||||
EmitEvents extends EventsMap = ListenEvents
|
||||
EmitEvents extends EventsMap = ListenEvents,
|
||||
ServerSideEvents extends EventsMap = DefaultEventsMap,
|
||||
SocketData = any
|
||||
> extends StrictEventEmitter<
|
||||
ListenEvents,
|
||||
EmitEvents,
|
||||
@@ -121,18 +124,20 @@ export class Socket<
|
||||
/**
|
||||
* Additional information that can be attached to the Socket instance and which will be used in the fetchSockets method
|
||||
*/
|
||||
public data: any = {};
|
||||
public data: Partial<SocketData> = {};
|
||||
|
||||
public connected: boolean;
|
||||
public disconnected: boolean;
|
||||
public connected: boolean = false;
|
||||
|
||||
private readonly server: Server<ListenEvents, EmitEvents>;
|
||||
private readonly server: Server<
|
||||
ListenEvents,
|
||||
EmitEvents,
|
||||
ServerSideEvents,
|
||||
SocketData
|
||||
>;
|
||||
private readonly adapter: Adapter;
|
||||
private acks: Map<number, () => void> = new Map();
|
||||
private fns: Array<
|
||||
(event: Array<any>, next: (err?: Error) => void) => void
|
||||
> = [];
|
||||
private flags: BroadcastFlags = {};
|
||||
private fns: Array<(event: Event, next: (err?: Error) => void) => void> = [];
|
||||
private flags: BroadcastFlags & { timeout?: number } = {};
|
||||
private _anyListeners?: Array<(...args: any[]) => void>;
|
||||
|
||||
/**
|
||||
@@ -144,8 +149,8 @@ export class Socket<
|
||||
* @package
|
||||
*/
|
||||
constructor(
|
||||
readonly nsp: Namespace<ListenEvents, EmitEvents>,
|
||||
readonly client: Client<ListenEvents, EmitEvents>,
|
||||
readonly nsp: Namespace<ListenEvents, EmitEvents, ServerSideEvents>,
|
||||
readonly client: Client<ListenEvents, EmitEvents, ServerSideEvents>,
|
||||
auth: object
|
||||
) {
|
||||
super();
|
||||
@@ -157,8 +162,6 @@ export class Socket<
|
||||
} else {
|
||||
this.id = base64id.generateId(); // don't reuse the Engine.IO id because it's sensitive information
|
||||
}
|
||||
this.connected = true;
|
||||
this.disconnected = false;
|
||||
this.handshake = this.buildHandshake(auth);
|
||||
}
|
||||
|
||||
@@ -177,7 +180,8 @@ export class Socket<
|
||||
secure: !!this.request.connection.encrypted,
|
||||
issued: +new Date(),
|
||||
url: this.request.url!,
|
||||
query: url.parse(this.request.url!, true).query,
|
||||
// @ts-ignore
|
||||
query: this.request._query,
|
||||
auth,
|
||||
};
|
||||
}
|
||||
@@ -203,9 +207,11 @@ export class Socket<
|
||||
|
||||
// access last argument to see if it's an ACK callback
|
||||
if (typeof data[data.length - 1] === "function") {
|
||||
debug("emitting packet with ack id %d", this.nsp._ids);
|
||||
this.acks.set(this.nsp._ids, data.pop());
|
||||
packet.id = this.nsp._ids++;
|
||||
const id = this.nsp._ids++;
|
||||
debug("emitting packet with ack id %d", id);
|
||||
|
||||
this.registerAckCallback(id, data.pop());
|
||||
packet.id = id;
|
||||
}
|
||||
|
||||
const flags = Object.assign({}, this.flags);
|
||||
@@ -216,6 +222,28 @@ export class Socket<
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private registerAckCallback(id: number, ack: (...args: any[]) => void): void {
|
||||
const timeout = this.flags.timeout;
|
||||
if (timeout === undefined) {
|
||||
this.acks.set(id, ack);
|
||||
return;
|
||||
}
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
debug("event with ack id %d has timed out after %d ms", id, timeout);
|
||||
this.acks.delete(id);
|
||||
ack.call(this, new Error("operation has timed out"));
|
||||
}, timeout);
|
||||
|
||||
this.acks.set(id, (...args) => {
|
||||
clearTimeout(timer);
|
||||
ack.apply(this, [null, ...args]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Targets a room when broadcasting.
|
||||
*
|
||||
@@ -223,7 +251,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);
|
||||
}
|
||||
|
||||
@@ -234,7 +262,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);
|
||||
}
|
||||
|
||||
@@ -245,7 +273,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);
|
||||
}
|
||||
|
||||
@@ -335,6 +365,7 @@ export class Socket<
|
||||
*/
|
||||
_onconnect(): void {
|
||||
debug("socket connected - writing packet");
|
||||
this.connected = true;
|
||||
this.join(this.id);
|
||||
if (this.conn.protocol === 3) {
|
||||
this.packet({ type: PacketType.CONNECT });
|
||||
@@ -482,7 +513,6 @@ export class Socket<
|
||||
this.nsp._remove(this);
|
||||
this.client._remove(this);
|
||||
this.connected = false;
|
||||
this.disconnected = true;
|
||||
this.emitReserved("disconnect", reason);
|
||||
return;
|
||||
}
|
||||
@@ -549,7 +579,7 @@ export class Socket<
|
||||
* @return {Socket} self
|
||||
* @public
|
||||
*/
|
||||
public get broadcast(): BroadcastOperator<EmitEvents> {
|
||||
public get broadcast(): BroadcastOperator<EmitEvents, SocketData> {
|
||||
return this.newBroadcastOperator();
|
||||
}
|
||||
|
||||
@@ -559,17 +589,37 @@ export class Socket<
|
||||
* @return {Socket} self
|
||||
* @public
|
||||
*/
|
||||
public get local(): BroadcastOperator<EmitEvents> {
|
||||
public get local(): BroadcastOperator<EmitEvents, SocketData> {
|
||||
return this.newBroadcastOperator().local;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a modifier for a subsequent event emission that the callback will be called with an error when the
|
||||
* given number of milliseconds have elapsed without an acknowledgement from the client:
|
||||
*
|
||||
* ```
|
||||
* socket.timeout(5000).emit("my-event", (err) => {
|
||||
* if (err) {
|
||||
* // the client did not acknowledge the event in the given delay
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @returns self
|
||||
* @public
|
||||
*/
|
||||
public timeout(timeout: number): this {
|
||||
this.flags.timeout = timeout;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch incoming event to socket listeners.
|
||||
*
|
||||
* @param {Array} event - event that will get emitted
|
||||
* @private
|
||||
*/
|
||||
private dispatch(event: [eventName: string, ...args: any[]]): void {
|
||||
private dispatch(event: Event): void {
|
||||
debug("dispatching an event %j", event);
|
||||
this.run(event, (err) => {
|
||||
process.nextTick(() => {
|
||||
@@ -592,9 +642,7 @@ export class Socket<
|
||||
* @return {Socket} self
|
||||
* @public
|
||||
*/
|
||||
public use(
|
||||
fn: (event: Array<any>, next: (err?: Error) => void) => void
|
||||
): this {
|
||||
public use(fn: (event: Event, next: (err?: Error) => void) => void): this {
|
||||
this.fns.push(fn);
|
||||
return this;
|
||||
}
|
||||
@@ -606,10 +654,7 @@ export class Socket<
|
||||
* @param {Function} fn - last fn call in the middleware
|
||||
* @private
|
||||
*/
|
||||
private run(
|
||||
event: [eventName: string, ...args: any[]],
|
||||
fn: (err: Error | null) => void
|
||||
): void {
|
||||
private run(event: Event, fn: (err: Error | null) => void): void {
|
||||
const fns = this.fns.slice(0);
|
||||
if (!fns.length) return fn(null);
|
||||
|
||||
@@ -629,6 +674,13 @@ export class Socket<
|
||||
run(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the socket is currently disconnected
|
||||
*/
|
||||
public get disconnected() {
|
||||
return !this.connected;
|
||||
}
|
||||
|
||||
/**
|
||||
* A reference to the request that originated the underlying Engine.IO Socket.
|
||||
*
|
||||
@@ -714,7 +766,7 @@ export class Socket<
|
||||
return this._anyListeners || [];
|
||||
}
|
||||
|
||||
private newBroadcastOperator(): BroadcastOperator<EmitEvents> {
|
||||
private newBroadcastOperator(): BroadcastOperator<EmitEvents, SocketData> {
|
||||
const flags = Object.assign({}, this.flags);
|
||||
this.flags = {};
|
||||
return new BroadcastOperator(
|
||||
|
||||
@@ -58,7 +58,7 @@ export type ReservedOrUserListener<
|
||||
* Needed because of https://github.com/microsoft/TypeScript/issues/41778
|
||||
*/
|
||||
type FallbackToUntypedListener<T> = [T] extends [never]
|
||||
? (...args: any[]) => void
|
||||
? (...args: any[]) => void | Promise<void>
|
||||
: T;
|
||||
|
||||
/**
|
||||
@@ -91,7 +91,8 @@ export abstract class StrictEventEmitter<
|
||||
ReservedEvents extends EventsMap = {}
|
||||
>
|
||||
extends EventEmitter
|
||||
implements TypedEventBroadcaster<EmitEvents> {
|
||||
implements TypedEventBroadcaster<EmitEvents>
|
||||
{
|
||||
/**
|
||||
* Adds the `listener` function as an event listener for `ev`.
|
||||
*
|
||||
|
||||
162
lib/uws.ts
Normal file
162
lib/uws.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
import { Adapter, Room } from "socket.io-adapter";
|
||||
import type { WebSocket } from "uWebSockets.js";
|
||||
import type { Socket } from "./socket.js";
|
||||
import { createReadStream, statSync } from "fs";
|
||||
import debugModule from "debug";
|
||||
|
||||
const debug = debugModule("socket.io:adapter-uws");
|
||||
|
||||
const SEPARATOR = "\x1f"; // see https://en.wikipedia.org/wiki/Delimiter#ASCII_delimited_text
|
||||
|
||||
const { addAll, del, broadcast } = Adapter.prototype;
|
||||
|
||||
export function patchAdapter(app /* : TemplatedApp */) {
|
||||
Adapter.prototype.addAll = function (id, rooms) {
|
||||
const isNew = !this.sids.has(id);
|
||||
addAll.call(this, id, rooms);
|
||||
const socket: Socket = this.nsp.sockets.get(id);
|
||||
if (!socket) {
|
||||
return;
|
||||
}
|
||||
if (socket.conn.transport.name === "websocket") {
|
||||
subscribe(this.nsp.name, socket, isNew, rooms);
|
||||
return;
|
||||
}
|
||||
if (isNew) {
|
||||
socket.conn.on("upgrade", () => {
|
||||
const rooms = this.sids.get(id);
|
||||
subscribe(this.nsp.name, socket, isNew, rooms);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Adapter.prototype.del = function (id, room) {
|
||||
del.call(this, id, room);
|
||||
const socket: Socket = this.nsp.sockets.get(id);
|
||||
if (socket && socket.conn.transport.name === "websocket") {
|
||||
// @ts-ignore
|
||||
const sessionId = socket.conn.id;
|
||||
// @ts-ignore
|
||||
const websocket: WebSocket = socket.conn.transport.socket;
|
||||
const topic = `${this.nsp.name}${SEPARATOR}${room}`;
|
||||
debug("unsubscribe connection %s from topic %s", sessionId, topic);
|
||||
websocket.unsubscribe(topic);
|
||||
}
|
||||
};
|
||||
|
||||
Adapter.prototype.broadcast = function (packet, opts) {
|
||||
const useFastPublish = opts.rooms.size <= 1 && opts.except!.size === 0;
|
||||
if (!useFastPublish) {
|
||||
broadcast.call(this, packet, opts);
|
||||
return;
|
||||
}
|
||||
|
||||
const flags = opts.flags || {};
|
||||
const basePacketOpts = {
|
||||
preEncoded: true,
|
||||
volatile: flags.volatile,
|
||||
compress: flags.compress,
|
||||
};
|
||||
|
||||
packet.nsp = this.nsp.name;
|
||||
const encodedPackets = this.encoder.encode(packet);
|
||||
|
||||
const topic =
|
||||
opts.rooms.size === 0
|
||||
? this.nsp.name
|
||||
: `${this.nsp.name}${SEPARATOR}${opts.rooms.keys().next().value}`;
|
||||
debug("fast publish to %s", topic);
|
||||
|
||||
// fast publish for clients connected with WebSocket
|
||||
encodedPackets.forEach((encodedPacket) => {
|
||||
const isBinary = typeof encodedPacket !== "string";
|
||||
// "4" being the message type in the Engine.IO protocol, see https://github.com/socketio/engine.io-protocol
|
||||
app.publish(
|
||||
topic,
|
||||
isBinary ? encodedPacket : "4" + encodedPacket,
|
||||
isBinary
|
||||
);
|
||||
});
|
||||
|
||||
this.apply(opts, (socket) => {
|
||||
if (socket.conn.transport.name !== "websocket") {
|
||||
// classic publish for clients connected with HTTP long-polling
|
||||
socket.client.writeToEngine(encodedPackets, basePacketOpts);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function subscribe(
|
||||
namespaceName: string,
|
||||
socket: Socket,
|
||||
isNew: boolean,
|
||||
rooms: Set<Room>
|
||||
) {
|
||||
// @ts-ignore
|
||||
const sessionId = socket.conn.id;
|
||||
// @ts-ignore
|
||||
const websocket: WebSocket = socket.conn.transport.socket;
|
||||
if (isNew) {
|
||||
debug("subscribe connection %s to topic %s", sessionId, namespaceName);
|
||||
websocket.subscribe(namespaceName);
|
||||
}
|
||||
rooms.forEach((room) => {
|
||||
const topic = `${namespaceName}${SEPARATOR}${room}`; // '#' can be used as wildcard
|
||||
debug("subscribe connection %s to topic %s", sessionId, topic);
|
||||
websocket.subscribe(topic);
|
||||
});
|
||||
}
|
||||
|
||||
export function restoreAdapter() {
|
||||
Adapter.prototype.addAll = addAll;
|
||||
Adapter.prototype.del = del;
|
||||
Adapter.prototype.broadcast = broadcast;
|
||||
}
|
||||
|
||||
const toArrayBuffer = (buffer: Buffer) => {
|
||||
const { buffer: arrayBuffer, byteOffset, byteLength } = buffer;
|
||||
return arrayBuffer.slice(byteOffset, byteOffset + byteLength);
|
||||
};
|
||||
|
||||
// imported from https://github.com/kolodziejczak-sz/uwebsocket-serve
|
||||
export function serveFile(res /* : HttpResponse */, filepath: string) {
|
||||
const { size } = statSync(filepath);
|
||||
const readStream = createReadStream(filepath);
|
||||
const destroyReadStream = () => !readStream.destroyed && readStream.destroy();
|
||||
|
||||
const onError = (error: Error) => {
|
||||
destroyReadStream();
|
||||
throw error;
|
||||
};
|
||||
|
||||
const onDataChunk = (chunk: Buffer) => {
|
||||
const arrayBufferChunk = toArrayBuffer(chunk);
|
||||
|
||||
const lastOffset = res.getWriteOffset();
|
||||
const [ok, done] = res.tryEnd(arrayBufferChunk, size);
|
||||
|
||||
if (!done && !ok) {
|
||||
readStream.pause();
|
||||
|
||||
res.onWritable((offset) => {
|
||||
const [ok, done] = res.tryEnd(
|
||||
arrayBufferChunk.slice(offset - lastOffset),
|
||||
size
|
||||
);
|
||||
|
||||
if (!done && ok) {
|
||||
readStream.resume();
|
||||
}
|
||||
|
||||
return ok;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
res.onAborted(destroyReadStream);
|
||||
readStream
|
||||
.on("data", onDataChunk)
|
||||
.on("error", onError)
|
||||
.on("end", destroyReadStream);
|
||||
}
|
||||
5859
package-lock.json
generated
5859
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
34
package.json
34
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "socket.io",
|
||||
"version": "4.0.2",
|
||||
"version": "4.4.1",
|
||||
"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",
|
||||
@@ -45,33 +46,28 @@
|
||||
"prepack": "npm run compile"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/cookie": "^0.4.0",
|
||||
"@types/cors": "^2.8.8",
|
||||
"@types/node": ">=10.0.0",
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "~2.0.0",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io": "~5.0.0",
|
||||
"socket.io-adapter": "~2.2.0",
|
||||
"socket.io-parser": "~4.0.3"
|
||||
"debug": "~4.3.2",
|
||||
"engine.io": "~6.1.0",
|
||||
"socket.io-adapter": "~2.3.3",
|
||||
"socket.io-parser": "~4.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^8.0.4",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^7.14.0",
|
||||
"eslint-config-prettier": "^6.11.0",
|
||||
"@types/mocha": "^9.0.0",
|
||||
"expect.js": "0.3.1",
|
||||
"mocha": "^3.5.3",
|
||||
"nyc": "^15.1.0",
|
||||
"prettier": "^2.2.0",
|
||||
"prettier": "^2.3.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"socket.io-client": "4.0.2",
|
||||
"socket.io-client": "4.4.1",
|
||||
"socket.io-client-v2": "npm:socket.io-client@^2.4.0",
|
||||
"superagent": "^6.1.0",
|
||||
"supertest": "^6.0.1",
|
||||
"ts-node": "^9.0.0",
|
||||
"tsd": "^0.14.0",
|
||||
"typescript": "^4.1.2"
|
||||
"supertest": "^6.1.6",
|
||||
"ts-node": "^10.2.1",
|
||||
"tsd": "^0.17.0",
|
||||
"typescript": "^4.4.2",
|
||||
"uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.0.0"
|
||||
},
|
||||
"contributors": [
|
||||
{
|
||||
|
||||
2
test/fixtures/server-close.ts
vendored
2
test/fixtures/server-close.ts
vendored
@@ -3,7 +3,7 @@ const ioc = require("socket.io-client");
|
||||
const io = require("../..")(server);
|
||||
|
||||
const srv = server.listen(() => {
|
||||
const socket = ioc("ws://localhost:" + server.address().port);
|
||||
const socket = ioc.connect("ws://localhost:" + server.address().port);
|
||||
socket.on("connect", () => {
|
||||
io.close();
|
||||
socket.close();
|
||||
|
||||
57
test/socket-timeout.ts
Normal file
57
test/socket-timeout.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { Server } from "..";
|
||||
import { createClient, success } from "./support/util";
|
||||
import expect from "expect.js";
|
||||
|
||||
describe("timeout", () => {
|
||||
it("should timeout if the client does not acknowledge the event", (done) => {
|
||||
const io = new Server(0);
|
||||
const client = createClient(io, "/");
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
socket.timeout(50).emit("unknown", (err) => {
|
||||
expect(err).to.be.an(Error);
|
||||
success(done, io, client);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should timeout if the client does not acknowledge the event in time", (done) => {
|
||||
const io = new Server(0);
|
||||
const client = createClient(io, "/");
|
||||
|
||||
client.on("echo", (arg, cb) => {
|
||||
cb(arg);
|
||||
});
|
||||
|
||||
let count = 0;
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
socket.timeout(0).emit("echo", 42, (err) => {
|
||||
expect(err).to.be.an(Error);
|
||||
count++;
|
||||
});
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
expect(count).to.eql(1);
|
||||
success(done, io, client);
|
||||
}, 200);
|
||||
});
|
||||
|
||||
it("should not timeout if the client does acknowledge the event", (done) => {
|
||||
const io = new Server(0);
|
||||
const client = createClient(io, "/");
|
||||
|
||||
client.on("echo", (arg, cb) => {
|
||||
cb(arg);
|
||||
});
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
socket.timeout(50).emit("echo", 42, (err, value) => {
|
||||
expect(err).to.be(null);
|
||||
expect(value).to.be(42);
|
||||
success(done, io, client);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,8 +1,9 @@
|
||||
"use strict";
|
||||
import { Server, Socket } from "..";
|
||||
import { Namespace, Server, Socket } from "..";
|
||||
import type { DefaultEventsMap } from "../lib/typed-events";
|
||||
import { createServer } from "http";
|
||||
import { expectError, expectType } from "tsd";
|
||||
import { Adapter } from "socket.io-adapter";
|
||||
|
||||
// This file is run by tsd, not mocha.
|
||||
|
||||
@@ -117,7 +118,9 @@ describe("server", () => {
|
||||
|
||||
it("does not accept arguments of wrong types", (done) => {
|
||||
const srv = createServer();
|
||||
const sio = new Server<BidirectionalEvents>(srv);
|
||||
const sio = new Server<BidirectionalEvents, BidirectionalEvents, {}>(
|
||||
srv
|
||||
);
|
||||
expectError(sio.on("random", (a, b, c) => {}));
|
||||
srv.listen(() => {
|
||||
expectError(sio.on("wrong name", (s) => {}));
|
||||
@@ -229,4 +232,69 @@ describe("server", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("listen and emit event maps", () => {
|
||||
interface ClientToServerEvents {
|
||||
helloFromClient: (message: string) => void;
|
||||
}
|
||||
|
||||
interface ServerToClientEvents {
|
||||
helloFromServer: (message: string, x: number) => void;
|
||||
}
|
||||
|
||||
interface InterServerEvents {
|
||||
helloFromServerToServer: (message: string, x: number) => void;
|
||||
}
|
||||
|
||||
describe("on", () => {
|
||||
it("infers correct types for listener parameters", () => {
|
||||
const srv = createServer();
|
||||
const sio = new Server<
|
||||
ClientToServerEvents,
|
||||
ServerToClientEvents,
|
||||
InterServerEvents
|
||||
>(srv);
|
||||
|
||||
expectType<
|
||||
Server<ClientToServerEvents, ServerToClientEvents, InterServerEvents>
|
||||
>(sio);
|
||||
srv.listen(() => {
|
||||
sio.serverSideEmit("helloFromServerToServer", "hello", 10);
|
||||
sio
|
||||
.of("/test")
|
||||
.serverSideEmit("helloFromServerToServer", "hello", 10);
|
||||
|
||||
sio.on("helloFromServerToServer", (message, x) => {
|
||||
expectType<string>(message);
|
||||
expectType<number>(x);
|
||||
});
|
||||
sio.of("/test").on("helloFromServerToServer", (message, x) => {
|
||||
expectType<string>(message);
|
||||
expectType<number>(x);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("adapter", () => {
|
||||
it("accepts arguments of the correct types", () => {
|
||||
const io = new Server({
|
||||
adapter: (nsp) => new Adapter(nsp),
|
||||
});
|
||||
io.adapter(Adapter);
|
||||
|
||||
class MyCustomAdapter extends Adapter {
|
||||
constructor(nsp, readonly opts) {
|
||||
super(nsp);
|
||||
}
|
||||
}
|
||||
io.adapter((nsp) => new MyCustomAdapter(nsp, { test: "123" }));
|
||||
});
|
||||
|
||||
it("does not accept arguments of wrong types", () => {
|
||||
const io = new Server();
|
||||
expectError(io.adapter((nsp) => "nope"));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,6 +14,9 @@ import { io as ioc, Socket as ClientSocket } from "socket.io-client";
|
||||
|
||||
import "./support/util";
|
||||
import "./utility-methods";
|
||||
import "./uws";
|
||||
|
||||
type callback = (err: Error | null, success: boolean) => void;
|
||||
|
||||
// Creates a socket.io client for the given server
|
||||
function client(srv, nsp?: string | object, opts?: object): ClientSocket {
|
||||
@@ -39,6 +42,11 @@ const waitFor = (emitter, event) => {
|
||||
});
|
||||
};
|
||||
|
||||
const getPort = (io: Server): number => {
|
||||
// @ts-ignore
|
||||
return io.httpServer.address().port;
|
||||
};
|
||||
|
||||
describe("socket.io", () => {
|
||||
it("should be the same version as client", () => {
|
||||
const version = require("../package").version;
|
||||
@@ -59,7 +67,7 @@ describe("socket.io", () => {
|
||||
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(filename + ".map");
|
||||
expect(res.headers["x-sourcemap"]).to.be(undefined);
|
||||
expect(res.text).to.match(/engine\.io/);
|
||||
expect(res.status).to.be(200);
|
||||
done();
|
||||
@@ -83,6 +91,10 @@ describe("socket.io", () => {
|
||||
};
|
||||
|
||||
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"));
|
||||
|
||||
@@ -116,6 +128,13 @@ describe("socket.io", () => {
|
||||
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);
|
||||
@@ -185,29 +204,17 @@ describe("socket.io", () => {
|
||||
|
||||
describe("port", () => {
|
||||
it("should be bound", (done) => {
|
||||
const sockets = new Server(54010);
|
||||
request("http://localhost:54010")
|
||||
.get("/socket.io/socket.io.js")
|
||||
.expect(200, done);
|
||||
});
|
||||
const io = new Server(0);
|
||||
|
||||
it("should be bound as a string", (done) => {
|
||||
const sockets = new Server(54020);
|
||||
request("http://localhost:54020")
|
||||
request(`http://localhost:${getPort(io)}`)
|
||||
.get("/socket.io/socket.io.js")
|
||||
.expect(200, done);
|
||||
});
|
||||
|
||||
it("with listen", (done) => {
|
||||
const sockets = new Server().listen(54011);
|
||||
request("http://localhost:54011")
|
||||
.get("/socket.io/socket.io.js")
|
||||
.expect(200, done);
|
||||
});
|
||||
const io = new Server().listen(0);
|
||||
|
||||
it("as a string", (done) => {
|
||||
const sockets = new Server().listen(54012);
|
||||
request("http://localhost:54012")
|
||||
request(`http://localhost:${getPort(io)}`)
|
||||
.get("/socket.io/socket.io.js")
|
||||
.expect(200, done);
|
||||
});
|
||||
@@ -218,7 +225,7 @@ describe("socket.io", () => {
|
||||
const request = require("superagent");
|
||||
|
||||
it("should send the Access-Control-Allow-xxx headers on OPTIONS request", (done) => {
|
||||
const sockets = new Server(54013, {
|
||||
const io = new Server(0, {
|
||||
cors: {
|
||||
origin: "http://localhost:54023",
|
||||
methods: ["GET", "POST"],
|
||||
@@ -227,7 +234,7 @@ describe("socket.io", () => {
|
||||
},
|
||||
});
|
||||
request
|
||||
.options("http://localhost:54013/socket.io/default/")
|
||||
.options(`http://localhost:${getPort(io)}/socket.io/default/`)
|
||||
.query({ transport: "polling", EIO: 4 })
|
||||
.set("Origin", "http://localhost:54023")
|
||||
.end((err, res) => {
|
||||
@@ -246,7 +253,7 @@ describe("socket.io", () => {
|
||||
});
|
||||
|
||||
it("should send the Access-Control-Allow-xxx headers on GET request", (done) => {
|
||||
const sockets = new Server(54014, {
|
||||
const io = new Server(0, {
|
||||
cors: {
|
||||
origin: "http://localhost:54024",
|
||||
methods: ["GET", "POST"],
|
||||
@@ -255,7 +262,7 @@ describe("socket.io", () => {
|
||||
},
|
||||
});
|
||||
request
|
||||
.get("http://localhost:54014/socket.io/default/")
|
||||
.get(`http://localhost:${getPort(io)}/socket.io/default/`)
|
||||
.query({ transport: "polling", EIO: 4 })
|
||||
.set("Origin", "http://localhost:54024")
|
||||
.end((err, res) => {
|
||||
@@ -270,12 +277,12 @@ describe("socket.io", () => {
|
||||
});
|
||||
|
||||
it("should allow request if custom function in opts.allowRequest returns true", (done) => {
|
||||
const sockets = new Server(createServer().listen(54022), {
|
||||
const io = new Server(0, {
|
||||
allowRequest: (req, callback) => callback(null, true),
|
||||
});
|
||||
|
||||
request
|
||||
.get("http://localhost:54022/socket.io/default/")
|
||||
.get(`http://localhost:${getPort(io)}/socket.io/default/`)
|
||||
.query({ transport: "polling", EIO: 4 })
|
||||
.end((err, res) => {
|
||||
expect(res.status).to.be(200);
|
||||
@@ -284,11 +291,11 @@ describe("socket.io", () => {
|
||||
});
|
||||
|
||||
it("should disallow request if custom function in opts.allowRequest returns false", (done) => {
|
||||
const sockets = new Server(createServer().listen(54023), {
|
||||
const io = new Server(0, {
|
||||
allowRequest: (req, callback) => callback(null, false),
|
||||
});
|
||||
request
|
||||
.get("http://localhost:54023/socket.io/default/")
|
||||
.get(`http://localhost:${getPort(io)}/socket.io/default/`)
|
||||
.set("origin", "http://foo.example")
|
||||
.query({ transport: "polling", EIO: 4 })
|
||||
.end((err, res) => {
|
||||
@@ -300,22 +307,22 @@ describe("socket.io", () => {
|
||||
|
||||
describe("close", () => {
|
||||
it("should be able to close sio sending a srv", (done) => {
|
||||
const PORT = 54018;
|
||||
const srv = createServer().listen(PORT);
|
||||
const sio = new Server(srv);
|
||||
const httpServer = createServer().listen(0);
|
||||
const io = new Server(httpServer);
|
||||
const port = getPort(io);
|
||||
const net = require("net");
|
||||
const server = net.createServer();
|
||||
|
||||
const clientSocket = client(srv, { reconnection: false });
|
||||
const clientSocket = client(httpServer, { reconnection: false });
|
||||
|
||||
clientSocket.on("disconnect", () => {
|
||||
expect(sio.sockets.sockets.size).to.equal(0);
|
||||
server.listen(PORT);
|
||||
expect(io.sockets.sockets.size).to.equal(0);
|
||||
server.listen(port);
|
||||
});
|
||||
|
||||
clientSocket.on("connect", () => {
|
||||
expect(sio.sockets.sockets.size).to.equal(1);
|
||||
sio.close();
|
||||
expect(io.sockets.sockets.size).to.equal(1);
|
||||
io.close();
|
||||
});
|
||||
|
||||
server.once("listening", () => {
|
||||
@@ -327,30 +334,31 @@ describe("socket.io", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should be able to close sio sending a port", () => {
|
||||
const PORT = 54019;
|
||||
const sio = new Server(PORT);
|
||||
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, {
|
||||
const clientSocket = ioc("ws://0.0.0.0:" + port, {
|
||||
reconnection: false,
|
||||
});
|
||||
|
||||
clientSocket.on("disconnect", () => {
|
||||
expect(Object.keys(sio._nsps["/"].sockets).length).to.equal(0);
|
||||
server.listen(PORT);
|
||||
expect(io.sockets.sockets.size).to.equal(0);
|
||||
server.listen(port);
|
||||
});
|
||||
|
||||
clientSocket.on("connect", () => {
|
||||
expect(Object.keys(sio._nsps["/"].sockets).length).to.equal(1);
|
||||
sio.close();
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -802,6 +810,7 @@ describe("socket.io", () => {
|
||||
srv.listen(() => {
|
||||
const socket = client(srv);
|
||||
|
||||
// @ts-ignore
|
||||
socket.io.engine.write = () => {}; // prevent the client from sending a CONNECT packet
|
||||
|
||||
socket.on("disconnect", () => {
|
||||
@@ -812,30 +821,28 @@ describe("socket.io", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should close a client without namespace", (done) => {
|
||||
it("should exclude a specific socket when emitting", (done) => {
|
||||
const srv = createServer();
|
||||
const sio = new Server(srv, {
|
||||
connectTimeout: 100,
|
||||
});
|
||||
|
||||
sio.use((_, next) => {
|
||||
next(new Error("nope"));
|
||||
});
|
||||
const io = new Server(srv);
|
||||
|
||||
srv.listen(() => {
|
||||
const socket = client(srv);
|
||||
const socket1 = client(srv, "/");
|
||||
const socket2 = client(srv, "/");
|
||||
|
||||
const success = () => {
|
||||
socket.close();
|
||||
sio.close();
|
||||
socket2.on("a", () => {
|
||||
done(new Error("should not happen"));
|
||||
});
|
||||
socket1.on("a", () => {
|
||||
done();
|
||||
};
|
||||
});
|
||||
|
||||
socket.on("disconnect", success);
|
||||
socket2.on("connect", () => {
|
||||
io.except(socket2.id).emit("a");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should exclude a specific socket when emitting", (done) => {
|
||||
it("should exclude a specific socket when emitting (in a namespace)", (done) => {
|
||||
const srv = createServer();
|
||||
const sio = new Server(srv);
|
||||
|
||||
@@ -886,6 +893,17 @@ describe("socket.io", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should emit an 'new_namespace' event", (done) => {
|
||||
const sio = new Server();
|
||||
|
||||
sio.on("new_namespace", (namespace) => {
|
||||
expect(namespace.name).to.eql("/nsp");
|
||||
done();
|
||||
});
|
||||
|
||||
sio.of("/nsp");
|
||||
});
|
||||
|
||||
describe("dynamic namespaces", () => {
|
||||
it("should allow connections to dynamic namespaces with a regex", (done) => {
|
||||
const srv = createServer();
|
||||
@@ -942,6 +960,64 @@ describe("socket.io", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should emit an 'new_namespace' event for a dynamic namespace", (done) => {
|
||||
const srv = createServer();
|
||||
const sio = new Server(srv);
|
||||
srv.listen(() => {
|
||||
sio.of(/^\/dynamic-\d+$/);
|
||||
|
||||
sio.on("new_namespace", (namespace) => {
|
||||
expect(namespace.name).to.be("/dynamic-101");
|
||||
|
||||
socket.disconnect();
|
||||
srv.close();
|
||||
done();
|
||||
});
|
||||
|
||||
const socket = client(srv, "/dynamic-101");
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle race conditions with dynamic namespaces (#4136)", (done) => {
|
||||
const srv = createServer();
|
||||
const sio = new Server(srv);
|
||||
const counters = {
|
||||
connected: 0,
|
||||
created: 0,
|
||||
events: 0,
|
||||
};
|
||||
const buffer: callback[] = [];
|
||||
sio.on("new_namespace", (namespace) => {
|
||||
counters.created++;
|
||||
});
|
||||
srv.listen(() => {
|
||||
const handler = () => {
|
||||
if (++counters.events === 2) {
|
||||
expect(counters.created).to.equal(1);
|
||||
done();
|
||||
}
|
||||
};
|
||||
|
||||
sio
|
||||
.of((name, query, next) => {
|
||||
buffer.push(next);
|
||||
if (buffer.length === 2) {
|
||||
buffer.forEach((next) => next(null, true));
|
||||
}
|
||||
})
|
||||
.on("connection", (socket) => {
|
||||
if (++counters.connected === 2) {
|
||||
sio.of("/dynamic-101").emit("message");
|
||||
}
|
||||
});
|
||||
|
||||
let one = client(srv, "/dynamic-101");
|
||||
let two = client(srv, "/dynamic-101");
|
||||
one.on("message", handler);
|
||||
two.on("message", handler);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -955,7 +1031,9 @@ describe("socket.io", () => {
|
||||
clientSocket.off("connect", init);
|
||||
clientSocket.io.engine.close();
|
||||
|
||||
clientSocket.connect();
|
||||
process.nextTick(() => {
|
||||
clientSocket.connect();
|
||||
});
|
||||
clientSocket.on("connect", () => {
|
||||
done();
|
||||
});
|
||||
@@ -972,7 +1050,7 @@ describe("socket.io", () => {
|
||||
reconnectionDelay: 100,
|
||||
});
|
||||
clientSocket.on("connect", () => {
|
||||
srv.close();
|
||||
sio.close();
|
||||
});
|
||||
|
||||
clientSocket.io.on("reconnect_failed", () => {
|
||||
@@ -1326,6 +1404,58 @@ describe("socket.io", () => {
|
||||
}, 200);
|
||||
});
|
||||
|
||||
it("should emit only one consecutive volatile event with binary (ws)", (done) => {
|
||||
const srv = createServer();
|
||||
const sio = new Server(srv, { transports: ["websocket"] });
|
||||
|
||||
let counter = 0;
|
||||
srv.listen(() => {
|
||||
sio.on("connection", (s) => {
|
||||
// Wait to make sure there are no packets being sent for opening the connection
|
||||
setTimeout(() => {
|
||||
s.volatile.emit("ev", Buffer.from([1, 2, 3]));
|
||||
s.volatile.emit("ev", Buffer.from([4, 5, 6]));
|
||||
}, 20);
|
||||
});
|
||||
|
||||
const socket = client(srv, { transports: ["websocket"] });
|
||||
socket.on("ev", () => {
|
||||
counter++;
|
||||
});
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
expect(counter).to.be(1);
|
||||
done();
|
||||
}, 200);
|
||||
});
|
||||
|
||||
it("should broadcast only one consecutive volatile event with binary (ws)", (done) => {
|
||||
const srv = createServer();
|
||||
const sio = new Server(srv, { transports: ["websocket"] });
|
||||
|
||||
let counter = 0;
|
||||
srv.listen(() => {
|
||||
sio.on("connection", (s) => {
|
||||
// Wait to make sure there are no packets being sent for opening the connection
|
||||
setTimeout(() => {
|
||||
sio.volatile.emit("ev", Buffer.from([1, 2, 3]));
|
||||
sio.volatile.emit("ev", Buffer.from([4, 5, 6]));
|
||||
}, 20);
|
||||
});
|
||||
|
||||
const socket = client(srv, { transports: ["websocket"] });
|
||||
socket.on("ev", () => {
|
||||
counter++;
|
||||
});
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
expect(counter).to.be(1);
|
||||
done();
|
||||
}, 200);
|
||||
});
|
||||
|
||||
it("should emit regular events after trying a failed volatile event (polling)", (done) => {
|
||||
const srv = createServer();
|
||||
const sio = new Server(srv, { transports: ["polling"] });
|
||||
@@ -1700,7 +1830,7 @@ describe("socket.io", () => {
|
||||
reconnectionDelay: 100,
|
||||
});
|
||||
clientSocket.once("connect", () => {
|
||||
srv.close(() => {
|
||||
sio.close(() => {
|
||||
clientSocket.io.on("reconnect", () => {
|
||||
clientSocket.emit("ev", "payload");
|
||||
});
|
||||
@@ -1750,6 +1880,7 @@ describe("socket.io", () => {
|
||||
console.log(
|
||||
"\u001b[96mNote: warning expected and normal in test.\u001b[39m"
|
||||
);
|
||||
// @ts-ignore
|
||||
socket.io.engine.write("5woooot");
|
||||
setTimeout(() => {
|
||||
done();
|
||||
@@ -1769,6 +1900,7 @@ describe("socket.io", () => {
|
||||
console.log(
|
||||
"\u001b[96mNote: warning expected and normal in test.\u001b[39m"
|
||||
);
|
||||
// @ts-ignore
|
||||
socket.io.engine.write('44["handle me please"]');
|
||||
setTimeout(() => {
|
||||
done();
|
||||
@@ -1789,6 +1921,7 @@ describe("socket.io", () => {
|
||||
done();
|
||||
});
|
||||
s.conn.on("upgrade", () => {
|
||||
// @ts-ignore
|
||||
socket.io.engine.write("5woooot");
|
||||
});
|
||||
});
|
||||
@@ -1806,6 +1939,7 @@ describe("socket.io", () => {
|
||||
done();
|
||||
});
|
||||
s.conn.on("upgrade", () => {
|
||||
// @ts-ignore
|
||||
socket.io.engine.write("5");
|
||||
});
|
||||
});
|
||||
@@ -2573,6 +2707,25 @@ describe("socket.io", () => {
|
||||
if (++count === 2) done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should only set `connected` to true after the middleware execution", (done) => {
|
||||
const httpServer = createServer();
|
||||
const io = new Server(httpServer);
|
||||
|
||||
const clientSocket = client(httpServer, "/");
|
||||
|
||||
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(io, clientSocket, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("socket middleware", () => {
|
||||
@@ -2712,4 +2865,6 @@ describe("socket.io", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
require("./socket-timeout");
|
||||
});
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
import type { Server } from "../..";
|
||||
import {
|
||||
io as ioc,
|
||||
ManagerOptions,
|
||||
Socket as ClientSocket,
|
||||
SocketOptions,
|
||||
} from "socket.io-client";
|
||||
|
||||
const expect = require("expect.js");
|
||||
const i = expect.stringify;
|
||||
|
||||
@@ -20,3 +28,19 @@ expect.Assertion.prototype.contain = function (...args) {
|
||||
}
|
||||
return contain.apply(this, args);
|
||||
};
|
||||
|
||||
export function createClient(
|
||||
io: Server,
|
||||
nsp: string,
|
||||
opts?: 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) {
|
||||
io.close();
|
||||
client.disconnect();
|
||||
done();
|
||||
}
|
||||
|
||||
197
test/uws.ts
Normal file
197
test/uws.ts
Normal file
@@ -0,0 +1,197 @@
|
||||
import { App, us_socket_local_port } from "uWebSockets.js";
|
||||
import { Server } from "..";
|
||||
import { io as ioc, Socket as ClientSocket } from "socket.io-client";
|
||||
import request from "supertest";
|
||||
import expect from "expect.js";
|
||||
|
||||
const createPartialDone = (done: (err?: Error) => void, count: number) => {
|
||||
let i = 0;
|
||||
return () => {
|
||||
if (++i === count) {
|
||||
done();
|
||||
} else if (i > count) {
|
||||
done(new Error(`partialDone() called too many times: ${i} > ${count}`));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const shouldNotHappen = (done) => () => done(new Error("should not happen"));
|
||||
|
||||
describe("socket.io with uWebSocket.js-based engine", () => {
|
||||
let io: Server,
|
||||
port: number,
|
||||
client: ClientSocket,
|
||||
clientWSOnly: ClientSocket,
|
||||
clientPollingOnly: ClientSocket,
|
||||
clientCustomNamespace: ClientSocket;
|
||||
|
||||
beforeEach((done) => {
|
||||
const app = App();
|
||||
io = new Server();
|
||||
io.attachApp(app);
|
||||
|
||||
io.of("/custom");
|
||||
|
||||
app.listen(0, (listenSocket) => {
|
||||
port = us_socket_local_port(listenSocket);
|
||||
|
||||
client = ioc(`http://localhost:${port}`);
|
||||
clientWSOnly = ioc(`http://localhost:${port}`, {
|
||||
transports: ["websocket"],
|
||||
});
|
||||
clientPollingOnly = ioc(`http://localhost:${port}`, {
|
||||
transports: ["polling"],
|
||||
});
|
||||
clientCustomNamespace = ioc(`http://localhost:${port}/custom`);
|
||||
});
|
||||
|
||||
const partialDone = createPartialDone(done, 4);
|
||||
client.on("connect", partialDone);
|
||||
clientWSOnly.on("connect", partialDone);
|
||||
clientPollingOnly.on("connect", partialDone);
|
||||
clientCustomNamespace.on("connect", partialDone);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
io.close();
|
||||
client.disconnect();
|
||||
clientWSOnly.disconnect();
|
||||
clientPollingOnly.disconnect();
|
||||
clientCustomNamespace.disconnect();
|
||||
});
|
||||
|
||||
it("should broadcast", (done) => {
|
||||
const partialDone = createPartialDone(done, 3);
|
||||
|
||||
client.on("hello", partialDone);
|
||||
clientWSOnly.on("hello", partialDone);
|
||||
clientPollingOnly.on("hello", partialDone);
|
||||
clientCustomNamespace.on("hello", shouldNotHappen(done));
|
||||
|
||||
io.emit("hello");
|
||||
});
|
||||
|
||||
it("should broadcast in a namespace", (done) => {
|
||||
client.on("hello", shouldNotHappen(done));
|
||||
clientWSOnly.on("hello", shouldNotHappen(done));
|
||||
clientPollingOnly.on("hello", shouldNotHappen(done));
|
||||
clientCustomNamespace.on("hello", done);
|
||||
|
||||
io.of("/custom").emit("hello");
|
||||
});
|
||||
|
||||
it("should broadcast in a dynamic namespace", (done) => {
|
||||
const dynamicNamespace = io.of(/\/dynamic-\d+/);
|
||||
const dynamicClient = clientWSOnly.io.socket("/dynamic-101");
|
||||
|
||||
dynamicClient.on("connect", () => {
|
||||
dynamicNamespace.emit("hello");
|
||||
});
|
||||
|
||||
dynamicClient.on("hello", () => {
|
||||
dynamicClient.disconnect();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should broadcast binary content", (done) => {
|
||||
const partialDone = createPartialDone(done, 3);
|
||||
|
||||
client.on("hello", partialDone);
|
||||
clientWSOnly.on("hello", partialDone);
|
||||
clientPollingOnly.on("hello", partialDone);
|
||||
clientCustomNamespace.on("hello", shouldNotHappen(done));
|
||||
|
||||
io.emit("hello", Buffer.from([1, 2, 3]));
|
||||
});
|
||||
|
||||
it("should broadcast volatile packet with binary content", (done) => {
|
||||
const partialDone = createPartialDone(done, 3);
|
||||
|
||||
client.on("hello", partialDone);
|
||||
clientWSOnly.on("hello", partialDone);
|
||||
clientPollingOnly.on("hello", partialDone);
|
||||
clientCustomNamespace.on("hello", shouldNotHappen(done));
|
||||
|
||||
// wait to make sure there are no packets being sent for opening the connection
|
||||
setTimeout(() => {
|
||||
io.volatile.emit("hello", Buffer.from([1, 2, 3]));
|
||||
}, 20);
|
||||
});
|
||||
|
||||
it("should broadcast in a room", (done) => {
|
||||
const partialDone = createPartialDone(done, 2);
|
||||
|
||||
client.on("hello", shouldNotHappen(done));
|
||||
clientWSOnly.on("hello", partialDone);
|
||||
clientPollingOnly.on("hello", partialDone);
|
||||
clientCustomNamespace.on("hello", shouldNotHappen(done));
|
||||
|
||||
io.of("/").sockets.get(clientWSOnly.id)!.join("room1");
|
||||
io.of("/").sockets.get(clientPollingOnly.id)!.join("room1");
|
||||
|
||||
io.to("room1").emit("hello");
|
||||
});
|
||||
|
||||
it("should broadcast in multiple rooms", (done) => {
|
||||
const partialDone = createPartialDone(done, 2);
|
||||
|
||||
client.on("hello", shouldNotHappen(done));
|
||||
clientWSOnly.on("hello", partialDone);
|
||||
clientPollingOnly.on("hello", partialDone);
|
||||
clientCustomNamespace.on("hello", shouldNotHappen(done));
|
||||
|
||||
io.of("/").sockets.get(clientWSOnly.id)!.join("room1");
|
||||
io.of("/").sockets.get(clientPollingOnly.id)!.join("room2");
|
||||
|
||||
io.to(["room1", "room2"]).emit("hello");
|
||||
});
|
||||
|
||||
it("should broadcast in all but a given room", (done) => {
|
||||
const partialDone = createPartialDone(done, 2);
|
||||
|
||||
client.on("hello", partialDone);
|
||||
clientWSOnly.on("hello", partialDone);
|
||||
clientPollingOnly.on("hello", shouldNotHappen(done));
|
||||
clientCustomNamespace.on("hello", shouldNotHappen(done));
|
||||
|
||||
io.of("/").sockets.get(clientWSOnly.id)!.join("room1");
|
||||
io.of("/").sockets.get(clientPollingOnly.id)!.join("room2");
|
||||
|
||||
io.except("room2").emit("hello");
|
||||
});
|
||||
|
||||
it("should work even after leaving room", (done) => {
|
||||
const partialDone = createPartialDone(done, 2);
|
||||
|
||||
client.on("hello", partialDone);
|
||||
clientWSOnly.on("hello", shouldNotHappen(done));
|
||||
clientPollingOnly.on("hello", partialDone);
|
||||
clientCustomNamespace.on("hello", shouldNotHappen(done));
|
||||
|
||||
io.of("/").sockets.get(client.id)!.join("room1");
|
||||
io.of("/").sockets.get(clientPollingOnly.id)!.join("room1");
|
||||
|
||||
io.of("/").sockets.get(clientWSOnly.id)!.join("room1");
|
||||
io.of("/").sockets.get(clientWSOnly.id)!.leave("room1");
|
||||
|
||||
io.to("room1").emit("hello");
|
||||
});
|
||||
|
||||
it("should serve static files", (done) => {
|
||||
const clientVersion = require("socket.io-client/package.json").version;
|
||||
|
||||
request(`http://localhost:${port}`)
|
||||
.get("/socket.io/socket.io.js")
|
||||
.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();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user