mirror of
https://github.com/socketio/socket.io.git
synced 2026-01-11 16:08:24 -05:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50176812a1 | ||
|
|
bf64870957 | ||
|
|
748e18c22e | ||
|
|
b9ce6a25d1 | ||
|
|
54dabe5bff | ||
|
|
e426f3e8e1 | ||
|
|
e36062ca2d | ||
|
|
0bbe8aec77 | ||
|
|
914a8bd2b9 | ||
|
|
d943c3e0b0 |
12
.github/workflows/ci.yml
vendored
12
.github/workflows/ci.yml
vendored
@@ -22,16 +22,20 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Install TypeScript 4.2
|
||||
run: npm i typescript@4.2
|
||||
if: ${{ matrix.node-version == '16' }}
|
||||
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
env:
|
||||
@@ -54,10 +58,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js 20
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
|
||||
17
CHANGELOG.md
17
CHANGELOG.md
@@ -2,6 +2,7 @@
|
||||
|
||||
## 2024
|
||||
|
||||
- [4.7.5](#475-2024-03-14) (Mar 2024)
|
||||
- [4.7.4](#474-2024-01-12) (Jan 2024)
|
||||
- [4.7.3](#473-2024-01-03) (Jan 2024)
|
||||
|
||||
@@ -66,6 +67,22 @@
|
||||
|
||||
# Release notes
|
||||
|
||||
## [4.7.5](https://github.com/socketio/socket.io/compare/4.7.4...4.7.5) (2024-03-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* close the adapters when the server is closed ([bf64870](https://github.com/socketio/socket.io/commit/bf64870957e626a73e0544716a1a41a4ba5093bb))
|
||||
* remove duplicate pipeline when serving bundle ([e426f3e](https://github.com/socketio/socket.io/commit/e426f3e8e1bfea5720c32d30a3663303200ee6ad))
|
||||
|
||||
|
||||
### Dependencies
|
||||
|
||||
- [`engine.io@~6.5.2`](https://github.com/socketio/engine.io/releases/tag/6.5.2) (no change)
|
||||
- [`ws@~8.11.0`](https://github.com/websockets/ws/releases/tag/8.11.0) (no change)
|
||||
|
||||
|
||||
|
||||
## [4.7.4](https://github.com/socketio/socket.io/compare/4.7.3...4.7.4) (2024-01-12)
|
||||
|
||||
|
||||
|
||||
4
client-dist/socket.io.esm.min.js
vendored
4
client-dist/socket.io.esm.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
||||
/*!
|
||||
* Socket.IO v4.7.4
|
||||
* Socket.IO v4.7.5
|
||||
* (c) 2014-2024 Guillermo Rauch
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
@@ -3022,6 +3022,29 @@
|
||||
*/
|
||||
_this._queueSeq = 0;
|
||||
_this.ids = 0;
|
||||
/**
|
||||
* A map containing acknowledgement handlers.
|
||||
*
|
||||
* The `withError` attribute is used to differentiate handlers that accept an error as first argument:
|
||||
*
|
||||
* - `socket.emit("test", (err, value) => { ... })` with `ackTimeout` option
|
||||
* - `socket.timeout(5000).emit("test", (err, value) => { ... })`
|
||||
* - `const value = await socket.emitWithAck("test")`
|
||||
*
|
||||
* From those that don't:
|
||||
*
|
||||
* - `socket.emit("test", (value) => { ... });`
|
||||
*
|
||||
* In the first case, the handlers will be called with an error when:
|
||||
*
|
||||
* - the timeout is reached
|
||||
* - the socket gets disconnected
|
||||
*
|
||||
* In the second case, the handlers will be simply discarded upon disconnection, since the client will never receive
|
||||
* an acknowledgement from the server.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_this.acks = {};
|
||||
_this.flags = {};
|
||||
_this.io = io;
|
||||
@@ -3216,14 +3239,16 @@
|
||||
}
|
||||
ack.call(_this2, new Error("operation has timed out"));
|
||||
}, timeout);
|
||||
this.acks[id] = function () {
|
||||
var fn = function fn() {
|
||||
// @ts-ignore
|
||||
_this2.io.clearTimeoutFn(timer);
|
||||
for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
|
||||
args[_key3] = arguments[_key3];
|
||||
}
|
||||
ack.apply(_this2, [null].concat(args));
|
||||
ack.apply(_this2, args);
|
||||
};
|
||||
fn.withError = true;
|
||||
this.acks[id] = fn;
|
||||
}
|
||||
/**
|
||||
* Emits an event and waits for an acknowledgement
|
||||
@@ -3248,16 +3273,12 @@
|
||||
for (var _len4 = arguments.length, args = new Array(_len4 > 1 ? _len4 - 1 : 0), _key4 = 1; _key4 < _len4; _key4++) {
|
||||
args[_key4 - 1] = arguments[_key4];
|
||||
}
|
||||
// the timeout flag is optional
|
||||
var withErr = this.flags.timeout !== undefined || this._opts.ackTimeout !== undefined;
|
||||
return new Promise(function (resolve, reject) {
|
||||
args.push(function (arg1, arg2) {
|
||||
if (withErr) {
|
||||
return arg1 ? reject(arg1) : resolve(arg2);
|
||||
} else {
|
||||
return resolve(arg1);
|
||||
}
|
||||
});
|
||||
var fn = function fn(arg1, arg2) {
|
||||
return arg1 ? reject(arg1) : resolve(arg2);
|
||||
};
|
||||
fn.withError = true;
|
||||
args.push(fn);
|
||||
_this3.emit.apply(_this3, [ev].concat(args));
|
||||
});
|
||||
}
|
||||
@@ -3405,6 +3426,31 @@
|
||||
this.connected = false;
|
||||
delete this.id;
|
||||
this.emitReserved("disconnect", reason, description);
|
||||
this._clearAcks();
|
||||
}
|
||||
/**
|
||||
* Clears the acknowledgement handlers upon disconnection, since the client will never receive an acknowledgement from
|
||||
* the server.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
}, {
|
||||
key: "_clearAcks",
|
||||
value: function _clearAcks() {
|
||||
var _this6 = this;
|
||||
Object.keys(this.acks).forEach(function (id) {
|
||||
var isBuffered = _this6.sendBuffer.some(function (packet) {
|
||||
return String(packet.id) === id;
|
||||
});
|
||||
if (!isBuffered) {
|
||||
// note: handlers that do not accept an error as first argument are ignored here
|
||||
var ack = _this6.acks[id];
|
||||
delete _this6.acks[id];
|
||||
if (ack.withError) {
|
||||
ack.call(_this6, new Error("socket has been disconnected"));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Called with socket packet.
|
||||
@@ -3512,7 +3558,7 @@
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Called upon a server acknowlegement.
|
||||
* Called upon a server acknowledgement.
|
||||
*
|
||||
* @param packet
|
||||
* @private
|
||||
@@ -3521,10 +3567,16 @@
|
||||
key: "onack",
|
||||
value: function onack(packet) {
|
||||
var ack = this.acks[packet.id];
|
||||
if ("function" === typeof ack) {
|
||||
ack.apply(this, packet.data);
|
||||
delete this.acks[packet.id];
|
||||
if (typeof ack !== "function") {
|
||||
return;
|
||||
}
|
||||
delete this.acks[packet.id];
|
||||
// @ts-ignore FIXME ack is incorrectly inferred as 'never'
|
||||
if (ack.withError) {
|
||||
packet.data.unshift(null);
|
||||
}
|
||||
// @ts-ignore
|
||||
ack.apply(this, packet.data);
|
||||
}
|
||||
/**
|
||||
* Called upon server connect.
|
||||
@@ -3550,14 +3602,14 @@
|
||||
}, {
|
||||
key: "emitBuffered",
|
||||
value: function emitBuffered() {
|
||||
var _this6 = this;
|
||||
var _this7 = this;
|
||||
this.receiveBuffer.forEach(function (args) {
|
||||
return _this6.emitEvent(args);
|
||||
return _this7.emitEvent(args);
|
||||
});
|
||||
this.receiveBuffer = [];
|
||||
this.sendBuffer.forEach(function (packet) {
|
||||
_this6.notifyOutgoingListeners(packet);
|
||||
_this6.packet(packet);
|
||||
_this7.notifyOutgoingListeners(packet);
|
||||
_this7.packet(packet);
|
||||
});
|
||||
this.sendBuffer = [];
|
||||
}
|
||||
|
||||
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
@@ -5,6 +5,8 @@ This example shows how to retrieve the authentication context from a basic [Expr
|
||||
|
||||

|
||||
|
||||
Please read the related guide: https://socket.io/how-to/use-with-passport
|
||||
|
||||
## How to use
|
||||
|
||||
```
|
||||
@@ -12,3 +14,33 @@ $ npm ci && npm start
|
||||
```
|
||||
|
||||
And point your browser to `http://localhost:3000`. Optionally, specify a port by supplying the `PORT` env variable.
|
||||
|
||||
## How it works
|
||||
|
||||
The Socket.IO server retrieves the user context from the session:
|
||||
|
||||
```js
|
||||
function onlyForHandshake(middleware) {
|
||||
return (req, res, next) => {
|
||||
const isHandshake = req._query.sid === undefined;
|
||||
if (isHandshake) {
|
||||
middleware(req, res, next);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
io.engine.use(onlyForHandshake(sessionMiddleware));
|
||||
io.engine.use(onlyForHandshake(passport.session()));
|
||||
io.engine.use(
|
||||
onlyForHandshake((req, res, next) => {
|
||||
if (req.user) {
|
||||
next();
|
||||
} else {
|
||||
res.writeHead(401);
|
||||
res.end();
|
||||
}
|
||||
}),
|
||||
);
|
||||
```
|
||||
|
||||
55
examples/passport-example/cjs/index.html
Normal file
55
examples/passport-example/cjs/index.html
Normal file
@@ -0,0 +1,55 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Passport example</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Authenticated!</p>
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Status</td>
|
||||
<td><span id="status">Disconnected</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Socket ID</td>
|
||||
<td><span id="socket-id"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Username</td>
|
||||
<td><span id="username"></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<form action="/logout" method="post">
|
||||
<div>
|
||||
<input type="submit" value="Log out" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script>
|
||||
const socket = io();
|
||||
const socketIdSpan = document.getElementById('socket-id');
|
||||
const usernameSpan = document.getElementById('username');
|
||||
const statusSpan = document.getElementById('status');
|
||||
|
||||
socket.on('connect', () => {
|
||||
statusSpan.innerText = 'connected';
|
||||
socketIdSpan.innerText = socket.id;
|
||||
|
||||
socket.emit('whoami', (username) => {
|
||||
usernameSpan.innerText = username;
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
statusSpan.innerText = 'disconnected';
|
||||
socketIdSpan.innerText = '-';
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
117
examples/passport-example/cjs/index.js
Normal file
117
examples/passport-example/cjs/index.js
Normal file
@@ -0,0 +1,117 @@
|
||||
const express = require("express");
|
||||
const { createServer } = require("node:http");
|
||||
const { Server } = require("socket.io");
|
||||
const session = require("express-session");
|
||||
const bodyParser = require("body-parser");
|
||||
const passport = require("passport");
|
||||
const LocalStrategy = require("passport-local").Strategy;
|
||||
const { join } = require("node:path");
|
||||
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
const app = express();
|
||||
const httpServer = createServer(app);
|
||||
|
||||
const sessionMiddleware = session({
|
||||
secret: "changeit",
|
||||
resave: true,
|
||||
saveUninitialized: true,
|
||||
});
|
||||
|
||||
app.use(sessionMiddleware);
|
||||
app.use(bodyParser.urlencoded({ extended: false }));
|
||||
app.use(passport.session());
|
||||
|
||||
app.get("/", (req, res) => {
|
||||
if (!req.user) {
|
||||
return res.redirect("/login");
|
||||
}
|
||||
res.sendFile(join(__dirname, "index.html"));
|
||||
});
|
||||
|
||||
app.get("/login", (req, res) => {
|
||||
if (req.user) {
|
||||
return res.redirect("/");
|
||||
}
|
||||
res.sendFile(join(__dirname, "login.html"));
|
||||
});
|
||||
|
||||
app.post(
|
||||
"/login",
|
||||
passport.authenticate("local", {
|
||||
successRedirect: "/",
|
||||
failureRedirect: "/",
|
||||
}),
|
||||
);
|
||||
|
||||
app.post("/logout", (req, res) => {
|
||||
const sessionId = req.session.id;
|
||||
req.session.destroy(() => {
|
||||
// disconnect all Socket.IO connections linked to this session ID
|
||||
io.to(`session:${sessionId}`).disconnectSockets();
|
||||
res.status(204).end();
|
||||
});
|
||||
});
|
||||
|
||||
passport.use(
|
||||
new LocalStrategy((username, password, done) => {
|
||||
if (username === "john" && password === "changeit") {
|
||||
console.log("authentication OK");
|
||||
return done(null, { id: 1, username });
|
||||
} else {
|
||||
console.log("wrong credentials");
|
||||
return done(null, false);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
passport.serializeUser((user, cb) => {
|
||||
console.log(`serializeUser ${user.id}`);
|
||||
cb(null, user);
|
||||
});
|
||||
|
||||
passport.deserializeUser((user, cb) => {
|
||||
console.log(`deserializeUser ${user.id}`);
|
||||
cb(null, user);
|
||||
});
|
||||
|
||||
const io = new Server(httpServer);
|
||||
|
||||
function onlyForHandshake(middleware) {
|
||||
return (req, res, next) => {
|
||||
const isHandshake = req._query.sid === undefined;
|
||||
if (isHandshake) {
|
||||
middleware(req, res, next);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
io.engine.use(onlyForHandshake(sessionMiddleware));
|
||||
io.engine.use(onlyForHandshake(passport.session()));
|
||||
io.engine.use(
|
||||
onlyForHandshake((req, res, next) => {
|
||||
if (req.user) {
|
||||
next();
|
||||
} else {
|
||||
res.writeHead(401);
|
||||
res.end();
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
const req = socket.request;
|
||||
|
||||
socket.join(`session:${req.session.id}`);
|
||||
socket.join(`user:${req.user.id}`);
|
||||
|
||||
socket.on("whoami", (cb) => {
|
||||
cb(req.user.username);
|
||||
});
|
||||
});
|
||||
|
||||
httpServer.listen(port, () => {
|
||||
console.log(`application is running at: http://localhost:${port}`);
|
||||
});
|
||||
@@ -8,17 +8,17 @@
|
||||
<p>Not authenticated</p>
|
||||
<form action="/login" method="post">
|
||||
<div>
|
||||
<label>Username:</label>
|
||||
<input type="text" name="username" />
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" id="username" name="username" />
|
||||
<br/>
|
||||
</div>
|
||||
<div>
|
||||
<label>Password:</label>
|
||||
<input type="password" name="password" />
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" id="password" name="password" />
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" value="Submit" />
|
||||
</div>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
20
examples/passport-example/cjs/package.json
Normal file
20
examples/passport-example/cjs/package.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "passport-example",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "commonjs",
|
||||
"description": "Example with passport (https://www.passportjs.org)",
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "~4.17.3",
|
||||
"express-session": "~1.17.2",
|
||||
"passport": "^0.7.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"socket.io": "^4.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^3.1.1"
|
||||
}
|
||||
}
|
||||
55
examples/passport-example/esm/index.html
Normal file
55
examples/passport-example/esm/index.html
Normal file
@@ -0,0 +1,55 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Passport example</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Authenticated!</p>
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Status</td>
|
||||
<td><span id="status">Disconnected</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Socket ID</td>
|
||||
<td><span id="socket-id"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Username</td>
|
||||
<td><span id="username"></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<form action="/logout" method="post">
|
||||
<div>
|
||||
<input type="submit" value="Log out" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script>
|
||||
const socket = io();
|
||||
const socketIdSpan = document.getElementById('socket-id');
|
||||
const usernameSpan = document.getElementById('username');
|
||||
const statusSpan = document.getElementById('status');
|
||||
|
||||
socket.on('connect', () => {
|
||||
statusSpan.innerText = 'connected';
|
||||
socketIdSpan.innerText = socket.id;
|
||||
|
||||
socket.emit('whoami', (username) => {
|
||||
usernameSpan.innerText = username;
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
statusSpan.innerText = 'disconnected';
|
||||
socketIdSpan.innerText = '-';
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
120
examples/passport-example/esm/index.js
Normal file
120
examples/passport-example/esm/index.js
Normal file
@@ -0,0 +1,120 @@
|
||||
import express from "express";
|
||||
import { createServer } from "http";
|
||||
import { Server } from "socket.io";
|
||||
import session from "express-session";
|
||||
import bodyParser from "body-parser";
|
||||
import passport from "passport";
|
||||
import { Strategy as LocalStrategy } from "passport-local";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
const app = express();
|
||||
const httpServer = createServer(app);
|
||||
|
||||
const sessionMiddleware = session({
|
||||
secret: "changeit",
|
||||
resave: true,
|
||||
saveUninitialized: true,
|
||||
});
|
||||
|
||||
app.use(sessionMiddleware);
|
||||
app.use(bodyParser.urlencoded({ extended: false }));
|
||||
app.use(passport.session());
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
app.get("/", (req, res) => {
|
||||
if (!req.user) {
|
||||
return res.redirect("/login");
|
||||
}
|
||||
res.sendFile(join(__dirname, "index.html"));
|
||||
});
|
||||
|
||||
app.get("/login", (req, res) => {
|
||||
if (req.user) {
|
||||
return res.redirect("/");
|
||||
}
|
||||
res.sendFile(join(__dirname, "login.html"));
|
||||
});
|
||||
|
||||
app.post(
|
||||
"/login",
|
||||
passport.authenticate("local", {
|
||||
successRedirect: "/",
|
||||
failureRedirect: "/",
|
||||
}),
|
||||
);
|
||||
|
||||
app.post("/logout", (req, res) => {
|
||||
const sessionId = req.session.id;
|
||||
req.session.destroy(() => {
|
||||
// disconnect all Socket.IO connections linked to this session ID
|
||||
io.to(`session:${sessionId}`).disconnectSockets();
|
||||
res.status(204).end();
|
||||
});
|
||||
});
|
||||
|
||||
passport.use(
|
||||
new LocalStrategy((username, password, done) => {
|
||||
if (username === "john" && password === "changeit") {
|
||||
console.log("authentication OK");
|
||||
return done(null, { id: 1, username });
|
||||
} else {
|
||||
console.log("wrong credentials");
|
||||
return done(null, false);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
passport.serializeUser((user, cb) => {
|
||||
console.log(`serializeUser ${user.id}`);
|
||||
cb(null, user);
|
||||
});
|
||||
|
||||
passport.deserializeUser((user, cb) => {
|
||||
console.log(`deserializeUser ${user.id}`);
|
||||
cb(null, user);
|
||||
});
|
||||
|
||||
const io = new Server(httpServer);
|
||||
|
||||
function onlyForHandshake(middleware) {
|
||||
return (req, res, next) => {
|
||||
const isHandshake = req._query.sid === undefined;
|
||||
if (isHandshake) {
|
||||
middleware(req, res, next);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
io.engine.use(onlyForHandshake(sessionMiddleware));
|
||||
io.engine.use(onlyForHandshake(passport.session()));
|
||||
io.engine.use(
|
||||
onlyForHandshake((req, res, next) => {
|
||||
if (req.user) {
|
||||
next();
|
||||
} else {
|
||||
res.writeHead(401);
|
||||
res.end();
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
const req = socket.request;
|
||||
|
||||
socket.join(`session:${req.session.id}`);
|
||||
socket.join(`user:${req.user.id}`);
|
||||
|
||||
socket.on("whoami", (cb) => {
|
||||
cb(req.user.username);
|
||||
});
|
||||
});
|
||||
|
||||
httpServer.listen(port, () => {
|
||||
console.log(`application is running at: http://localhost:${port}`);
|
||||
});
|
||||
24
examples/passport-example/esm/login.html
Normal file
24
examples/passport-example/esm/login.html
Normal file
@@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Passport example</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Not authenticated</p>
|
||||
<form action="/login" method="post">
|
||||
<div>
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" id="username" name="username" />
|
||||
<br/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" id="password" name="password" />
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" value="Submit" />
|
||||
</div>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
20
examples/passport-example/esm/package.json
Normal file
20
examples/passport-example/esm/package.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "passport-example",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Example with passport (https://www.passportjs.org)",
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "~4.17.3",
|
||||
"express-session": "~1.17.2",
|
||||
"passport": "^0.7.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"socket.io": "^4.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^3.1.1"
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Passport example</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Authenticated!</p>
|
||||
<p>Socket ID: <span id="socketId"></span></p>
|
||||
<p>Username: <span id="username"></span></p>
|
||||
<form action="/logout" method="post">
|
||||
<div>
|
||||
<input type="submit" value="Log out" />
|
||||
</div>
|
||||
</form>
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script>
|
||||
const socket = io();
|
||||
const socketIdSpan = document.getElementById("socketId");
|
||||
const usernameSpan = document.getElementById("username");
|
||||
|
||||
socket.on('connect', () => {
|
||||
socketIdSpan.innerText = socket.id;
|
||||
|
||||
socket.emit('whoami', (username) => {
|
||||
usernameSpan.innerText = username;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,104 +0,0 @@
|
||||
const app = require("express")();
|
||||
const server = require("http").createServer(app);
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
const session = require("express-session");
|
||||
const bodyParser = require("body-parser");
|
||||
const passport = require("passport");
|
||||
const LocalStrategy = require("passport-local").Strategy;
|
||||
|
||||
const sessionMiddleware = session({ secret: "changeit", resave: false, saveUninitialized: false });
|
||||
app.use(sessionMiddleware);
|
||||
app.use(bodyParser.urlencoded({ extended: false }));
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
|
||||
const DUMMY_USER = {
|
||||
id: 1,
|
||||
username: "john",
|
||||
};
|
||||
|
||||
passport.use(
|
||||
new LocalStrategy((username, password, done) => {
|
||||
if (username === "john" && password === "doe") {
|
||||
console.log("authentication OK");
|
||||
return done(null, DUMMY_USER);
|
||||
} else {
|
||||
console.log("wrong credentials");
|
||||
return done(null, false);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
app.get("/", (req, res) => {
|
||||
const isAuthenticated = !!req.user;
|
||||
if (isAuthenticated) {
|
||||
console.log(`user is authenticated, session is ${req.session.id}`);
|
||||
} else {
|
||||
console.log("unknown user");
|
||||
}
|
||||
res.sendFile(isAuthenticated ? "index.html" : "login.html", { root: __dirname });
|
||||
});
|
||||
|
||||
app.post(
|
||||
"/login",
|
||||
passport.authenticate("local", {
|
||||
successRedirect: "/",
|
||||
failureRedirect: "/",
|
||||
})
|
||||
);
|
||||
|
||||
app.post("/logout", (req, res) => {
|
||||
console.log(`logout ${req.session.id}`);
|
||||
const socketId = req.session.socketId;
|
||||
if (socketId && io.of("/").sockets.get(socketId)) {
|
||||
console.log(`forcefully closing socket ${socketId}`);
|
||||
io.of("/").sockets.get(socketId).disconnect(true);
|
||||
}
|
||||
req.logout();
|
||||
res.cookie("connect.sid", "", { expires: new Date() });
|
||||
res.redirect("/");
|
||||
});
|
||||
|
||||
passport.serializeUser((user, cb) => {
|
||||
console.log(`serializeUser ${user.id}`);
|
||||
cb(null, user.id);
|
||||
});
|
||||
|
||||
passport.deserializeUser((id, cb) => {
|
||||
console.log(`deserializeUser ${id}`);
|
||||
cb(null, DUMMY_USER);
|
||||
});
|
||||
|
||||
const io = require('socket.io')(server);
|
||||
|
||||
// convert a connect middleware to a Socket.IO middleware
|
||||
const wrap = middleware => (socket, next) => middleware(socket.request, {}, next);
|
||||
|
||||
io.use(wrap(sessionMiddleware));
|
||||
io.use(wrap(passport.initialize()));
|
||||
io.use(wrap(passport.session()));
|
||||
|
||||
io.use((socket, next) => {
|
||||
if (socket.request.user) {
|
||||
next();
|
||||
} else {
|
||||
next(new Error('unauthorized'))
|
||||
}
|
||||
});
|
||||
|
||||
io.on('connect', (socket) => {
|
||||
console.log(`new connection ${socket.id}`);
|
||||
socket.on('whoami', (cb) => {
|
||||
cb(socket.request.user ? socket.request.user.username : '');
|
||||
});
|
||||
|
||||
const session = socket.request.session;
|
||||
console.log(`saving sid ${socket.id} in session ${session.id}`);
|
||||
session.socketId = socket.id;
|
||||
session.save();
|
||||
});
|
||||
|
||||
server.listen(port, () => {
|
||||
console.log(`application is running at: http://localhost:${port}`);
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"name": "passport-example",
|
||||
"version": "0.0.1",
|
||||
"description": "Example with Passport (http://www.passportjs.org/)",
|
||||
"dependencies": {
|
||||
"body-parser": "~1.19.0",
|
||||
"express": "~4.17.1",
|
||||
"express-session": "~1.17.1",
|
||||
"passport": "~0.4.1",
|
||||
"passport-local": "~1.0.0",
|
||||
"socket.io": "^4.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
}
|
||||
}
|
||||
55
examples/passport-example/ts/index.html
Normal file
55
examples/passport-example/ts/index.html
Normal file
@@ -0,0 +1,55 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Passport example</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Authenticated!</p>
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Status</td>
|
||||
<td><span id="status">Disconnected</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Socket ID</td>
|
||||
<td><span id="socket-id"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Username</td>
|
||||
<td><span id="username"></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<form action="/logout" method="post">
|
||||
<div>
|
||||
<input type="submit" value="Log out" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script>
|
||||
const socket = io();
|
||||
const socketIdSpan = document.getElementById('socket-id');
|
||||
const usernameSpan = document.getElementById('username');
|
||||
const statusSpan = document.getElementById('status');
|
||||
|
||||
socket.on('connect', () => {
|
||||
statusSpan.innerText = 'connected';
|
||||
socketIdSpan.innerText = socket.id;
|
||||
|
||||
socket.emit('whoami', (username) => {
|
||||
usernameSpan.innerText = username;
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
statusSpan.innerText = 'disconnected';
|
||||
socketIdSpan.innerText = '-';
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
137
examples/passport-example/ts/index.ts
Normal file
137
examples/passport-example/ts/index.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import express = require("express");
|
||||
import { createServer } from "http";
|
||||
import { Server } from "socket.io";
|
||||
import session from "express-session";
|
||||
import { type Request, type Response } from "express";
|
||||
import bodyParser = require("body-parser");
|
||||
import passport = require("passport");
|
||||
import { Strategy as LocalStrategy } from "passport-local";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface User {
|
||||
id: number;
|
||||
username: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
const app = express();
|
||||
const httpServer = createServer(app);
|
||||
|
||||
const sessionMiddleware = session({
|
||||
secret: "changeit",
|
||||
resave: true,
|
||||
saveUninitialized: true,
|
||||
});
|
||||
|
||||
app.use(sessionMiddleware);
|
||||
app.use(bodyParser.urlencoded({ extended: false }));
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
app.get("/", (req, res) => {
|
||||
if (!req.user) {
|
||||
return res.redirect("/login");
|
||||
}
|
||||
res.sendFile(join(__dirname, "index.html"));
|
||||
});
|
||||
|
||||
app.get("/login", (req, res) => {
|
||||
if (req.user) {
|
||||
return res.redirect("/");
|
||||
}
|
||||
res.sendFile(join(__dirname, "login.html"));
|
||||
});
|
||||
|
||||
app.post(
|
||||
"/login",
|
||||
passport.authenticate("local", {
|
||||
successRedirect: "/",
|
||||
failureRedirect: "/",
|
||||
}),
|
||||
);
|
||||
|
||||
app.post("/logout", (req, res) => {
|
||||
const sessionId = req.session.id;
|
||||
req.session.destroy(() => {
|
||||
// disconnect all Socket.IO connections linked to this session ID
|
||||
io.to(`session:${sessionId}`).disconnectSockets();
|
||||
res.status(204).end();
|
||||
});
|
||||
});
|
||||
|
||||
passport.use(
|
||||
new LocalStrategy((username, password, done) => {
|
||||
if (username === "john" && password === "changeit") {
|
||||
console.log("authentication OK");
|
||||
return done(null, { id: 1, username });
|
||||
} else {
|
||||
console.log("wrong credentials");
|
||||
return done(null, false);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
passport.serializeUser((user, cb) => {
|
||||
console.log(`serializeUser ${user.id}`);
|
||||
cb(null, user);
|
||||
});
|
||||
|
||||
passport.deserializeUser((user: Express.User, cb) => {
|
||||
console.log(`deserializeUser ${user.id}`);
|
||||
cb(null, user);
|
||||
});
|
||||
|
||||
const io = new Server(httpServer);
|
||||
|
||||
function onlyForHandshake(
|
||||
middleware: (req: Request, res: Response, next: any) => void,
|
||||
) {
|
||||
return (
|
||||
req: Request & { _query: Record<string, string> },
|
||||
res: Response,
|
||||
next: (err?: Error) => void,
|
||||
) => {
|
||||
const isHandshake = req._query.sid === undefined;
|
||||
if (isHandshake) {
|
||||
middleware(req, res, next);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
io.engine.use(onlyForHandshake(sessionMiddleware));
|
||||
io.engine.use(onlyForHandshake(passport.session()));
|
||||
io.engine.use(
|
||||
onlyForHandshake((req, res, next) => {
|
||||
if (req.user) {
|
||||
next();
|
||||
} else {
|
||||
res.writeHead(401);
|
||||
res.end();
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
const req = socket.request as Request & { user: Express.User };
|
||||
|
||||
socket.join(`session:${req.session.id}`);
|
||||
socket.join(`user:${req.user.id}`);
|
||||
|
||||
socket.on("whoami", (cb) => {
|
||||
cb(req.user.username);
|
||||
});
|
||||
});
|
||||
|
||||
httpServer.listen(port, () => {
|
||||
console.log(`application is running at: http://localhost:${port}`);
|
||||
});
|
||||
24
examples/passport-example/ts/login.html
Normal file
24
examples/passport-example/ts/login.html
Normal file
@@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Passport example</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Not authenticated</p>
|
||||
<form action="/login" method="post">
|
||||
<div>
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" id="username" name="username" />
|
||||
<br/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" id="password" name="password" />
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" value="Submit" />
|
||||
</div>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
27
examples/passport-example/ts/package.json
Normal file
27
examples/passport-example/ts/package.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "passport-example",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Example with passport (https://www.passportjs.org)",
|
||||
"scripts": {
|
||||
"start": "ts-node index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/express-session": "^1.17.7",
|
||||
"@types/node": "^20.6.0",
|
||||
"@types/passport": "^1.0.16",
|
||||
"express": "~4.17.3",
|
||||
"express-session": "~1.17.2",
|
||||
"passport": "^0.7.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"socket.io": "^4.7.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/passport-local": "^1.0.38",
|
||||
"prettier": "^3.1.1"
|
||||
}
|
||||
}
|
||||
11
examples/passport-example/ts/tsconfig.json
Normal file
11
examples/passport-example/ts/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"target": "ES2022",
|
||||
"strict": true
|
||||
},
|
||||
"ts-node": {
|
||||
"esm": true
|
||||
}
|
||||
}
|
||||
41
examples/passport-jwt-example/README.md
Normal file
41
examples/passport-jwt-example/README.md
Normal file
@@ -0,0 +1,41 @@
|
||||
|
||||
# Example with [`passport-jwt`](https://www.passportjs.org/packages/passport-jwt/)
|
||||
|
||||
This example shows how to retrieve the authentication context from a basic [Express](http://expressjs.com/) + [Passport](http://www.passportjs.org/) application.
|
||||
|
||||

|
||||
|
||||
Please read the related guide: https://socket.io/how-to/use-with-jwt
|
||||
|
||||
## How to use
|
||||
|
||||
```
|
||||
$ npm ci && npm start
|
||||
```
|
||||
|
||||
And point your browser to `http://localhost:3000`. Optionally, specify a port by supplying the `PORT` env variable.
|
||||
|
||||
## How it works
|
||||
|
||||
The client sends the JWT in the headers:
|
||||
|
||||
```js
|
||||
const socket = io({
|
||||
extraHeaders: {
|
||||
authorization: `bearer token`
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
And the Socket.IO server then parses the token and retrieves the user context:
|
||||
|
||||
```js
|
||||
io.engine.use((req, res, next) => {
|
||||
const isHandshake = req._query.sid === undefined;
|
||||
if (isHandshake) {
|
||||
passport.authenticate("jwt", { session: false })(req, res, next);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
```
|
||||
BIN
examples/passport-jwt-example/assets/passport_example.gif
Normal file
BIN
examples/passport-jwt-example/assets/passport_example.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
154
examples/passport-jwt-example/cjs/index.html
Normal file
154
examples/passport-jwt-example/cjs/index.html
Normal file
@@ -0,0 +1,154 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Passport JWT example</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="login-panel" style="display: none">
|
||||
<p>Not authenticated</p>
|
||||
<form id="login-form">
|
||||
<div>
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" id="username" name="username" value="john" />
|
||||
<br/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" id="password" name="password" value="changeit" />
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" value="Submit" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="home-panel" style="display: none">
|
||||
<p>Authenticated!</p>
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Status</td>
|
||||
<td><span id="status">Disconnected</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Socket ID</td>
|
||||
<td><span id="socket-id"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Username</td>
|
||||
<td><span id="name"></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<form id="logout-form">
|
||||
<div>
|
||||
<input type="submit" value="Log out" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script>
|
||||
const loginPanel = document.getElementById('login-panel');
|
||||
const homePanel = document.getElementById('home-panel');
|
||||
const loginForm = document.getElementById('login-form');
|
||||
const usernameInput = document.getElementById('username');
|
||||
const passwordInput = document.getElementById('password');
|
||||
const statusSpan = document.getElementById('status');
|
||||
const socketIdSpan = document.getElementById('socket-id');
|
||||
const usernameSpan = document.getElementById('name');
|
||||
const logoutForm = document.getElementById('logout-form');
|
||||
|
||||
let socket;
|
||||
|
||||
async function main() {
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
if (!token) {
|
||||
return showLoginPanel();
|
||||
}
|
||||
|
||||
const res = await fetch('/self', {
|
||||
headers: {
|
||||
authorization: `bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
if (res.status === 200) {
|
||||
showHomePanel();
|
||||
} else {
|
||||
showLoginPanel();
|
||||
}
|
||||
}
|
||||
|
||||
function showHomePanel() {
|
||||
loginPanel.style.display = 'none';
|
||||
homePanel.style.display = 'block';
|
||||
|
||||
// this will only work if HTTP long-polling is enabled, since WebSockets do not support providing additional headers
|
||||
socket = io({
|
||||
extraHeaders: {
|
||||
authorization: `bearer ${localStorage.getItem('token')}`
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('connect', () => {
|
||||
statusSpan.innerText = 'connected';
|
||||
socketIdSpan.innerText = socket.id;
|
||||
|
||||
socket.emit('whoami', (username) => {
|
||||
usernameSpan.innerText = username;
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
statusSpan.innerText = 'disconnected';
|
||||
socketIdSpan.innerText = '-';
|
||||
});
|
||||
}
|
||||
|
||||
function showLoginPanel() {
|
||||
loginPanel.style.display = 'block';
|
||||
homePanel.style.display = 'none';
|
||||
}
|
||||
|
||||
loginForm.onsubmit = async function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const res = await fetch('/login', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'content-type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: usernameInput.value,
|
||||
password: passwordInput.value
|
||||
})
|
||||
})
|
||||
|
||||
if (res.status === 200) {
|
||||
const { token } = await res.json();
|
||||
localStorage.setItem('token', token);
|
||||
|
||||
showHomePanel();
|
||||
} else {
|
||||
passwordInput.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
logoutForm.onsubmit = function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
socket.disconnect();
|
||||
localStorage.removeItem('token');
|
||||
|
||||
showLoginPanel();
|
||||
}
|
||||
|
||||
main();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
100
examples/passport-jwt-example/cjs/index.js
Normal file
100
examples/passport-jwt-example/cjs/index.js
Normal file
@@ -0,0 +1,100 @@
|
||||
const express = require("express");
|
||||
const { createServer } = require("node:http");
|
||||
const { join } = require("node:path");
|
||||
const passport = require("passport");
|
||||
const passportJwt = require("passport-jwt");
|
||||
const JwtStrategy = passportJwt.Strategy;
|
||||
const ExtractJwt = passportJwt.ExtractJwt;
|
||||
const bodyParser = require("body-parser");
|
||||
const { Server } = require("socket.io");
|
||||
const jwt = require("jsonwebtoken");
|
||||
|
||||
const port = process.env.PORT || 3000;
|
||||
const jwtSecret = "Mys3cr3t";
|
||||
|
||||
const app = express();
|
||||
const httpServer = createServer(app);
|
||||
|
||||
app.use(bodyParser.json());
|
||||
|
||||
app.get("/", (req, res) => {
|
||||
res.sendFile(join(__dirname, "index.html"));
|
||||
});
|
||||
|
||||
app.get(
|
||||
"/self",
|
||||
passport.authenticate("jwt", { session: false }),
|
||||
(req, res) => {
|
||||
if (req.user) {
|
||||
res.send(req.user);
|
||||
} else {
|
||||
res.status(401).end();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
app.post("/login", (req, res) => {
|
||||
if (req.body.username === "john" && req.body.password === "changeit") {
|
||||
console.log("authentication OK");
|
||||
|
||||
const user = {
|
||||
id: 1,
|
||||
username: "john",
|
||||
};
|
||||
|
||||
const token = jwt.sign(
|
||||
{
|
||||
data: user,
|
||||
},
|
||||
jwtSecret,
|
||||
{
|
||||
issuer: "accounts.examplesoft.com",
|
||||
audience: "yoursite.net",
|
||||
expiresIn: "1h",
|
||||
},
|
||||
);
|
||||
|
||||
res.json({ token });
|
||||
} else {
|
||||
console.log("wrong credentials");
|
||||
res.status(401).end();
|
||||
}
|
||||
});
|
||||
|
||||
const jwtDecodeOptions = {
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
secretOrKey: jwtSecret,
|
||||
issuer: "accounts.examplesoft.com",
|
||||
audience: "yoursite.net",
|
||||
};
|
||||
|
||||
passport.use(
|
||||
new JwtStrategy(jwtDecodeOptions, (payload, done) => {
|
||||
return done(null, payload.data);
|
||||
}),
|
||||
);
|
||||
|
||||
const io = new Server(httpServer);
|
||||
|
||||
io.engine.use((req, res, next) => {
|
||||
const isHandshake = req._query.sid === undefined;
|
||||
if (isHandshake) {
|
||||
passport.authenticate("jwt", { session: false })(req, res, next);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
const req = socket.request;
|
||||
|
||||
socket.join(`user:${req.user.id}`);
|
||||
|
||||
socket.on("whoami", (cb) => {
|
||||
cb(req.user.username);
|
||||
});
|
||||
});
|
||||
|
||||
httpServer.listen(port, () => {
|
||||
console.log(`application is running at: http://localhost:${port}`);
|
||||
});
|
||||
21
examples/passport-jwt-example/cjs/package.json
Normal file
21
examples/passport-jwt-example/cjs/package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "passport-jwt-example",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "commonjs",
|
||||
"description": "Example with passport and JWT (https://www.passportjs.org/packages/passport-jwt/)",
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"body-parser": "^1.20.2",
|
||||
"express": "~4.17.3",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"passport": "^0.7.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"socket.io": "^4.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^3.1.1"
|
||||
}
|
||||
}
|
||||
154
examples/passport-jwt-example/esm/index.html
Normal file
154
examples/passport-jwt-example/esm/index.html
Normal file
@@ -0,0 +1,154 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Passport JWT example</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="login-panel" style="display: none">
|
||||
<p>Not authenticated</p>
|
||||
<form id="login-form">
|
||||
<div>
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" id="username" name="username" value="john" />
|
||||
<br/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" id="password" name="password" value="changeit" />
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" value="Submit" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="home-panel" style="display: none">
|
||||
<p>Authenticated!</p>
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Status</td>
|
||||
<td><span id="status">Disconnected</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Socket ID</td>
|
||||
<td><span id="socket-id"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Username</td>
|
||||
<td><span id="name"></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<form id="logout-form">
|
||||
<div>
|
||||
<input type="submit" value="Log out" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script>
|
||||
const loginPanel = document.getElementById('login-panel');
|
||||
const homePanel = document.getElementById('home-panel');
|
||||
const loginForm = document.getElementById('login-form');
|
||||
const usernameInput = document.getElementById('username');
|
||||
const passwordInput = document.getElementById('password');
|
||||
const statusSpan = document.getElementById('status');
|
||||
const socketIdSpan = document.getElementById('socket-id');
|
||||
const usernameSpan = document.getElementById('name');
|
||||
const logoutForm = document.getElementById('logout-form');
|
||||
|
||||
let socket;
|
||||
|
||||
async function main() {
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
if (!token) {
|
||||
return showLoginPanel();
|
||||
}
|
||||
|
||||
const res = await fetch('/self', {
|
||||
headers: {
|
||||
authorization: `bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
if (res.status === 200) {
|
||||
showHomePanel();
|
||||
} else {
|
||||
showLoginPanel();
|
||||
}
|
||||
}
|
||||
|
||||
function showHomePanel() {
|
||||
loginPanel.style.display = 'none';
|
||||
homePanel.style.display = 'block';
|
||||
|
||||
// this will only work if HTTP long-polling is enabled, since WebSockets do not support providing additional headers
|
||||
socket = io({
|
||||
extraHeaders: {
|
||||
authorization: `bearer ${localStorage.getItem('token')}`
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('connect', () => {
|
||||
statusSpan.innerText = 'connected';
|
||||
socketIdSpan.innerText = socket.id;
|
||||
|
||||
socket.emit('whoami', (username) => {
|
||||
usernameSpan.innerText = username;
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
statusSpan.innerText = 'disconnected';
|
||||
socketIdSpan.innerText = '-';
|
||||
});
|
||||
}
|
||||
|
||||
function showLoginPanel() {
|
||||
loginPanel.style.display = 'block';
|
||||
homePanel.style.display = 'none';
|
||||
}
|
||||
|
||||
loginForm.onsubmit = async function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const res = await fetch('/login', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'content-type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: usernameInput.value,
|
||||
password: passwordInput.value
|
||||
})
|
||||
})
|
||||
|
||||
if (res.status === 200) {
|
||||
const { token } = await res.json();
|
||||
localStorage.setItem('token', token);
|
||||
|
||||
showHomePanel();
|
||||
} else {
|
||||
passwordInput.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
logoutForm.onsubmit = function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
socket.disconnect();
|
||||
localStorage.removeItem('token');
|
||||
|
||||
showLoginPanel();
|
||||
}
|
||||
|
||||
main();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
101
examples/passport-jwt-example/esm/index.js
Normal file
101
examples/passport-jwt-example/esm/index.js
Normal file
@@ -0,0 +1,101 @@
|
||||
import express from "express";
|
||||
import { createServer } from "node:http";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import passport from "passport";
|
||||
import { Strategy as JwtStrategy, ExtractJwt } from "passport-jwt";
|
||||
import bodyParser from "body-parser";
|
||||
import { Server } from "socket.io";
|
||||
import jwt from "jsonwebtoken";
|
||||
|
||||
const port = process.env.PORT || 3000;
|
||||
const jwtSecret = "Mys3cr3t";
|
||||
|
||||
const app = express();
|
||||
const httpServer = createServer(app);
|
||||
|
||||
app.use(bodyParser.json());
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
app.get("/", (req, res) => {
|
||||
res.sendFile(join(__dirname, "index.html"));
|
||||
});
|
||||
|
||||
app.get(
|
||||
"/self",
|
||||
passport.authenticate("jwt", { session: false }),
|
||||
(req, res) => {
|
||||
if (req.user) {
|
||||
res.send(req.user);
|
||||
} else {
|
||||
res.status(401).end();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
app.post("/login", (req, res) => {
|
||||
if (req.body.username === "john" && req.body.password === "changeit") {
|
||||
console.log("authentication OK");
|
||||
|
||||
const user = {
|
||||
id: 1,
|
||||
username: "john",
|
||||
};
|
||||
|
||||
const token = jwt.sign(
|
||||
{
|
||||
data: user,
|
||||
},
|
||||
jwtSecret,
|
||||
{
|
||||
issuer: "accounts.examplesoft.com",
|
||||
audience: "yoursite.net",
|
||||
expiresIn: "1h",
|
||||
},
|
||||
);
|
||||
|
||||
res.json({ token });
|
||||
} else {
|
||||
console.log("wrong credentials");
|
||||
res.status(401).end();
|
||||
}
|
||||
});
|
||||
|
||||
const jwtDecodeOptions = {
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
secretOrKey: jwtSecret,
|
||||
issuer: "accounts.examplesoft.com",
|
||||
audience: "yoursite.net",
|
||||
};
|
||||
|
||||
passport.use(
|
||||
new JwtStrategy(jwtDecodeOptions, (payload, done) => {
|
||||
return done(null, payload.data);
|
||||
}),
|
||||
);
|
||||
|
||||
const io = new Server(httpServer);
|
||||
|
||||
io.engine.use((req, res, next) => {
|
||||
const isHandshake = req._query.sid === undefined;
|
||||
if (isHandshake) {
|
||||
passport.authenticate("jwt", { session: false })(req, res, next);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
const req = socket.request;
|
||||
|
||||
socket.join(`user:${req.user.id}`);
|
||||
|
||||
socket.on("whoami", (cb) => {
|
||||
cb(req.user.username);
|
||||
});
|
||||
});
|
||||
|
||||
httpServer.listen(port, () => {
|
||||
console.log(`application is running at: http://localhost:${port}`);
|
||||
});
|
||||
21
examples/passport-jwt-example/esm/package.json
Normal file
21
examples/passport-jwt-example/esm/package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "passport-jwt-example",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Example with passport and JWT (https://www.passportjs.org/packages/passport-jwt/)",
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"body-parser": "^1.20.2",
|
||||
"express": "~4.17.3",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"passport": "^0.7.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"socket.io": "^4.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^3.1.1"
|
||||
}
|
||||
}
|
||||
154
examples/passport-jwt-example/ts/index.html
Normal file
154
examples/passport-jwt-example/ts/index.html
Normal file
@@ -0,0 +1,154 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Passport JWT example</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="login-panel" style="display: none">
|
||||
<p>Not authenticated</p>
|
||||
<form id="login-form">
|
||||
<div>
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" id="username" name="username" value="john" />
|
||||
<br/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" id="password" name="password" value="changeit" />
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" value="Submit" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="home-panel" style="display: none">
|
||||
<p>Authenticated!</p>
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Status</td>
|
||||
<td><span id="status">Disconnected</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Socket ID</td>
|
||||
<td><span id="socket-id"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Username</td>
|
||||
<td><span id="name"></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<form id="logout-form">
|
||||
<div>
|
||||
<input type="submit" value="Log out" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script>
|
||||
const loginPanel = document.getElementById('login-panel');
|
||||
const homePanel = document.getElementById('home-panel');
|
||||
const loginForm = document.getElementById('login-form');
|
||||
const usernameInput = document.getElementById('username');
|
||||
const passwordInput = document.getElementById('password');
|
||||
const statusSpan = document.getElementById('status');
|
||||
const socketIdSpan = document.getElementById('socket-id');
|
||||
const usernameSpan = document.getElementById('name');
|
||||
const logoutForm = document.getElementById('logout-form');
|
||||
|
||||
let socket;
|
||||
|
||||
async function main() {
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
if (!token) {
|
||||
return showLoginPanel();
|
||||
}
|
||||
|
||||
const res = await fetch('/self', {
|
||||
headers: {
|
||||
authorization: `bearer ${token}`
|
||||
}
|
||||
});
|
||||
|
||||
if (res.status === 200) {
|
||||
showHomePanel();
|
||||
} else {
|
||||
showLoginPanel();
|
||||
}
|
||||
}
|
||||
|
||||
function showHomePanel() {
|
||||
loginPanel.style.display = 'none';
|
||||
homePanel.style.display = 'block';
|
||||
|
||||
// this will only work if HTTP long-polling is enabled, since WebSockets do not support providing additional headers
|
||||
socket = io({
|
||||
extraHeaders: {
|
||||
authorization: `bearer ${localStorage.getItem('token')}`
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('connect', () => {
|
||||
statusSpan.innerText = 'connected';
|
||||
socketIdSpan.innerText = socket.id;
|
||||
|
||||
socket.emit('whoami', (username) => {
|
||||
usernameSpan.innerText = username;
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
statusSpan.innerText = 'disconnected';
|
||||
socketIdSpan.innerText = '-';
|
||||
});
|
||||
}
|
||||
|
||||
function showLoginPanel() {
|
||||
loginPanel.style.display = 'block';
|
||||
homePanel.style.display = 'none';
|
||||
}
|
||||
|
||||
loginForm.onsubmit = async function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const res = await fetch('/login', {
|
||||
method: 'post',
|
||||
headers: {
|
||||
'content-type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: usernameInput.value,
|
||||
password: passwordInput.value
|
||||
})
|
||||
})
|
||||
|
||||
if (res.status === 200) {
|
||||
const { token } = await res.json();
|
||||
localStorage.setItem('token', token);
|
||||
|
||||
showHomePanel();
|
||||
} else {
|
||||
passwordInput.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
logoutForm.onsubmit = function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
socket.disconnect();
|
||||
localStorage.removeItem('token');
|
||||
|
||||
showLoginPanel();
|
||||
}
|
||||
|
||||
main();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
113
examples/passport-jwt-example/ts/index.ts
Normal file
113
examples/passport-jwt-example/ts/index.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import express from "express";
|
||||
import { type Request, type Response } from "express";
|
||||
import { createServer } from "node:http";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import passport from "passport";
|
||||
import { Strategy as JwtStrategy, ExtractJwt } from "passport-jwt";
|
||||
import bodyParser from "body-parser";
|
||||
import { Server } from "socket.io";
|
||||
import jwt from "jsonwebtoken";
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface User {
|
||||
id: number;
|
||||
username: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const port = process.env.PORT || 3000;
|
||||
const jwtSecret = "Mys3cr3t";
|
||||
|
||||
const app = express();
|
||||
const httpServer = createServer(app);
|
||||
|
||||
app.use(bodyParser.json());
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
app.get("/", (req, res) => {
|
||||
res.sendFile(join(__dirname, "index.html"));
|
||||
});
|
||||
|
||||
app.get(
|
||||
"/self",
|
||||
passport.authenticate("jwt", { session: false }),
|
||||
(req, res) => {
|
||||
if (req.user) {
|
||||
res.send(req.user);
|
||||
} else {
|
||||
res.status(401).end();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
app.post("/login", (req, res) => {
|
||||
if (req.body.username === "john" && req.body.password === "changeit") {
|
||||
console.log("authentication OK");
|
||||
|
||||
const user = {
|
||||
id: 1,
|
||||
username: "john",
|
||||
};
|
||||
|
||||
const token = jwt.sign(
|
||||
{
|
||||
data: user,
|
||||
},
|
||||
jwtSecret,
|
||||
{
|
||||
issuer: "accounts.examplesoft.com",
|
||||
audience: "yoursite.net",
|
||||
expiresIn: "1h",
|
||||
},
|
||||
);
|
||||
|
||||
res.json({ token });
|
||||
} else {
|
||||
console.log("wrong credentials");
|
||||
res.status(401).end();
|
||||
}
|
||||
});
|
||||
|
||||
const jwtDecodeOptions = {
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
secretOrKey: jwtSecret,
|
||||
issuer: "accounts.examplesoft.com",
|
||||
audience: "yoursite.net",
|
||||
};
|
||||
|
||||
passport.use(
|
||||
new JwtStrategy(jwtDecodeOptions, (payload, done) => {
|
||||
return done(null, payload.data);
|
||||
}),
|
||||
);
|
||||
|
||||
const io = new Server(httpServer);
|
||||
|
||||
io.engine.use(
|
||||
(req: { _query: Record<string, string> }, res: Response, next: Function) => {
|
||||
const isHandshake = req._query.sid === undefined;
|
||||
if (isHandshake) {
|
||||
passport.authenticate("jwt", { session: false })(req, res, next);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
const req = socket.request as Request & { user: Express.User };
|
||||
|
||||
socket.join(`user:${req.user.id}`);
|
||||
|
||||
socket.on("whoami", (cb) => {
|
||||
cb(req.user.username);
|
||||
});
|
||||
});
|
||||
|
||||
httpServer.listen(port, () => {
|
||||
console.log(`application is running at: http://localhost:${port}`);
|
||||
});
|
||||
27
examples/passport-jwt-example/ts/package.json
Normal file
27
examples/passport-jwt-example/ts/package.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "passport-jwt-example",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Example with passport and JWT (https://www.passportjs.org/packages/passport-jwt/)",
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/jsonwebtoken": "^9.0.5",
|
||||
"@types/passport": "^1.0.16",
|
||||
"@types/passport-jwt": "^4.0.0",
|
||||
"body-parser": "^1.20.2",
|
||||
"express": "~4.17.3",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"passport": "^0.7.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"socket.io": "^4.7.2",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^3.1.1"
|
||||
}
|
||||
}
|
||||
11
examples/passport-jwt-example/ts/tsconfig.json
Normal file
11
examples/passport-jwt-example/ts/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"target": "ES2022",
|
||||
"strict": true
|
||||
},
|
||||
"ts-node": {
|
||||
"esm": true
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,8 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@fails-components/webtransport": "^0.1.7",
|
||||
"socket.io": "^4.7.1"
|
||||
"@fails-components/webtransport": "^1.0.8",
|
||||
"@fails-components/webtransport-transport-http3-quiche": "^1.0.8",
|
||||
"socket.io": "^4.7.4"
|
||||
}
|
||||
}
|
||||
|
||||
13
lib/index.ts
13
lib/index.ts
@@ -625,7 +625,6 @@ export class Server<
|
||||
switch (encoding) {
|
||||
case "br":
|
||||
res.writeHead(200, { "content-encoding": "br" });
|
||||
readStream.pipe(createBrotliCompress()).pipe(res);
|
||||
pipeline(readStream, createBrotliCompress(), res, onError);
|
||||
break;
|
||||
case "gzip":
|
||||
@@ -743,13 +742,17 @@ export class Server<
|
||||
* @param [fn] optional, called as `fn([err])` on error OR all conns closed
|
||||
*/
|
||||
public close(fn?: (err?: Error) => void): void {
|
||||
for (const socket of this.sockets.sockets.values()) {
|
||||
socket._onclose("server shutting down");
|
||||
}
|
||||
this._nsps.forEach((nsp) => {
|
||||
nsp.sockets.forEach((socket) => {
|
||||
socket._onclose("server shutting down");
|
||||
});
|
||||
|
||||
nsp.adapter.close();
|
||||
});
|
||||
|
||||
this.engine.close();
|
||||
|
||||
// restore the Adapter prototype
|
||||
// restore the Adapter prototype, when the Socket.IO server was attached to a uWebSockets.js server
|
||||
restoreAdapter();
|
||||
|
||||
if (this.httpServer) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
DefaultEventsMap,
|
||||
EventNamesWithoutAck,
|
||||
} from "./typed-events";
|
||||
import { Adapter } from "socket.io-adapter";
|
||||
import type { BroadcastOptions } from "socket.io-adapter";
|
||||
import debugModule from "debug";
|
||||
|
||||
@@ -33,7 +34,7 @@ export class ParentNamespace<
|
||||
SocketData = any
|
||||
> extends Namespace<ListenEvents, EmitEvents, ServerSideEvents, SocketData> {
|
||||
private static count: number = 0;
|
||||
private children: Set<
|
||||
private readonly children: Set<
|
||||
Namespace<ListenEvents, EmitEvents, ServerSideEvents, SocketData>
|
||||
> = new Set();
|
||||
|
||||
@@ -47,13 +48,7 @@ export class ParentNamespace<
|
||||
* @private
|
||||
*/
|
||||
_initAdapter(): void {
|
||||
const broadcast = (packet: any, opts: BroadcastOptions) => {
|
||||
this.children.forEach((nsp) => {
|
||||
nsp.adapter.broadcast(packet, opts);
|
||||
});
|
||||
};
|
||||
// @ts-ignore FIXME is there a way to declare an inner class in TypeScript?
|
||||
this.adapter = { broadcast };
|
||||
this.adapter = new ParentBroadcastAdapter(this, this.children);
|
||||
}
|
||||
|
||||
public emit<Ev extends EventNamesWithoutAck<EmitEvents>>(
|
||||
@@ -112,3 +107,19 @@ export class ParentNamespace<
|
||||
throw new Error("fetchSockets() is not supported on parent namespaces");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A dummy adapter that only supports broadcasting to child (concrete) namespaces.
|
||||
* @private file
|
||||
*/
|
||||
class ParentBroadcastAdapter extends Adapter {
|
||||
constructor(parentNsp: any, private readonly children: Set<Namespace>) {
|
||||
super(parentNsp);
|
||||
}
|
||||
|
||||
broadcast(packet: any, opts: BroadcastOptions) {
|
||||
this.children.forEach((nsp) => {
|
||||
nsp.adapter.broadcast(packet, opts);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
18
package-lock.json
generated
18
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "socket.io",
|
||||
"version": "4.7.3",
|
||||
"version": "4.7.5",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "socket.io",
|
||||
"version": "4.7.3",
|
||||
"version": "4.7.5",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.4",
|
||||
@@ -24,7 +24,7 @@
|
||||
"nyc": "^15.1.0",
|
||||
"prettier": "^2.3.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"socket.io-client": "4.7.4",
|
||||
"socket.io-client": "4.7.5",
|
||||
"socket.io-client-v2": "npm:socket.io-client@^2.4.0",
|
||||
"superagent": "^8.0.0",
|
||||
"supertest": "^6.1.6",
|
||||
@@ -3478,9 +3478,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-client": {
|
||||
"version": "4.7.4",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.4.tgz",
|
||||
"integrity": "sha512-wh+OkeF0rAVCrABWQBaEjLfb7DVPotMbu0cgWgyR0v6eA4EoVnAwcIeIbcdTE3GT/H3kbdLl7OoH2+asoDRIIg==",
|
||||
"version": "4.7.5",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz",
|
||||
"integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
@@ -6942,9 +6942,9 @@
|
||||
}
|
||||
},
|
||||
"socket.io-client": {
|
||||
"version": "4.7.4",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.4.tgz",
|
||||
"integrity": "sha512-wh+OkeF0rAVCrABWQBaEjLfb7DVPotMbu0cgWgyR0v6eA4EoVnAwcIeIbcdTE3GT/H3kbdLl7OoH2+asoDRIIg==",
|
||||
"version": "4.7.5",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz",
|
||||
"integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "socket.io",
|
||||
"version": "4.7.4",
|
||||
"version": "4.7.5",
|
||||
"description": "node.js realtime framework server",
|
||||
"keywords": [
|
||||
"realtime",
|
||||
@@ -61,7 +61,7 @@
|
||||
"nyc": "^15.1.0",
|
||||
"prettier": "^2.3.2",
|
||||
"rimraf": "^3.0.2",
|
||||
"socket.io-client": "4.7.4",
|
||||
"socket.io-client": "4.7.5",
|
||||
"socket.io-client-v2": "npm:socket.io-client@^2.4.0",
|
||||
"superagent": "^8.0.0",
|
||||
"supertest": "^6.1.6",
|
||||
|
||||
@@ -221,11 +221,11 @@ describe("connection state recovery", () => {
|
||||
const httpServer = createServer().listen(0);
|
||||
|
||||
class DummyAdapter extends Adapter {
|
||||
override persistSession(session) {
|
||||
persistSession(session) {
|
||||
expect().fail();
|
||||
}
|
||||
|
||||
override restoreSession(pid, offset) {
|
||||
restoreSession(pid, offset) {
|
||||
expect().fail();
|
||||
return Promise.reject("should not happen");
|
||||
}
|
||||
|
||||
@@ -70,6 +70,21 @@ describe("server attachment", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should serve client (br)", (done) => {
|
||||
const srv = createServer();
|
||||
new Server(srv);
|
||||
request(srv)
|
||||
.get("/socket.io/socket.io.js")
|
||||
.set("accept-encoding", "br")
|
||||
.buffer(true)
|
||||
.end((err, res) => {
|
||||
if (err) return done(err);
|
||||
expect(res.headers["content-encoding"]).to.be("br");
|
||||
expect(res.status).to.be(200);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it("should serve client with necessary CORS headers", (done) => {
|
||||
const srv = createServer();
|
||||
new Server(srv, {
|
||||
|
||||
Reference in New Issue
Block a user