mirror of
https://github.com/socketio/socket.io.git
synced 2026-01-13 08:57:59 -05:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12221f296d | ||
|
|
6f4bd7f8e7 | ||
|
|
4f2e9a716d | ||
|
|
9e8f288ca9 | ||
|
|
86eb4227b2 | ||
|
|
cf873fd831 | ||
|
|
0d10e6131b | ||
|
|
10aafbbc16 | ||
|
|
f34cfca26d | ||
|
|
d412e876b8 | ||
|
|
f05a4a6f82 | ||
|
|
2c883f5d4e | ||
|
|
161091dd4c | ||
|
|
d52532b7be | ||
|
|
6b1d7901db | ||
|
|
b55892ae80 | ||
|
|
233650c222 | ||
|
|
9925746c8e | ||
|
|
de8dffd252 |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [10.x, 12.x, 14.x]
|
||||
node-version: [10.x, 12.x, 14.x, 15.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
26
CHANGELOG.md
26
CHANGELOG.md
@@ -1,3 +1,29 @@
|
||||
## [3.1.1](https://github.com/socketio/socket.io/compare/3.1.0...3.1.1) (2021-02-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* properly parse the CONNECT packet in v2 compatibility mode ([6f4bd7f](https://github.com/socketio/socket.io/commit/6f4bd7f8e7c41a075a8014565330a77c38b03a8d))
|
||||
* **typings:** add return types and general-case overload signatures ([#3776](https://github.com/socketio/socket.io/issues/3776)) ([9e8f288](https://github.com/socketio/socket.io/commit/9e8f288ca9f14f91064b8d3cce5946f7d23d407c))
|
||||
* **typings:** update the types of "query", "auth" and "headers" ([4f2e9a7](https://github.com/socketio/socket.io/commit/4f2e9a716d9835b550c8fd9a9b429ebf069c2895))
|
||||
|
||||
|
||||
# [3.1.0](https://github.com/socketio/socket.io/compare/3.0.5...3.1.0) (2021-01-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* confirm a weak but matching ETag ([#3485](https://github.com/socketio/socket.io/issues/3485)) ([161091d](https://github.com/socketio/socket.io/commit/161091dd4c9e1b1610ac3d45d964195e63d92b94))
|
||||
* **esm:** export the Namespace and Socket class ([#3699](https://github.com/socketio/socket.io/issues/3699)) ([233650c](https://github.com/socketio/socket.io/commit/233650c22209708b5fccc4349c38d2fa1b465d8f))
|
||||
* add support for Socket.IO v2 clients ([9925746](https://github.com/socketio/socket.io/commit/9925746c8ee3a6522bd640b5d586c83f04f2f1ba))
|
||||
* add room events ([155fa63](https://github.com/socketio/socket.io-adapter/commit/155fa6333a504036e99a33667dc0397f6aede25e))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* allow integers as event names ([1c220dd](https://github.com/socketio/socket.io-parser/commit/1c220ddbf45ea4b44bc8dbf6f9ae245f672ba1b9))
|
||||
|
||||
|
||||
## [3.0.5](https://github.com/socketio/socket.io/compare/3.0.4...3.0.5) (2021-01-05)
|
||||
|
||||
|
||||
|
||||
11
Readme.md
11
Readme.md
@@ -1,6 +1,5 @@
|
||||
|
||||
# socket.io
|
||||
|
||||
[](https://repl.it/github/socketio/socket.io)
|
||||
[](#backers) [](#sponsors)
|
||||
[](https://github.com/socketio/socket.io/actions)
|
||||
[](https://david-dm.org/socketio/socket.io)
|
||||
@@ -22,6 +21,8 @@ Some implementations in other languages are also available:
|
||||
- [C++](https://github.com/socketio/socket.io-client-cpp)
|
||||
- [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)
|
||||
|
||||
Its main features are:
|
||||
|
||||
@@ -35,7 +36,7 @@ For this purpose, it relies on [Engine.IO](https://github.com/socketio/engine.io
|
||||
|
||||
#### Auto-reconnection support
|
||||
|
||||
Unless instructed otherwise a disconnected client will try to reconnect forever, until the server is available again. Please see the available reconnection options [here](https://github.com/socketio/socket.io-client/blob/master/docs/API.md#new-managerurl-options).
|
||||
Unless instructed otherwise a disconnected client will try to reconnect forever, until the server is available again. Please see the available reconnection options [here](https://socket.io/docs/v3/client-api/#new-Manager-url-options).
|
||||
|
||||
#### Disconnection detection
|
||||
|
||||
@@ -84,7 +85,11 @@ This is a useful feature to send notifications to a group of users, or to a give
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
// with npm
|
||||
npm install socket.io
|
||||
|
||||
// with yarn
|
||||
yarn add socket.io
|
||||
```
|
||||
|
||||
## How to use
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
4
client-dist/socket.io.min.js
vendored
4
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
4
client-dist/socket.io.msgpack.min.js
vendored
4
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
@@ -1,36 +1,36 @@
|
||||
$(function() {
|
||||
var FADE_TIME = 150; // ms
|
||||
var TYPING_TIMER_LENGTH = 400; // ms
|
||||
var COLORS = [
|
||||
const FADE_TIME = 150; // ms
|
||||
const TYPING_TIMER_LENGTH = 400; // ms
|
||||
const COLORS = [
|
||||
'#e21400', '#91580f', '#f8a700', '#f78b00',
|
||||
'#58dc00', '#287b00', '#a8f07a', '#4ae8c4',
|
||||
'#3b88eb', '#3824aa', '#a700ff', '#d300e7'
|
||||
];
|
||||
|
||||
// Initialize variables
|
||||
var $window = $(window);
|
||||
var $usernameInput = $('.usernameInput'); // Input for username
|
||||
var $messages = $('.messages'); // Messages area
|
||||
var $inputMessage = $('.inputMessage'); // Input message input box
|
||||
const $window = $(window);
|
||||
const $usernameInput = $('.usernameInput'); // Input for username
|
||||
const $messages = $('.messages'); // Messages area
|
||||
const $inputMessage = $('.inputMessage'); // Input message input box
|
||||
|
||||
var $loginPage = $('.login.page'); // The login page
|
||||
var $chatPage = $('.chat.page'); // The chatroom page
|
||||
const $loginPage = $('.login.page'); // The login page
|
||||
const $chatPage = $('.chat.page'); // The chatroom page
|
||||
|
||||
const socket = io();
|
||||
|
||||
// Prompt for setting a username
|
||||
var username;
|
||||
var connected = false;
|
||||
var typing = false;
|
||||
var lastTypingTime;
|
||||
var $currentInput = $usernameInput.focus();
|
||||
|
||||
var socket = io();
|
||||
let username;
|
||||
let connected = false;
|
||||
let typing = false;
|
||||
let lastTypingTime;
|
||||
let $currentInput = $usernameInput.focus();
|
||||
|
||||
const addParticipantsMessage = (data) => {
|
||||
var message = '';
|
||||
let message = '';
|
||||
if (data.numUsers === 1) {
|
||||
message += "there's 1 participant";
|
||||
message += `there's 1 participant`;
|
||||
} else {
|
||||
message += "there are " + data.numUsers + " participants";
|
||||
message += `there are ${data.numUsers} participants`;
|
||||
}
|
||||
log(message);
|
||||
}
|
||||
@@ -53,45 +53,41 @@ $(function() {
|
||||
|
||||
// Sends a chat message
|
||||
const sendMessage = () => {
|
||||
var message = $inputMessage.val();
|
||||
let message = $inputMessage.val();
|
||||
// Prevent markup from being injected into the message
|
||||
message = cleanInput(message);
|
||||
// if there is a non-empty message and a socket connection
|
||||
if (message && connected) {
|
||||
$inputMessage.val('');
|
||||
addChatMessage({
|
||||
username: username,
|
||||
message: message
|
||||
});
|
||||
addChatMessage({ username, message });
|
||||
// tell server to execute 'new message' and send along one parameter
|
||||
socket.emit('new message', message);
|
||||
}
|
||||
}
|
||||
|
||||
// Log a message
|
||||
const log = (message, options) => {
|
||||
var $el = $('<li>').addClass('log').text(message);
|
||||
const log = (message, options) => {
|
||||
const $el = $('<li>').addClass('log').text(message);
|
||||
addMessageElement($el, options);
|
||||
}
|
||||
|
||||
// Adds the visual chat message to the message list
|
||||
const addChatMessage = (data, options) => {
|
||||
// Don't fade the message in if there is an 'X was typing'
|
||||
var $typingMessages = getTypingMessages(data);
|
||||
options = options || {};
|
||||
const $typingMessages = getTypingMessages(data);
|
||||
if ($typingMessages.length !== 0) {
|
||||
options.fade = false;
|
||||
$typingMessages.remove();
|
||||
}
|
||||
|
||||
var $usernameDiv = $('<span class="username"/>')
|
||||
const $usernameDiv = $('<span class="username"/>')
|
||||
.text(data.username)
|
||||
.css('color', getUsernameColor(data.username));
|
||||
var $messageBodyDiv = $('<span class="messageBody">')
|
||||
const $messageBodyDiv = $('<span class="messageBody">')
|
||||
.text(data.message);
|
||||
|
||||
var typingClass = data.typing ? 'typing' : '';
|
||||
var $messageDiv = $('<li class="message"/>')
|
||||
const typingClass = data.typing ? 'typing' : '';
|
||||
const $messageDiv = $('<li class="message"/>')
|
||||
.data('username', data.username)
|
||||
.addClass(typingClass)
|
||||
.append($usernameDiv, $messageBodyDiv);
|
||||
@@ -119,8 +115,7 @@ $(function() {
|
||||
// options.prepend - If the element should prepend
|
||||
// all other messages (default = false)
|
||||
const addMessageElement = (el, options) => {
|
||||
var $el = $(el);
|
||||
|
||||
const $el = $(el);
|
||||
// Setup default options
|
||||
if (!options) {
|
||||
options = {};
|
||||
@@ -141,6 +136,7 @@ $(function() {
|
||||
} else {
|
||||
$messages.append($el);
|
||||
}
|
||||
|
||||
$messages[0].scrollTop = $messages[0].scrollHeight;
|
||||
}
|
||||
|
||||
@@ -159,8 +155,8 @@ $(function() {
|
||||
lastTypingTime = (new Date()).getTime();
|
||||
|
||||
setTimeout(() => {
|
||||
var typingTimer = (new Date()).getTime();
|
||||
var timeDiff = typingTimer - lastTypingTime;
|
||||
const typingTimer = (new Date()).getTime();
|
||||
const timeDiff = typingTimer - lastTypingTime;
|
||||
if (timeDiff >= TYPING_TIMER_LENGTH && typing) {
|
||||
socket.emit('stop typing');
|
||||
typing = false;
|
||||
@@ -179,12 +175,12 @@ $(function() {
|
||||
// Gets the color of a username through our hash function
|
||||
const getUsernameColor = (username) => {
|
||||
// Compute hash code
|
||||
var hash = 7;
|
||||
for (var i = 0; i < username.length; i++) {
|
||||
hash = username.charCodeAt(i) + (hash << 5) - hash;
|
||||
let hash = 7;
|
||||
for (let i = 0; i < username.length; i++) {
|
||||
hash = username.charCodeAt(i) + (hash << 5) - hash;
|
||||
}
|
||||
// Calculate color
|
||||
var index = Math.abs(hash % COLORS.length);
|
||||
const index = Math.abs(hash % COLORS.length);
|
||||
return COLORS[index];
|
||||
}
|
||||
|
||||
@@ -229,7 +225,7 @@ $(function() {
|
||||
socket.on('login', (data) => {
|
||||
connected = true;
|
||||
// Display the welcome message
|
||||
var message = "Welcome to Socket.IO Chat – ";
|
||||
const message = 'Welcome to Socket.IO Chat – ';
|
||||
log(message, {
|
||||
prepend: true
|
||||
});
|
||||
@@ -243,13 +239,13 @@ $(function() {
|
||||
|
||||
// Whenever the server emits 'user joined', log it in the chat body
|
||||
socket.on('user joined', (data) => {
|
||||
log(data.username + ' joined');
|
||||
log(`${data.username} joined`);
|
||||
addParticipantsMessage(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'user left', log it in the chat body
|
||||
socket.on('user left', (data) => {
|
||||
log(data.username + ' left');
|
||||
log(`${data.username} left`);
|
||||
addParticipantsMessage(data);
|
||||
removeChatTyping(data);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM mhart/alpine-node:6
|
||||
FROM node:14-alpine
|
||||
|
||||
# Create app directory
|
||||
RUN mkdir -p /usr/src/app
|
||||
@@ -6,7 +6,7 @@ WORKDIR /usr/src/app
|
||||
|
||||
# Install app dependencies
|
||||
COPY package.json /usr/src/app/
|
||||
RUN npm install
|
||||
RUN npm install --prod
|
||||
|
||||
# Bundle app source
|
||||
COPY . /usr/src/app
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
"license": "BSD",
|
||||
"dependencies": {
|
||||
"express": "4.13.4",
|
||||
"socket.io": "^1.7.2",
|
||||
"socket.io-redis": "^3.0.0"
|
||||
"socket.io": "^3.1.0",
|
||||
"socket.io-redis": "^6.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
|
||||
@@ -3,6 +3,8 @@ Listen 80
|
||||
|
||||
ServerName localhost
|
||||
|
||||
LoadModule mpm_event_module modules/mod_mpm_event.so
|
||||
|
||||
LoadModule authn_file_module modules/mod_authn_file.so
|
||||
LoadModule authn_core_module modules/mod_authn_core.so
|
||||
LoadModule authz_host_module modules/mod_authz_host.so
|
||||
|
||||
@@ -6,7 +6,7 @@ WORKDIR /usr/src/app
|
||||
|
||||
# Install app dependencies
|
||||
COPY package.json /usr/src/app/
|
||||
RUN npm install
|
||||
RUN npm install --prod
|
||||
|
||||
# Bundle app source
|
||||
COPY . /usr/src/app
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
"license": "BSD",
|
||||
"dependencies": {
|
||||
"express": "4.13.4",
|
||||
"socket.io": "^1.7.2",
|
||||
"socket.io-redis": "^3.0.0"
|
||||
"socket.io": "^3.1.0",
|
||||
"socket.io-redis": "^6.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
|
||||
@@ -22,6 +22,16 @@ Each node connects to the redis backend, which will enable to broadcast to every
|
||||
$ docker-compose stop server-george
|
||||
```
|
||||
|
||||
A `client` container is included in the `docker-compose.yml` file, in order to test the routing.
|
||||
|
||||
You can create additional `client` containers with:
|
||||
|
||||
```
|
||||
$ docker-compose up -d --scale=client=10 client
|
||||
# and then
|
||||
$ docker-compose logs client
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Multiple users can join a chat room by each entering a unique username
|
||||
|
||||
15
examples/cluster-nginx/client/Dockerfile
Normal file
15
examples/cluster-nginx/client/Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
||||
FROM node:14-alpine
|
||||
|
||||
# Create app directory
|
||||
RUN mkdir -p /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Install app dependencies
|
||||
COPY package.json /usr/src/app/
|
||||
RUN npm install --prod
|
||||
|
||||
# Bundle app source
|
||||
COPY . /usr/src/app
|
||||
|
||||
EXPOSE 3000
|
||||
CMD [ "npm", "start" ]
|
||||
13
examples/cluster-nginx/client/index.js
Normal file
13
examples/cluster-nginx/client/index.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const socket = require('socket.io-client')('ws://nginx');
|
||||
|
||||
socket.on('connect', () => {
|
||||
console.log('connected');
|
||||
});
|
||||
|
||||
socket.on('my-name-is', (serverName) => {
|
||||
console.log(`connected to ${serverName}`);
|
||||
});
|
||||
|
||||
socket.on('disconnect', (reason) => {
|
||||
console.log(`disconnected due to ${reason}`);
|
||||
});
|
||||
15
examples/cluster-nginx/client/package.json
Normal file
15
examples/cluster-nginx/client/package.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "socket.io-chat",
|
||||
"version": "0.0.0",
|
||||
"description": "A simple chat client using socket.io",
|
||||
"main": "index.js",
|
||||
"author": "Grant Timmerman",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"socket.io-client": "^3.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,11 @@ server-ringo:
|
||||
environment:
|
||||
- NAME=Ringo
|
||||
|
||||
client:
|
||||
build: ./client
|
||||
links:
|
||||
- nginx
|
||||
|
||||
redis:
|
||||
image: redis:alpine
|
||||
expose:
|
||||
|
||||
@@ -24,8 +24,12 @@ http {
|
||||
}
|
||||
|
||||
upstream nodes {
|
||||
# enable sticky session
|
||||
ip_hash;
|
||||
# enable sticky session with either "hash" (uses the complete IP address)
|
||||
hash $remote_addr consistent;
|
||||
# or "ip_hash" (uses the first three octets of the client IPv4 address, or the entire IPv6 address)
|
||||
# ip_hash;
|
||||
# or "sticky" (needs commercial subscription)
|
||||
# sticky cookie srv_id expires=1h domain=.example.com path=/;
|
||||
|
||||
server server-john:3000;
|
||||
server server-paul:3000;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM mhart/alpine-node:6
|
||||
FROM node:14-alpine
|
||||
|
||||
# Create app directory
|
||||
RUN mkdir -p /usr/src/app
|
||||
@@ -6,7 +6,7 @@ WORKDIR /usr/src/app
|
||||
|
||||
# Install app dependencies
|
||||
COPY package.json /usr/src/app/
|
||||
RUN npm install
|
||||
RUN npm install --prod
|
||||
|
||||
# Bundle app source
|
||||
COPY . /usr/src/app
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"express": "4.13.4",
|
||||
"socket.io": "^1.7.2",
|
||||
"socket.io-redis": "^3.0.0"
|
||||
"socket.io": "^3.1.0",
|
||||
"socket.io-redis": "^6.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
|
||||
22
examples/cluster-traefik/README.md
Normal file
22
examples/cluster-traefik/README.md
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
# Socket.IO Chat with traefik & [redis](https://redis.io/)
|
||||
|
||||
A simple chat demo for Socket.IO
|
||||
|
||||
## How to use
|
||||
|
||||
Install [Docker Compose](https://docs.docker.com/compose/install/), then:
|
||||
|
||||
```
|
||||
$ docker-compose up -d
|
||||
```
|
||||
|
||||
And then point your browser to `http://localhost:3000`.
|
||||
|
||||
You can then scale the server to multiple instances:
|
||||
|
||||
```
|
||||
$ docker-compose up -d --scale=server=7
|
||||
```
|
||||
|
||||
The session stickiness, which is [required](https://socket.io/docs/v3/using-multiple-nodes/) when using multiple Socket.IO server instances, is achieved with a cookie. More information [here](https://doc.traefik.io/traefik/v2.0/routing/services/#sticky-sessions).
|
||||
27
examples/cluster-traefik/docker-compose.yml
Normal file
27
examples/cluster-traefik/docker-compose.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:2.4
|
||||
volumes:
|
||||
- ./traefik.yml:/etc/traefik/traefik.yml
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
links:
|
||||
- server
|
||||
ports:
|
||||
- "3000:80"
|
||||
- "8080:8080"
|
||||
|
||||
server:
|
||||
build: ./server
|
||||
links:
|
||||
- redis
|
||||
labels:
|
||||
- "traefik.http.routers.chat.rule=PathPrefix(`/`)"
|
||||
- traefik.http.services.chat.loadBalancer.sticky.cookie.name=server_id
|
||||
- traefik.http.services.chat.loadBalancer.sticky.cookie.httpOnly=true
|
||||
|
||||
redis:
|
||||
image: redis:6-alpine
|
||||
labels:
|
||||
- traefik.enable=false
|
||||
15
examples/cluster-traefik/server/Dockerfile
Normal file
15
examples/cluster-traefik/server/Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
||||
FROM node:14-alpine
|
||||
|
||||
# Create app directory
|
||||
RUN mkdir -p /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Install app dependencies
|
||||
COPY package.json /usr/src/app/
|
||||
RUN npm install --prod
|
||||
|
||||
# Bundle app source
|
||||
COPY . /usr/src/app
|
||||
|
||||
EXPOSE 3000
|
||||
CMD [ "npm", "start" ]
|
||||
83
examples/cluster-traefik/server/index.js
Normal file
83
examples/cluster-traefik/server/index.js
Normal file
@@ -0,0 +1,83 @@
|
||||
// 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 crypto = require('crypto');
|
||||
var serverName = crypto.randomBytes(3).toString('hex');
|
||||
|
||||
io.adapter(redis({ host: 'redis', port: 6379 }));
|
||||
|
||||
server.listen(port, function () {
|
||||
console.log('Server listening at port %d', port);
|
||||
console.log('Hello, I\'m %s, how can I help?', serverName);
|
||||
});
|
||||
|
||||
// Routing
|
||||
app.use(express.static(__dirname + '/public'));
|
||||
|
||||
// Chatroom
|
||||
|
||||
var numUsers = 0;
|
||||
|
||||
io.on('connection', function (socket) {
|
||||
socket.emit('my-name-is', serverName);
|
||||
|
||||
var addedUser = false;
|
||||
|
||||
// when the client emits 'new message', this listens and executes
|
||||
socket.on('new message', function (data) {
|
||||
// we tell the client to execute 'new message'
|
||||
socket.broadcast.emit('new message', {
|
||||
username: socket.username,
|
||||
message: data
|
||||
});
|
||||
});
|
||||
|
||||
// when the client emits 'add user', this listens and executes
|
||||
socket.on('add user', function (username) {
|
||||
if (addedUser) return;
|
||||
|
||||
// we store the username in the socket session for this client
|
||||
socket.username = username;
|
||||
++numUsers;
|
||||
addedUser = true;
|
||||
socket.emit('login', {
|
||||
numUsers: numUsers
|
||||
});
|
||||
// echo globally (all clients) that a person has connected
|
||||
socket.broadcast.emit('user joined', {
|
||||
username: socket.username,
|
||||
numUsers: numUsers
|
||||
});
|
||||
});
|
||||
|
||||
// when the client emits 'typing', we broadcast it to others
|
||||
socket.on('typing', function () {
|
||||
socket.broadcast.emit('typing', {
|
||||
username: socket.username
|
||||
});
|
||||
});
|
||||
|
||||
// when the client emits 'stop typing', we broadcast it to others
|
||||
socket.on('stop typing', function () {
|
||||
socket.broadcast.emit('stop typing', {
|
||||
username: socket.username
|
||||
});
|
||||
});
|
||||
|
||||
// when the user disconnects.. perform this
|
||||
socket.on('disconnect', function () {
|
||||
if (addedUser) {
|
||||
--numUsers;
|
||||
|
||||
// echo globally that this client has left
|
||||
socket.broadcast.emit('user left', {
|
||||
username: socket.username,
|
||||
numUsers: numUsers
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
17
examples/cluster-traefik/server/package.json
Normal file
17
examples/cluster-traefik/server/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "socket.io-chat",
|
||||
"version": "0.0.0",
|
||||
"description": "A simple chat client using socket.io",
|
||||
"main": "index.js",
|
||||
"author": "Grant Timmerman",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"express": "4.13.4",
|
||||
"socket.io": "^3.1.0",
|
||||
"socket.io-redis": "^6.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
}
|
||||
}
|
||||
28
examples/cluster-traefik/server/public/index.html
Normal file
28
examples/cluster-traefik/server/public/index.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Socket.IO Chat Example</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<ul class="pages">
|
||||
<li class="chat page">
|
||||
<div class="chatArea">
|
||||
<ul class="messages"></ul>
|
||||
</div>
|
||||
<input class="inputMessage" placeholder="Type here..."/>
|
||||
</li>
|
||||
<li class="login page">
|
||||
<div class="form">
|
||||
<h3 class="title">What's your nickname?</h3>
|
||||
<input class="usernameInput" type="text" maxlength="14" />
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-1.10.2.min.js"></script>
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script src="/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
286
examples/cluster-traefik/server/public/main.js
Normal file
286
examples/cluster-traefik/server/public/main.js
Normal file
@@ -0,0 +1,286 @@
|
||||
$(function() {
|
||||
var FADE_TIME = 150; // ms
|
||||
var TYPING_TIMER_LENGTH = 400; // ms
|
||||
var COLORS = [
|
||||
'#e21400', '#91580f', '#f8a700', '#f78b00',
|
||||
'#58dc00', '#287b00', '#a8f07a', '#4ae8c4',
|
||||
'#3b88eb', '#3824aa', '#a700ff', '#d300e7'
|
||||
];
|
||||
|
||||
// Initialize variables
|
||||
var $window = $(window);
|
||||
var $usernameInput = $('.usernameInput'); // Input for username
|
||||
var $messages = $('.messages'); // Messages area
|
||||
var $inputMessage = $('.inputMessage'); // Input message input box
|
||||
|
||||
var $loginPage = $('.login.page'); // The login page
|
||||
var $chatPage = $('.chat.page'); // The chatroom page
|
||||
|
||||
// Prompt for setting a username
|
||||
var username;
|
||||
var connected = false;
|
||||
var typing = false;
|
||||
var lastTypingTime;
|
||||
var $currentInput = $usernameInput.focus();
|
||||
|
||||
var socket = io();
|
||||
|
||||
function addParticipantsMessage (data) {
|
||||
var message = '';
|
||||
if (data.numUsers === 1) {
|
||||
message += "there's 1 participant";
|
||||
} else {
|
||||
message += "there are " + data.numUsers + " participants";
|
||||
}
|
||||
log(message);
|
||||
}
|
||||
|
||||
// Sets the client's username
|
||||
function setUsername () {
|
||||
username = cleanInput($usernameInput.val().trim());
|
||||
|
||||
// If the username is valid
|
||||
if (username) {
|
||||
$loginPage.fadeOut();
|
||||
$chatPage.show();
|
||||
$loginPage.off('click');
|
||||
$currentInput = $inputMessage.focus();
|
||||
|
||||
// Tell the server your username
|
||||
socket.emit('add user', username);
|
||||
}
|
||||
}
|
||||
|
||||
// Sends a chat message
|
||||
function sendMessage () {
|
||||
var message = $inputMessage.val();
|
||||
// Prevent markup from being injected into the message
|
||||
message = cleanInput(message);
|
||||
// if there is a non-empty message and a socket connection
|
||||
if (message && connected) {
|
||||
$inputMessage.val('');
|
||||
addChatMessage({
|
||||
username: username,
|
||||
message: message
|
||||
});
|
||||
// tell server to execute 'new message' and send along one parameter
|
||||
socket.emit('new message', message);
|
||||
}
|
||||
}
|
||||
|
||||
// Log a message
|
||||
function log (message, options) {
|
||||
var $el = $('<li>').addClass('log').text(message);
|
||||
addMessageElement($el, options);
|
||||
}
|
||||
|
||||
// Adds the visual chat message to the message list
|
||||
function addChatMessage (data, options) {
|
||||
// Don't fade the message in if there is an 'X was typing'
|
||||
var $typingMessages = getTypingMessages(data);
|
||||
options = options || {};
|
||||
if ($typingMessages.length !== 0) {
|
||||
options.fade = false;
|
||||
$typingMessages.remove();
|
||||
}
|
||||
|
||||
var $usernameDiv = $('<span class="username"/>')
|
||||
.text(data.username)
|
||||
.css('color', getUsernameColor(data.username));
|
||||
var $messageBodyDiv = $('<span class="messageBody">')
|
||||
.text(data.message);
|
||||
|
||||
var typingClass = data.typing ? 'typing' : '';
|
||||
var $messageDiv = $('<li class="message"/>')
|
||||
.data('username', data.username)
|
||||
.addClass(typingClass)
|
||||
.append($usernameDiv, $messageBodyDiv);
|
||||
|
||||
addMessageElement($messageDiv, options);
|
||||
}
|
||||
|
||||
// Adds the visual chat typing message
|
||||
function addChatTyping (data) {
|
||||
data.typing = true;
|
||||
data.message = 'is typing';
|
||||
addChatMessage(data);
|
||||
}
|
||||
|
||||
// Removes the visual chat typing message
|
||||
function removeChatTyping (data) {
|
||||
getTypingMessages(data).fadeOut(function () {
|
||||
$(this).remove();
|
||||
});
|
||||
}
|
||||
|
||||
// Adds a message element to the messages and scrolls to the bottom
|
||||
// el - The element to add as a message
|
||||
// options.fade - If the element should fade-in (default = true)
|
||||
// options.prepend - If the element should prepend
|
||||
// all other messages (default = false)
|
||||
function addMessageElement (el, options) {
|
||||
var $el = $(el);
|
||||
|
||||
// Setup default options
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
if (typeof options.fade === 'undefined') {
|
||||
options.fade = true;
|
||||
}
|
||||
if (typeof options.prepend === 'undefined') {
|
||||
options.prepend = false;
|
||||
}
|
||||
|
||||
// Apply options
|
||||
if (options.fade) {
|
||||
$el.hide().fadeIn(FADE_TIME);
|
||||
}
|
||||
if (options.prepend) {
|
||||
$messages.prepend($el);
|
||||
} else {
|
||||
$messages.append($el);
|
||||
}
|
||||
$messages[0].scrollTop = $messages[0].scrollHeight;
|
||||
}
|
||||
|
||||
// Prevents input from having injected markup
|
||||
function cleanInput (input) {
|
||||
return $('<div/>').text(input).text();
|
||||
}
|
||||
|
||||
// Updates the typing event
|
||||
function updateTyping () {
|
||||
if (connected) {
|
||||
if (!typing) {
|
||||
typing = true;
|
||||
socket.emit('typing');
|
||||
}
|
||||
lastTypingTime = (new Date()).getTime();
|
||||
|
||||
setTimeout(function () {
|
||||
var typingTimer = (new Date()).getTime();
|
||||
var timeDiff = typingTimer - lastTypingTime;
|
||||
if (timeDiff >= TYPING_TIMER_LENGTH && typing) {
|
||||
socket.emit('stop typing');
|
||||
typing = false;
|
||||
}
|
||||
}, TYPING_TIMER_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the 'X is typing' messages of a user
|
||||
function getTypingMessages (data) {
|
||||
return $('.typing.message').filter(function (i) {
|
||||
return $(this).data('username') === data.username;
|
||||
});
|
||||
}
|
||||
|
||||
// Gets the color of a username through our hash function
|
||||
function getUsernameColor (username) {
|
||||
// Compute hash code
|
||||
var hash = 7;
|
||||
for (var i = 0; i < username.length; i++) {
|
||||
hash = username.charCodeAt(i) + (hash << 5) - hash;
|
||||
}
|
||||
// Calculate color
|
||||
var index = Math.abs(hash % COLORS.length);
|
||||
return COLORS[index];
|
||||
}
|
||||
|
||||
// Keyboard events
|
||||
|
||||
$window.keydown(function (event) {
|
||||
// Auto-focus the current input when a key is typed
|
||||
if (!(event.ctrlKey || event.metaKey || event.altKey)) {
|
||||
$currentInput.focus();
|
||||
}
|
||||
// When the client hits ENTER on their keyboard
|
||||
if (event.which === 13) {
|
||||
if (username) {
|
||||
sendMessage();
|
||||
socket.emit('stop typing');
|
||||
typing = false;
|
||||
} else {
|
||||
setUsername();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$inputMessage.on('input', function() {
|
||||
updateTyping();
|
||||
});
|
||||
|
||||
// Click events
|
||||
|
||||
// Focus input when clicking anywhere on login page
|
||||
$loginPage.click(function () {
|
||||
$currentInput.focus();
|
||||
});
|
||||
|
||||
// Focus input when clicking on the message input's border
|
||||
$inputMessage.click(function () {
|
||||
$inputMessage.focus();
|
||||
});
|
||||
|
||||
// Socket events
|
||||
|
||||
// Whenever the server emits 'login', log the login message
|
||||
socket.on('login', function (data) {
|
||||
connected = true;
|
||||
// Display the welcome message
|
||||
var message = "Welcome to Socket.IO Chat – ";
|
||||
log(message, {
|
||||
prepend: true
|
||||
});
|
||||
addParticipantsMessage(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'new message', update the chat body
|
||||
socket.on('new message', function (data) {
|
||||
addChatMessage(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'user joined', log it in the chat body
|
||||
socket.on('user joined', function (data) {
|
||||
log(data.username + ' joined');
|
||||
addParticipantsMessage(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'user left', log it in the chat body
|
||||
socket.on('user left', function (data) {
|
||||
log(data.username + ' left');
|
||||
addParticipantsMessage(data);
|
||||
removeChatTyping(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'typing', show the typing message
|
||||
socket.on('typing', function (data) {
|
||||
addChatTyping(data);
|
||||
});
|
||||
|
||||
// Whenever the server emits 'stop typing', kill the typing message
|
||||
socket.on('stop typing', function (data) {
|
||||
removeChatTyping(data);
|
||||
});
|
||||
|
||||
socket.on('disconnect', function () {
|
||||
log('you have been disconnected');
|
||||
});
|
||||
|
||||
socket.on('connect', function () {
|
||||
if (username) {
|
||||
log('you have been reconnected');
|
||||
socket.emit('add user', username);
|
||||
}
|
||||
});
|
||||
|
||||
socket.io.on('reconnect_error', function () {
|
||||
log('attempt to reconnect has failed');
|
||||
});
|
||||
|
||||
socket.on('my-name-is', function (serverName) {
|
||||
log('host is now ' + serverName);
|
||||
})
|
||||
|
||||
});
|
||||
149
examples/cluster-traefik/server/public/style.css
Normal file
149
examples/cluster-traefik/server/public/style.css
Normal file
@@ -0,0 +1,149 @@
|
||||
/* Fix user-agent */
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-weight: 300;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
html, input {
|
||||
font-family:
|
||||
"HelveticaNeue-Light",
|
||||
"Helvetica Neue Light",
|
||||
"Helvetica Neue",
|
||||
Helvetica,
|
||||
Arial,
|
||||
"Lucida Grande",
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
/* Pages */
|
||||
|
||||
.pages {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.page {
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Login Page */
|
||||
|
||||
.login.page {
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.login.page .form {
|
||||
height: 100px;
|
||||
margin-top: -100px;
|
||||
position: absolute;
|
||||
|
||||
text-align: center;
|
||||
top: 50%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login.page .form .usernameInput {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid #fff;
|
||||
outline: none;
|
||||
padding-bottom: 15px;
|
||||
text-align: center;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.login.page .title {
|
||||
font-size: 200%;
|
||||
}
|
||||
|
||||
.login.page .usernameInput {
|
||||
font-size: 200%;
|
||||
letter-spacing: 3px;
|
||||
}
|
||||
|
||||
.login.page .title, .login.page .usernameInput {
|
||||
color: #fff;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
/* Chat page */
|
||||
|
||||
.chat.page {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Font */
|
||||
|
||||
.messages {
|
||||
font-size: 150%;
|
||||
}
|
||||
|
||||
.inputMessage {
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
.log {
|
||||
color: gray;
|
||||
font-size: 70%;
|
||||
margin: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Messages */
|
||||
|
||||
.chatArea {
|
||||
height: 100%;
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
|
||||
.messages {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow-y: scroll;
|
||||
padding: 10px 20px 10px 20px;
|
||||
}
|
||||
|
||||
.message.typing .messageBody {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.username {
|
||||
font-weight: 700;
|
||||
overflow: hidden;
|
||||
padding-right: 15px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* Input */
|
||||
|
||||
.inputMessage {
|
||||
border: 10px solid #000;
|
||||
bottom: 0;
|
||||
height: 60px;
|
||||
left: 0;
|
||||
outline: none;
|
||||
padding-left: 10px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
}
|
||||
10
examples/cluster-traefik/traefik.yml
Normal file
10
examples/cluster-traefik/traefik.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
api:
|
||||
insecure: true
|
||||
|
||||
entryPoints:
|
||||
web:
|
||||
address: ":80"
|
||||
|
||||
providers:
|
||||
docker: {}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Decoder, Encoder, Packet, PacketType } from "socket.io-parser";
|
||||
import debugModule = require("debug");
|
||||
import url = require("url");
|
||||
import type { IncomingMessage } from "http";
|
||||
import type { Namespace, Server } from "./index";
|
||||
import type { Socket } from "./socket";
|
||||
@@ -77,7 +78,7 @@ export class Client {
|
||||
* @param {Object} auth - the auth parameters
|
||||
* @private
|
||||
*/
|
||||
private connect(name: string, auth: object = {}) {
|
||||
private connect(name: string, auth: object = {}): void {
|
||||
if (this.server._nsps.has(name)) {
|
||||
debug("connecting to namespace %s", name);
|
||||
return this.doConnect(name, auth);
|
||||
@@ -112,7 +113,7 @@ export class Client {
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private doConnect(name: string, auth: object) {
|
||||
private doConnect(name: string, auth: object): void {
|
||||
const nsp = this.server.of(name);
|
||||
|
||||
const socket = nsp._add(this, auth, () => {
|
||||
@@ -131,7 +132,7 @@ export class Client {
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_disconnect() {
|
||||
_disconnect(): void {
|
||||
for (const socket of this.sockets.values()) {
|
||||
socket.disconnect();
|
||||
}
|
||||
@@ -144,7 +145,7 @@ export class Client {
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_remove(socket: Socket) {
|
||||
_remove(socket: Socket): void {
|
||||
if (this.sockets.has(socket.id)) {
|
||||
const nsp = this.sockets.get(socket.id)!.nsp.name;
|
||||
this.sockets.delete(socket.id);
|
||||
@@ -159,8 +160,8 @@ export class Client {
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private close() {
|
||||
if ("open" == this.conn.readyState) {
|
||||
private close(): void {
|
||||
if ("open" === this.conn.readyState) {
|
||||
debug("forcing transport close");
|
||||
this.conn.close();
|
||||
this.onclose("forced server close");
|
||||
@@ -174,19 +175,20 @@ export class Client {
|
||||
* @param {Object} opts
|
||||
* @private
|
||||
*/
|
||||
_packet(packet, opts?) {
|
||||
_packet(packet: Packet, opts?: any): void {
|
||||
opts = opts || {};
|
||||
const self = this;
|
||||
|
||||
// this writes to the actual connection
|
||||
function writeToEngine(encodedPackets) {
|
||||
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) {
|
||||
if ("open" === this.conn.readyState) {
|
||||
debug("writing packet %j", packet);
|
||||
if (!opts.preEncoded) {
|
||||
// not broadcasting, need to encode
|
||||
@@ -205,7 +207,7 @@ export class Client {
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private ondata(data) {
|
||||
private ondata(data): void {
|
||||
// try/catch is needed for protocol violations (GH-1880)
|
||||
try {
|
||||
this.decoder.add(data);
|
||||
@@ -219,9 +221,14 @@ export class Client {
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private ondecoded(packet: Packet) {
|
||||
if (PacketType.CONNECT == packet.type) {
|
||||
this.connect(packet.nsp, packet.data);
|
||||
private ondecoded(packet: Packet): void {
|
||||
if (PacketType.CONNECT === packet.type) {
|
||||
if (this.conn.protocol === 3) {
|
||||
const parsed = url.parse(packet.nsp, true);
|
||||
this.connect(parsed.pathname!, parsed.query);
|
||||
} else {
|
||||
this.connect(packet.nsp, packet.data);
|
||||
}
|
||||
} else {
|
||||
const socket = this.nsps.get(packet.nsp);
|
||||
if (socket) {
|
||||
@@ -240,7 +247,7 @@ export class Client {
|
||||
* @param {Object} err object
|
||||
* @private
|
||||
*/
|
||||
private onerror(err) {
|
||||
private onerror(err): void {
|
||||
for (const socket of this.sockets.values()) {
|
||||
socket._onerror(err);
|
||||
}
|
||||
@@ -253,7 +260,7 @@ export class Client {
|
||||
* @param reason
|
||||
* @private
|
||||
*/
|
||||
private onclose(reason: string) {
|
||||
private onclose(reason: string): void {
|
||||
debug("client close with reason %s", reason);
|
||||
|
||||
// ignore a potential subsequent `close` event
|
||||
@@ -272,7 +279,7 @@ export class Client {
|
||||
* Cleans up event listeners.
|
||||
* @private
|
||||
*/
|
||||
private destroy() {
|
||||
private destroy(): void {
|
||||
this.conn.removeListener("data", this.ondata);
|
||||
this.conn.removeListener("error", this.onerror);
|
||||
this.conn.removeListener("close", this.onclose);
|
||||
|
||||
85
lib/index.ts
85
lib/index.ts
@@ -25,7 +25,7 @@ const dotMapRegex = /\.map/;
|
||||
type Transport = "polling" | "websocket";
|
||||
type ParentNspNameMatchFn = (
|
||||
name: string,
|
||||
query: object,
|
||||
auth: { [key: string]: any },
|
||||
fn: (err: Error | null, success: boolean) => void
|
||||
) => void;
|
||||
|
||||
@@ -100,6 +100,11 @@ interface EngineOptions {
|
||||
* the options that will be forwarded to the cors module
|
||||
*/
|
||||
cors: CorsOptions;
|
||||
/**
|
||||
* whether to enable compatibility with Socket.IO v2 clients
|
||||
* @default false
|
||||
*/
|
||||
allowEIO3: boolean;
|
||||
}
|
||||
|
||||
interface AttachOptions {
|
||||
@@ -186,6 +191,10 @@ export class Server extends EventEmitter {
|
||||
*/
|
||||
constructor(opts?: Partial<ServerOptions>);
|
||||
constructor(srv?: http.Server | number, opts?: Partial<ServerOptions>);
|
||||
constructor(
|
||||
srv: undefined | Partial<ServerOptions> | http.Server | number,
|
||||
opts?: Partial<ServerOptions>
|
||||
);
|
||||
constructor(
|
||||
srv: undefined | Partial<ServerOptions> | http.Server | number,
|
||||
opts: Partial<ServerOptions> = {}
|
||||
@@ -217,9 +226,10 @@ export class Server extends EventEmitter {
|
||||
* @return self when setting or value when getting
|
||||
* @public
|
||||
*/
|
||||
public serveClient(v: boolean): Server;
|
||||
public serveClient(v: boolean): this;
|
||||
public serveClient(): boolean;
|
||||
public serveClient(v?: boolean): Server | boolean {
|
||||
public serveClient(v?: boolean): this | boolean;
|
||||
public serveClient(v?: boolean): this | boolean {
|
||||
if (!arguments.length) return this._serveClient;
|
||||
this._serveClient = v!;
|
||||
return this;
|
||||
@@ -229,16 +239,16 @@ export class Server extends EventEmitter {
|
||||
* Executes the middleware for an incoming namespace not already created on the server.
|
||||
*
|
||||
* @param name - name of incoming namespace
|
||||
* @param {Object} auth - the auth parameters
|
||||
* @param auth - the auth parameters
|
||||
* @param fn - callback
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_checkNamespace(
|
||||
name: string,
|
||||
auth: object,
|
||||
auth: { [key: string]: any },
|
||||
fn: (nsp: Namespace | false) => void
|
||||
) {
|
||||
): void {
|
||||
if (this.parentNsps.size === 0) return fn(false);
|
||||
|
||||
const keysIterator = this.parentNsps.keys();
|
||||
@@ -267,9 +277,10 @@ export class Server extends EventEmitter {
|
||||
* @return {Server|String} self when setting or value when getting
|
||||
* @public
|
||||
*/
|
||||
public path(v: string): Server;
|
||||
public path(v: string): this;
|
||||
public path(): string;
|
||||
public path(v?: string): Server | string {
|
||||
public path(v?: string): this | string;
|
||||
public path(v?: string): this | string {
|
||||
if (!arguments.length) return this._path;
|
||||
|
||||
this._path = v!.replace(/\/$/, "");
|
||||
@@ -288,9 +299,10 @@ export class Server extends EventEmitter {
|
||||
* @param v
|
||||
* @public
|
||||
*/
|
||||
public connectTimeout(v: number): Server;
|
||||
public connectTimeout(v: number): this;
|
||||
public connectTimeout(): number;
|
||||
public connectTimeout(v?: number): Server | number {
|
||||
public connectTimeout(v?: number): this | number;
|
||||
public connectTimeout(v?: number): this | number {
|
||||
if (v === undefined) return this._connectTimeout;
|
||||
this._connectTimeout = v;
|
||||
return this;
|
||||
@@ -304,8 +316,9 @@ export class Server extends EventEmitter {
|
||||
* @public
|
||||
*/
|
||||
public adapter(): typeof Adapter | undefined;
|
||||
public adapter(v: typeof Adapter): Server;
|
||||
public adapter(v?: typeof Adapter): typeof Adapter | undefined | Server {
|
||||
public adapter(v: typeof Adapter): this;
|
||||
public adapter(v?: typeof Adapter): typeof Adapter | undefined | this;
|
||||
public adapter(v?: typeof Adapter): typeof Adapter | undefined | this {
|
||||
if (!arguments.length) return this._adapter;
|
||||
this._adapter = v;
|
||||
for (const nsp of this._nsps.values()) {
|
||||
@@ -325,7 +338,7 @@ export class Server extends EventEmitter {
|
||||
public listen(
|
||||
srv: http.Server | number,
|
||||
opts: Partial<ServerOptions> = {}
|
||||
): Server {
|
||||
): this {
|
||||
return this.attach(srv, opts);
|
||||
}
|
||||
|
||||
@@ -340,7 +353,7 @@ export class Server extends EventEmitter {
|
||||
public attach(
|
||||
srv: http.Server | number,
|
||||
opts: Partial<ServerOptions> = {}
|
||||
): Server {
|
||||
): this {
|
||||
if ("function" == typeof srv) {
|
||||
const msg =
|
||||
"You are trying to attach socket.io to an express " +
|
||||
@@ -380,7 +393,10 @@ export class Server extends EventEmitter {
|
||||
* @param opts - options passed to engine.io
|
||||
* @private
|
||||
*/
|
||||
private initEngine(srv: http.Server, opts: Partial<EngineAttachOptions>) {
|
||||
private initEngine(
|
||||
srv: http.Server,
|
||||
opts: Partial<EngineAttachOptions>
|
||||
): void {
|
||||
// initialize engine
|
||||
debug("creating engine.io instance with opts %j", opts);
|
||||
this.eio = engine.attach(srv, opts);
|
||||
@@ -401,7 +417,7 @@ export class Server extends EventEmitter {
|
||||
* @param srv http server
|
||||
* @private
|
||||
*/
|
||||
private attachServe(srv: http.Server) {
|
||||
private attachServe(srv: http.Server): void {
|
||||
debug("attaching client serving req handler");
|
||||
|
||||
const evs = srv.listeners("request").slice(0);
|
||||
@@ -424,7 +440,7 @@ export class Server extends EventEmitter {
|
||||
* @param res
|
||||
* @private
|
||||
*/
|
||||
private serve(req: http.IncomingMessage, res: http.ServerResponse) {
|
||||
private serve(req: http.IncomingMessage, res: http.ServerResponse): void {
|
||||
const filename = req.url!.replace(this._path, "");
|
||||
const isMap = dotMapRegex.test(filename);
|
||||
const type = isMap ? "map" : "source";
|
||||
@@ -432,10 +448,11 @@ export class Server extends EventEmitter {
|
||||
// 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.headers["if-none-match"];
|
||||
if (etag) {
|
||||
if (expectedEtag == etag) {
|
||||
if (expectedEtag === etag || weakEtag === etag) {
|
||||
debug("serve client %s 304", type);
|
||||
res.writeHead(304);
|
||||
res.end();
|
||||
@@ -468,7 +485,7 @@ export class Server extends EventEmitter {
|
||||
filename: string,
|
||||
req: http.IncomingMessage,
|
||||
res: http.ServerResponse
|
||||
) {
|
||||
): void {
|
||||
const readStream = createReadStream(
|
||||
path.join(__dirname, "../client-dist/", filename)
|
||||
);
|
||||
@@ -507,7 +524,7 @@ export class Server extends EventEmitter {
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public bind(engine): Server {
|
||||
public bind(engine): this {
|
||||
this.engine = engine;
|
||||
this.engine.on("connection", this.onconnection.bind(this));
|
||||
return this;
|
||||
@@ -520,9 +537,13 @@ export class Server extends EventEmitter {
|
||||
* @return self
|
||||
* @private
|
||||
*/
|
||||
private onconnection(conn): Server {
|
||||
private onconnection(conn): this {
|
||||
debug("incoming connection with id %s", conn.id);
|
||||
new Client(this, conn);
|
||||
const client = new Client(this, conn);
|
||||
if (conn.protocol === 3) {
|
||||
// @ts-ignore
|
||||
client.connect("/");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -530,13 +551,13 @@ export class Server extends EventEmitter {
|
||||
* Looks up a namespace.
|
||||
*
|
||||
* @param {String|RegExp|Function} name nsp name
|
||||
* @param [fn] optional, nsp `connection` ev handler
|
||||
* @param fn optional, nsp `connection` ev handler
|
||||
* @public
|
||||
*/
|
||||
public of(
|
||||
name: string | RegExp | ParentNspNameMatchFn,
|
||||
fn?: (socket: Socket) => void
|
||||
) {
|
||||
): Namespace {
|
||||
if (typeof name === "function" || name instanceof RegExp) {
|
||||
const parentNsp = new ParentNamespace(this);
|
||||
debug("initializing parent namespace %s", parentNsp.name);
|
||||
@@ -595,7 +616,7 @@ export class Server extends EventEmitter {
|
||||
*/
|
||||
public use(
|
||||
fn: (socket: Socket, next: (err?: ExtendedError) => void) => void
|
||||
): Server {
|
||||
): this {
|
||||
this.sockets.use(fn);
|
||||
return this;
|
||||
}
|
||||
@@ -607,7 +628,7 @@ export class Server extends EventEmitter {
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public to(name: Room): Server {
|
||||
public to(name: Room): this {
|
||||
this.sockets.to(name);
|
||||
return this;
|
||||
}
|
||||
@@ -619,7 +640,7 @@ export class Server extends EventEmitter {
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public in(name: Room): Server {
|
||||
public in(name: Room): this {
|
||||
this.sockets.in(name);
|
||||
return this;
|
||||
}
|
||||
@@ -630,7 +651,7 @@ export class Server extends EventEmitter {
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public send(...args: readonly any[]): Server {
|
||||
public send(...args: readonly any[]): this {
|
||||
this.sockets.emit("message", ...args);
|
||||
return this;
|
||||
}
|
||||
@@ -641,7 +662,7 @@ export class Server extends EventEmitter {
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public write(...args: readonly any[]): Server {
|
||||
public write(...args: readonly any[]): this {
|
||||
this.sockets.emit("message", ...args);
|
||||
return this;
|
||||
}
|
||||
@@ -662,7 +683,7 @@ export class Server extends EventEmitter {
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public compress(compress: boolean): Server {
|
||||
public compress(compress: boolean): this {
|
||||
this.sockets.compress(compress);
|
||||
return this;
|
||||
}
|
||||
@@ -675,7 +696,7 @@ export class Server extends EventEmitter {
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public get volatile(): Server {
|
||||
public get volatile(): this {
|
||||
this.sockets.volatile;
|
||||
return this;
|
||||
}
|
||||
@@ -686,7 +707,7 @@ export class Server extends EventEmitter {
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public get local(): Server {
|
||||
public get local(): this {
|
||||
this.sockets.local;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ export class Namespace extends EventEmitter {
|
||||
*/
|
||||
public use(
|
||||
fn: (socket: Socket, next: (err?: ExtendedError) => void) => void
|
||||
): Namespace {
|
||||
): this {
|
||||
this._fns.push(fn);
|
||||
return this;
|
||||
}
|
||||
@@ -106,7 +106,7 @@ export class Namespace extends EventEmitter {
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public to(name: Room): Namespace {
|
||||
public to(name: Room): this {
|
||||
this._rooms.add(name);
|
||||
return this;
|
||||
}
|
||||
@@ -118,7 +118,7 @@ export class Namespace extends EventEmitter {
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public in(name: Room): Namespace {
|
||||
public in(name: Room): this {
|
||||
this._rooms.add(name);
|
||||
return this;
|
||||
}
|
||||
@@ -135,11 +135,16 @@ export class Namespace extends EventEmitter {
|
||||
this.run(socket, (err) => {
|
||||
process.nextTick(() => {
|
||||
if ("open" == client.conn.readyState) {
|
||||
if (err)
|
||||
return socket._error({
|
||||
message: err.message,
|
||||
data: err.data,
|
||||
});
|
||||
if (err) {
|
||||
if (client.conn.protocol === 3) {
|
||||
return socket._error(err.data || err.message);
|
||||
} else {
|
||||
return socket._error({
|
||||
message: err.message,
|
||||
data: err.data,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// track socket
|
||||
this.sockets.set(socket.id, socket);
|
||||
@@ -217,7 +222,7 @@ export class Namespace extends EventEmitter {
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public send(...args: readonly any[]): Namespace {
|
||||
public send(...args: readonly any[]): this {
|
||||
this.emit("message", ...args);
|
||||
return this;
|
||||
}
|
||||
@@ -228,7 +233,7 @@ export class Namespace extends EventEmitter {
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public write(...args: readonly any[]): Namespace {
|
||||
public write(...args: readonly any[]): this {
|
||||
this.emit("message", ...args);
|
||||
return this;
|
||||
}
|
||||
@@ -257,7 +262,7 @@ export class Namespace extends EventEmitter {
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public compress(compress: boolean): Namespace {
|
||||
public compress(compress: boolean): this {
|
||||
this._flags.compress = compress;
|
||||
return this;
|
||||
}
|
||||
@@ -270,7 +275,7 @@ export class Namespace extends EventEmitter {
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public get volatile(): Namespace {
|
||||
public get volatile(): this {
|
||||
this._flags.volatile = true;
|
||||
return this;
|
||||
}
|
||||
@@ -281,7 +286,7 @@ export class Namespace extends EventEmitter {
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public get local(): Namespace {
|
||||
public get local(): this {
|
||||
this._flags.local = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { EventEmitter } from "events";
|
||||
import { PacketType } from "socket.io-parser";
|
||||
import { Packet, PacketType } from "socket.io-parser";
|
||||
import url = require("url");
|
||||
import debugModule from "debug";
|
||||
import type { Server } from "./index";
|
||||
import type { Client } from "./client";
|
||||
import type { Namespace } from "./namespace";
|
||||
import type { IncomingMessage } from "http";
|
||||
import type { IncomingMessage, IncomingHttpHeaders } from "http";
|
||||
import type {
|
||||
Adapter,
|
||||
BroadcastFlags,
|
||||
@@ -13,6 +13,7 @@ import type {
|
||||
SocketId,
|
||||
} from "socket.io-adapter";
|
||||
import base64id from "base64id";
|
||||
import type { ParsedUrlQuery } from "querystring";
|
||||
|
||||
const debug = debugModule("socket.io:socket");
|
||||
|
||||
@@ -33,7 +34,7 @@ export interface Handshake {
|
||||
/**
|
||||
* The headers sent as part of the handshake
|
||||
*/
|
||||
headers: object;
|
||||
headers: IncomingHttpHeaders;
|
||||
|
||||
/**
|
||||
* The date of creation (as string)
|
||||
@@ -68,12 +69,12 @@ export interface Handshake {
|
||||
/**
|
||||
* The query object
|
||||
*/
|
||||
query: object;
|
||||
query: ParsedUrlQuery;
|
||||
|
||||
/**
|
||||
* The auth object
|
||||
*/
|
||||
auth: object;
|
||||
auth: { [key: string]: any };
|
||||
}
|
||||
|
||||
export class Socket extends EventEmitter {
|
||||
@@ -105,7 +106,12 @@ export class Socket extends EventEmitter {
|
||||
super();
|
||||
this.server = nsp.server;
|
||||
this.adapter = this.nsp.adapter;
|
||||
this.id = base64id.generateId(); // don't reuse the Engine.IO id because it's sensitive information
|
||||
if (client.conn.protocol === 3) {
|
||||
// @ts-ignore
|
||||
this.id = nsp.name !== "/" ? nsp.name + "#" + client.id : client.id;
|
||||
} else {
|
||||
this.id = base64id.generateId(); // don't reuse the Engine.IO id because it's sensitive information
|
||||
}
|
||||
this.connected = true;
|
||||
this.disconnected = false;
|
||||
this.handshake = this.buildHandshake(auth);
|
||||
@@ -185,7 +191,7 @@ export class Socket extends EventEmitter {
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public to(name: Room): Socket {
|
||||
public to(name: Room): this {
|
||||
this._rooms.add(name);
|
||||
return this;
|
||||
}
|
||||
@@ -197,7 +203,7 @@ export class Socket extends EventEmitter {
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public in(name: Room): Socket {
|
||||
public in(name: Room): this {
|
||||
this._rooms.add(name);
|
||||
return this;
|
||||
}
|
||||
@@ -208,7 +214,7 @@ export class Socket extends EventEmitter {
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public send(...args: readonly any[]): Socket {
|
||||
public send(...args: readonly any[]): this {
|
||||
this.emit("message", ...args);
|
||||
return this;
|
||||
}
|
||||
@@ -219,7 +225,7 @@ export class Socket extends EventEmitter {
|
||||
* @return self
|
||||
* @public
|
||||
*/
|
||||
public write(...args: readonly any[]): Socket {
|
||||
public write(...args: readonly any[]): this {
|
||||
this.emit("message", ...args);
|
||||
return this;
|
||||
}
|
||||
@@ -231,10 +237,13 @@ export class Socket extends EventEmitter {
|
||||
* @param {Object} opts - options
|
||||
* @private
|
||||
*/
|
||||
private packet(packet, opts: any = {}) {
|
||||
private packet(
|
||||
packet: Omit<Packet, "nsp"> & Partial<Pick<Packet, "nsp">>,
|
||||
opts: any = {}
|
||||
): void {
|
||||
packet.nsp = this.nsp.name;
|
||||
opts.compress = false !== opts.compress;
|
||||
this.client._packet(packet, opts);
|
||||
this.client._packet(packet as Packet, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -286,7 +295,11 @@ export class Socket extends EventEmitter {
|
||||
_onconnect(): void {
|
||||
debug("socket connected - writing packet");
|
||||
this.join(this.id);
|
||||
this.packet({ type: PacketType.CONNECT, data: { sid: this.id } });
|
||||
if (this.conn.protocol === 3) {
|
||||
this.packet({ type: PacketType.CONNECT });
|
||||
} else {
|
||||
this.packet({ type: PacketType.CONNECT, data: { sid: this.id } });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -295,7 +308,7 @@ export class Socket extends EventEmitter {
|
||||
* @param {Object} packet
|
||||
* @private
|
||||
*/
|
||||
_onpacket(packet) {
|
||||
_onpacket(packet: Packet): void {
|
||||
debug("got packet %j", packet);
|
||||
switch (packet.type) {
|
||||
case PacketType.EVENT:
|
||||
@@ -326,10 +339,10 @@ export class Socket extends EventEmitter {
|
||||
/**
|
||||
* Called upon event packet.
|
||||
*
|
||||
* @param {Object} packet - packet object
|
||||
* @param {Packet} packet - packet object
|
||||
* @private
|
||||
*/
|
||||
private onevent(packet): void {
|
||||
private onevent(packet: Packet): void {
|
||||
const args = packet.data || [];
|
||||
debug("emitting event %j", args);
|
||||
|
||||
@@ -353,7 +366,7 @@ export class Socket extends EventEmitter {
|
||||
* @param {Number} id - packet id
|
||||
* @private
|
||||
*/
|
||||
private ack(id: number) {
|
||||
private ack(id: number): () => void {
|
||||
const self = this;
|
||||
let sent = false;
|
||||
return function () {
|
||||
@@ -377,12 +390,12 @@ export class Socket extends EventEmitter {
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private onack(packet): void {
|
||||
const ack = this.acks.get(packet.id);
|
||||
private onack(packet: Packet): void {
|
||||
const ack = this.acks.get(packet.id!);
|
||||
if ("function" == typeof ack) {
|
||||
debug("calling ack %s with %j", packet.id, packet.data);
|
||||
ack.apply(this, packet.data);
|
||||
this.acks.delete(packet.id);
|
||||
this.acks.delete(packet.id!);
|
||||
} else {
|
||||
debug("bad ack %s", packet.id);
|
||||
}
|
||||
@@ -420,7 +433,7 @@ export class Socket extends EventEmitter {
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_onclose(reason: string): Socket | undefined {
|
||||
_onclose(reason: string): this | undefined {
|
||||
if (!this.connected) return this;
|
||||
debug("closing socket - reason %s", reason);
|
||||
super.emit("disconnecting", reason);
|
||||
@@ -440,7 +453,7 @@ export class Socket extends EventEmitter {
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_error(err) {
|
||||
_error(err): void {
|
||||
this.packet({ type: PacketType.CONNECT_ERROR, data: err });
|
||||
}
|
||||
|
||||
@@ -452,7 +465,7 @@ export class Socket extends EventEmitter {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
public disconnect(close = false): Socket {
|
||||
public disconnect(close = false): this {
|
||||
if (!this.connected) return this;
|
||||
if (close) {
|
||||
this.client._disconnect();
|
||||
@@ -470,7 +483,7 @@ export class Socket extends EventEmitter {
|
||||
* @return {Socket} self
|
||||
* @public
|
||||
*/
|
||||
public compress(compress: boolean): Socket {
|
||||
public compress(compress: boolean): this {
|
||||
this.flags.compress = compress;
|
||||
return this;
|
||||
}
|
||||
@@ -483,7 +496,7 @@ export class Socket extends EventEmitter {
|
||||
* @return {Socket} self
|
||||
* @public
|
||||
*/
|
||||
public get volatile(): Socket {
|
||||
public get volatile(): this {
|
||||
this.flags.volatile = true;
|
||||
return this;
|
||||
}
|
||||
@@ -495,7 +508,7 @@ export class Socket extends EventEmitter {
|
||||
* @return {Socket} self
|
||||
* @public
|
||||
*/
|
||||
public get broadcast(): Socket {
|
||||
public get broadcast(): this {
|
||||
this.flags.broadcast = true;
|
||||
return this;
|
||||
}
|
||||
@@ -506,7 +519,7 @@ export class Socket extends EventEmitter {
|
||||
* @return {Socket} self
|
||||
* @public
|
||||
*/
|
||||
public get local(): Socket {
|
||||
public get local(): this {
|
||||
this.flags.local = true;
|
||||
return this;
|
||||
}
|
||||
@@ -517,7 +530,7 @@ export class Socket extends EventEmitter {
|
||||
* @param {Array} event - event that will get emitted
|
||||
* @private
|
||||
*/
|
||||
private dispatch(event): void {
|
||||
private dispatch(event: [eventName: string, ...args: any[]]): void {
|
||||
debug("dispatching an event %j", event);
|
||||
this.run(event, (err) => {
|
||||
process.nextTick(() => {
|
||||
@@ -538,7 +551,7 @@ export class Socket extends EventEmitter {
|
||||
*/
|
||||
public use(
|
||||
fn: (event: Array<any>, next: (err: Error) => void) => void
|
||||
): Socket {
|
||||
): this {
|
||||
this.fns.push(fn);
|
||||
return this;
|
||||
}
|
||||
@@ -550,11 +563,14 @@ export class Socket extends EventEmitter {
|
||||
* @param {Function} fn - last fn call in the middleware
|
||||
* @private
|
||||
*/
|
||||
private run(event: Array<any>, fn: (err: Error | null) => void) {
|
||||
private run(
|
||||
event: [eventName: string, ...args: any[]],
|
||||
fn: (err: Error | null) => void
|
||||
): void {
|
||||
const fns = this.fns.slice(0);
|
||||
if (!fns.length) return fn(null);
|
||||
|
||||
function run(i) {
|
||||
function run(i: number) {
|
||||
fns[i](event, function (err) {
|
||||
// upon error, short-circuit
|
||||
if (err) return fn(err);
|
||||
@@ -602,7 +618,7 @@ export class Socket extends EventEmitter {
|
||||
* @param listener
|
||||
* @public
|
||||
*/
|
||||
public onAny(listener: (...args: any[]) => void): Socket {
|
||||
public onAny(listener: (...args: any[]) => void): this {
|
||||
this._anyListeners = this._anyListeners || [];
|
||||
this._anyListeners.push(listener);
|
||||
return this;
|
||||
@@ -615,7 +631,7 @@ export class Socket extends EventEmitter {
|
||||
* @param listener
|
||||
* @public
|
||||
*/
|
||||
public prependAny(listener: (...args: any[]) => void): Socket {
|
||||
public prependAny(listener: (...args: any[]) => void): this {
|
||||
this._anyListeners = this._anyListeners || [];
|
||||
this._anyListeners.unshift(listener);
|
||||
return this;
|
||||
@@ -627,7 +643,7 @@ export class Socket extends EventEmitter {
|
||||
* @param listener
|
||||
* @public
|
||||
*/
|
||||
public offAny(listener?: (...args: any[]) => void): Socket {
|
||||
public offAny(listener?: (...args: any[]) => void): this {
|
||||
if (!this._anyListeners) {
|
||||
return this;
|
||||
}
|
||||
|
||||
179
package-lock.json
generated
179
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "socket.io",
|
||||
"version": "3.0.5",
|
||||
"version": "3.1.1",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@@ -462,6 +462,12 @@
|
||||
"integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==",
|
||||
"dev": true
|
||||
},
|
||||
"after": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
|
||||
"integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=",
|
||||
"dev": true
|
||||
},
|
||||
"aggregate-error": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
|
||||
@@ -535,6 +541,12 @@
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"arraybuffer.slice": {
|
||||
"version": "0.0.7",
|
||||
"resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz",
|
||||
"integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==",
|
||||
"dev": true
|
||||
},
|
||||
"astral-regex": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
|
||||
@@ -584,6 +596,12 @@
|
||||
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
|
||||
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="
|
||||
},
|
||||
"blob": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz",
|
||||
"integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==",
|
||||
"dev": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
@@ -722,11 +740,23 @@
|
||||
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
|
||||
"dev": true
|
||||
},
|
||||
"component-bind": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz",
|
||||
"integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=",
|
||||
"dev": true
|
||||
},
|
||||
"component-emitter": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
|
||||
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
|
||||
},
|
||||
"component-inherit": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz",
|
||||
"integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=",
|
||||
"dev": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
@@ -830,9 +860,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"engine.io": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-4.0.6.tgz",
|
||||
"integrity": "sha512-rf7HAVZpcRrcKEKddgIzYUnwg0g5HE1RvJaTLwkcfJmce4g+po8aMuE6vxzp6JwlK8FEq/vi0KWN6tA585DjaA==",
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-4.1.0.tgz",
|
||||
"integrity": "sha512-vW7EAtn0HDQ4MtT5QbmCHF17TaYLONv2/JwdYsq9USPRZVM4zG7WB3k0Nc321z8EuSOlhGokrYlYx4176QhD0A==",
|
||||
"requires": {
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "2.0.0",
|
||||
@@ -844,9 +874,9 @@
|
||||
}
|
||||
},
|
||||
"engine.io-client": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-4.0.6.tgz",
|
||||
"integrity": "sha512-5lPh8rrhxIruo5ZlgFt31KM626o5OCXrCHBweieWWuVicDtnYdz/iR93k6N9k0Xs61WrYxZKIWXzeSaJF6fpNA==",
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-4.1.1.tgz",
|
||||
"integrity": "sha512-iYasV/EttP/2pLrdowe9G3zwlNIFhwny8VSIh+vPlMnYZqSzLsTzSLa9hFy015OrH1s4fzoYxeHjVkO8hSFKwg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"base64-arraybuffer": "0.1.4",
|
||||
@@ -1314,6 +1344,15 @@
|
||||
"function-bind": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"has-binary2": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz",
|
||||
"integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"isarray": "2.0.1"
|
||||
}
|
||||
},
|
||||
"has-cors": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
|
||||
@@ -1376,6 +1415,12 @@
|
||||
"integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
|
||||
"dev": true
|
||||
},
|
||||
"indexof": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
|
||||
"integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=",
|
||||
"dev": true
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
@@ -1440,6 +1485,12 @@
|
||||
"integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
|
||||
"dev": true
|
||||
},
|
||||
"isarray": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
|
||||
"integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=",
|
||||
"dev": true
|
||||
},
|
||||
"isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
@@ -2202,23 +2253,115 @@
|
||||
}
|
||||
},
|
||||
"socket.io-adapter": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.0.3.tgz",
|
||||
"integrity": "sha512-2wo4EXgxOGSFueqvHAdnmi5JLZzWqMArjuP4nqC26AtLh5PoCPsaRbRdah2xhcwTAMooZfjYiNVNkkmmSMaxOQ=="
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.1.0.tgz",
|
||||
"integrity": "sha512-+vDov/aTsLjViYTwS9fPy5pEtTkrbEKsw2M+oVSoFGw6OD1IpvlV1VPhUzNbofCQ8oyMbdYJqDtGdmHQK6TdPg=="
|
||||
},
|
||||
"socket.io-client": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-3.0.5.tgz",
|
||||
"integrity": "sha512-NNnv3UH5h+aICeVDAdSHll3vSujp1OnzvDtuVz1ukUXliffr1+LTGc1W+qZAm3H7McapGsJhTI5nsBoY1r21dQ==",
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-3.1.1.tgz",
|
||||
"integrity": "sha512-BLgIuCjI7Sf3mDHunKddX9zKR/pbkP7IACM3sJS3jha+zJ6/pGKRV6Fz5XSBHCfUs9YzT8kYIqNwOOuFNLtnYA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/component-emitter": "^1.2.10",
|
||||
"backo2": "~1.0.2",
|
||||
"component-emitter": "~1.3.0",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io-client": "~4.0.6",
|
||||
"engine.io-client": "~4.1.0",
|
||||
"parseuri": "0.0.6",
|
||||
"socket.io-parser": "~4.0.3"
|
||||
"socket.io-parser": "~4.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"socket.io-parser": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz",
|
||||
"integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/component-emitter": "^1.2.10",
|
||||
"component-emitter": "~1.3.0",
|
||||
"debug": "~4.3.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"socket.io-client-v2": {
|
||||
"version": "npm:socket.io-client@2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.4.0.tgz",
|
||||
"integrity": "sha512-M6xhnKQHuuZd4Ba9vltCLT9oa+YvTsP8j9NcEiLElfIg8KeYPyhWOes6x4t+LTAC8enQbE/995AdTem2uNyKKQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"backo2": "1.0.2",
|
||||
"component-bind": "1.0.0",
|
||||
"component-emitter": "~1.3.0",
|
||||
"debug": "~3.1.0",
|
||||
"engine.io-client": "~3.5.0",
|
||||
"has-binary2": "~1.0.2",
|
||||
"indexof": "0.0.1",
|
||||
"parseqs": "0.0.6",
|
||||
"parseuri": "0.0.6",
|
||||
"socket.io-parser": "~3.3.0",
|
||||
"to-array": "0.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"engine.io-client": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.5.0.tgz",
|
||||
"integrity": "sha512-12wPRfMrugVw/DNyJk34GQ5vIVArEcVMXWugQGGuw2XxUSztFNmJggZmv8IZlLyEdnpO1QB9LkcjeWewO2vxtA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"component-emitter": "~1.3.0",
|
||||
"component-inherit": "0.0.3",
|
||||
"debug": "~3.1.0",
|
||||
"engine.io-parser": "~2.2.0",
|
||||
"has-cors": "1.1.0",
|
||||
"indexof": "0.0.1",
|
||||
"parseqs": "0.0.6",
|
||||
"parseuri": "0.0.6",
|
||||
"ws": "~7.4.2",
|
||||
"xmlhttprequest-ssl": "~1.5.4",
|
||||
"yeast": "0.1.2"
|
||||
}
|
||||
},
|
||||
"engine.io-parser": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.1.tgz",
|
||||
"integrity": "sha512-x+dN/fBH8Ro8TFwJ+rkB2AmuVw9Yu2mockR/p3W8f8YtExwFgDvBDi0GWyb4ZLkpahtDGZgtr3zLovanJghPqg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"after": "0.8.2",
|
||||
"arraybuffer.slice": "~0.0.7",
|
||||
"base64-arraybuffer": "0.1.4",
|
||||
"blob": "0.0.5",
|
||||
"has-binary2": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
|
||||
"dev": true
|
||||
},
|
||||
"socket.io-parser": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.2.tgz",
|
||||
"integrity": "sha512-FJvDBuOALxdCI9qwRrO/Rfp9yfndRtc1jSgVgV8FDraihmSP/MLGD5PEuJrNfjALvcQ+vMDM/33AWOYP/JSjDg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"component-emitter": "~1.3.0",
|
||||
"debug": "~3.1.0",
|
||||
"isarray": "2.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"socket.io-parser": {
|
||||
@@ -2408,6 +2551,12 @@
|
||||
"integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
|
||||
"dev": true
|
||||
},
|
||||
"to-array": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
|
||||
"integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=",
|
||||
"dev": true
|
||||
},
|
||||
"to-fast-properties": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "socket.io",
|
||||
"version": "3.0.5",
|
||||
"version": "3.1.1",
|
||||
"description": "node.js realtime framework server",
|
||||
"keywords": [
|
||||
"realtime",
|
||||
@@ -49,8 +49,8 @@
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "~2.0.0",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io": "~4.0.6",
|
||||
"socket.io-adapter": "~2.0.3",
|
||||
"engine.io": "~4.1.0",
|
||||
"socket.io-adapter": "~2.1.0",
|
||||
"socket.io-parser": "~4.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -63,7 +63,8 @@
|
||||
"nyc": "^15.1.0",
|
||||
"prettier": "^2.2.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"socket.io-client": "3.0.5",
|
||||
"socket.io-client": "3.1.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",
|
||||
|
||||
@@ -8,11 +8,11 @@ import { exec } from "child_process";
|
||||
import request from "supertest";
|
||||
import expect from "expect.js";
|
||||
import type { AddressInfo } from "net";
|
||||
import * as io_v2 from "socket.io-client-v2";
|
||||
|
||||
const ioc = require("socket.io-client");
|
||||
|
||||
import "./support/util";
|
||||
import exp = require("constants");
|
||||
|
||||
// Creates a socket.io client for the given server
|
||||
function client(srv, nsp?: string | object, opts?: object) {
|
||||
@@ -26,6 +26,18 @@ function client(srv, nsp?: string | object, opts?: object) {
|
||||
return ioc(url, opts);
|
||||
}
|
||||
|
||||
const success = (sio, clientSocket, done) => {
|
||||
sio.close();
|
||||
clientSocket.close();
|
||||
done();
|
||||
};
|
||||
|
||||
const waitFor = (emitter, event) => {
|
||||
return new Promise((resolve) => {
|
||||
emitter.once(event, resolve);
|
||||
});
|
||||
};
|
||||
|
||||
describe("socket.io", () => {
|
||||
it("should be the same version as client", () => {
|
||||
const version = require("../package").version;
|
||||
@@ -116,6 +128,19 @@ describe("socket.io", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle 304", (done) => {
|
||||
const srv = createServer();
|
||||
new Server(srv);
|
||||
request(srv)
|
||||
.get("/socket.io/socket.io.js")
|
||||
.set("If-None-Match", 'W/"' + clientVersion + '"')
|
||||
.end((err, res) => {
|
||||
if (err) return done(err);
|
||||
expect(res.statusCode).to.be(304);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should not serve static files", (done) => {
|
||||
const srv = createServer();
|
||||
new Server(srv, { serveClient: false });
|
||||
@@ -2430,4 +2455,74 @@ describe("socket.io", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("v2 compatibility", () => {
|
||||
it("should connect if `allowEIO3` is true", (done) => {
|
||||
const srv = createServer();
|
||||
const sio = new Server(srv, {
|
||||
allowEIO3: true,
|
||||
});
|
||||
|
||||
srv.listen(async () => {
|
||||
const port = (srv.address() as AddressInfo).port;
|
||||
const clientSocket = io_v2.connect(`http://localhost:${port}`, {
|
||||
multiplex: false,
|
||||
});
|
||||
|
||||
const [socket]: Array<any> = await Promise.all([
|
||||
waitFor(sio, "connection"),
|
||||
waitFor(clientSocket, "connect"),
|
||||
]);
|
||||
|
||||
expect(socket.id).to.eql(clientSocket.id);
|
||||
success(sio, clientSocket, done);
|
||||
});
|
||||
});
|
||||
|
||||
it("should be able to connect to a namespace with a query", (done) => {
|
||||
const srv = createServer();
|
||||
const sio = new Server(srv, {
|
||||
allowEIO3: true,
|
||||
});
|
||||
|
||||
srv.listen(async () => {
|
||||
const port = (srv.address() as AddressInfo).port;
|
||||
const clientSocket = io_v2.connect(
|
||||
`http://localhost:${port}/the-namespace`,
|
||||
{
|
||||
multiplex: false,
|
||||
}
|
||||
);
|
||||
clientSocket.query = { test: "123" };
|
||||
|
||||
const [socket]: Array<any> = await Promise.all([
|
||||
waitFor(sio.of("/the-namespace"), "connection"),
|
||||
waitFor(clientSocket, "connect"),
|
||||
]);
|
||||
|
||||
expect(socket.handshake.auth).to.eql({ test: "123" });
|
||||
success(sio, clientSocket, done);
|
||||
});
|
||||
});
|
||||
|
||||
it("should not connect if `allowEIO3` is false (default)", (done) => {
|
||||
const srv = createServer();
|
||||
const sio = new Server(srv);
|
||||
|
||||
srv.listen(() => {
|
||||
const port = (srv.address() as AddressInfo).port;
|
||||
const clientSocket = io_v2.connect(`http://localhost:${port}`, {
|
||||
multiplex: false,
|
||||
});
|
||||
|
||||
clientSocket.on("connect", () => {
|
||||
done(new Error("should not happen"));
|
||||
});
|
||||
|
||||
clientSocket.on("connect_error", () => {
|
||||
success(sio, clientSocket, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
import io from "./dist/index.js";
|
||||
|
||||
export const Server = io.Server;
|
||||
export const {Server, Namespace, Socket} = io;
|
||||
|
||||
Reference in New Issue
Block a user