-
-
Notifications
You must be signed in to change notification settings - Fork 3k
Expand file tree
/
Copy pathsocketio.ts
More file actions
153 lines (132 loc) · 6.4 KB
/
Copy pathsocketio.ts
File metadata and controls
153 lines (132 loc) · 6.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
'use strict';
import {ArgsExpressType} from "../../types/ArgsExpressType";
import events from 'events';
const express = require('../express');
import log4js from 'log4js';
const proxyaddr = require('proxy-addr');
import settings from '../../utils/Settings';
import {Server, Socket} from 'socket.io'
const socketIORouter = require('../../handler/SocketIORouter');
const hooks = require('../../../static/js/pluginfw/hooks');
const padMessageHandler = require('../../handler/PadMessageHandler');
let io:any;
const logger = log4js.getLogger('socket.io');
/** Returns the socket.io Server once expressCreateServer has run, or null otherwise. Used by features that need to broadcast outside the regular hook surface. */
export const getIo = (): any => io;
const sockets = new Set();
const socketsEvents = new events.EventEmitter();
export const expressCloseServer = async () => {
if (io == null) return;
logger.info('Closing socket.io engine...');
// Close the socket.io engine to disconnect existing clients and reject new clients. Don't call
// io.close() because that closes the underlying HTTP server, which is already done elsewhere.
// (Closing an HTTP server twice throws an exception.) The `engine` property of socket.io Server
// objects is undocumented, but I don't see any other way to shut down socket.io without also
// closing the HTTP server.
io.engine.close();
// Closing the socket.io engine should disconnect all clients but it is not documented. Wait for
// all of the connections to close to make sure, and log the progress so that we can troubleshoot
// if socket.io's behavior ever changes.
//
// Note: `io.sockets.clients()` should not be used here to track the remaining clients.
// `io.sockets.clients()` works with socket.io 2.x, but not with 3.x: With socket.io 2.x all
// clients are always added to the default namespace (`io.sockets`) even if they specified a
// different namespace upon connection, but with socket.io 3.x clients are NOT added to the
// default namespace if they have specified a different namespace. With socket.io 3.x there does
// not appear to be a way to get all clients across all namespaces without tracking them
// ourselves, so that is what we do.
let lastLogged = 0;
while (sockets.size > 0 && !settings.enableAdminUITests) {
if (Date.now() - lastLogged > 1000) { // Rate limit to avoid filling logs.
logger.info(`Waiting for ${sockets.size} socket.io clients to disconnect...`);
lastLogged = Date.now();
}
await events.once(socketsEvents, 'updated');
}
logger.info('All socket.io clients have disconnected');
};
const socketSessionMiddleware = (args: any) => (socket: any, next: Function) => {
const req = socket.request;
// Express sets req.ip but socket.io does not. Replicate Express's behavior here.
if (req.ip == null) {
if (settings.trustProxy) {
req.ip = proxyaddr(req, args.app.get('trust proxy fn'));
} else {
req.ip = socket.handshake.address;
}
}
if (!req.headers.cookie) {
// socketio.js-client on node.js doesn't support cookies, so pass them via a query parameter.
req.headers.cookie = socket.handshake.query.cookie;
}
express.sessionMiddleware(req, {}, next);
};
export const expressCreateServer = (hookName:string, args:ArgsExpressType, cb:Function) => {
// Engine.io socket flush deferral (#7756 / #7767). Apply BEFORE building
// the socket.io Server so the patched Socket prototype is in effect when
// the Server creates its engine.
if (settings.engineFlushDefer === true) {
// eslint-disable-next-line @typescript-eslint/no-require-imports
require('../../utils/EngineFlushDeferral').installEngineFlushDeferral();
}
// init socket.io and redirect all requests to the MessageHandler
// there shouldn't be a browser that isn't compatible to all
// transports in this list at once
// e.g. XHR is disabled in IE by default, so in IE it should use jsonp-polling
io = new Server(args.server,{
transports: settings.socketTransportProtocols,
cookie: false,
maxHttpBufferSize: settings.socketIo.maxHttpBufferSize,
})
const handleConnection = (socket:Socket) => {
sockets.add(socket);
socketsEvents.emit('updated');
// https://socket.io/docs/v3/faq/index.html
// @ts-ignore
const session = socket.request.session;
session.connections++;
session.save();
socket.on('disconnect', () => {
sockets.delete(socket);
socketsEvents.emit('updated');
});
}
const renewSession = (socket:any, next:Function) => {
socket.conn.on('packet', (packet:string) => {
// Tell express-session that the session is still active. The session store can use these
// touch events to defer automatic session cleanup, and if express-session is configured with
// rolling=true the cookie's expiration time will be renewed. (Note that WebSockets does not
// have a standard mechanism for periodically updating the browser's cookies, so the browser
// will not see the new cookie expiration time unless it makes a new HTTP request or the new
// cookie value is sent to the client in a custom socket.io message.)
if (socket.request.session != null) socket.request.session.touch();
});
next();
}
io.on('connection', handleConnection);
io.use(socketSessionMiddleware(args));
// Temporary workaround so all clients go through middleware and handle connection
io.of('/pluginfw/installer')
.on('connection',handleConnection)
.use(socketSessionMiddleware(args))
.use(renewSession)
io.of('/settings')
.on('connection',handleConnection)
.use(socketSessionMiddleware(args))
.use(renewSession)
io.use(renewSession);
// var socketIOLogger = log4js.getLogger("socket.io");
// Debug logging now has to be set at an environment level, this is stupid.
// https://github.com/Automattic/socket.io/wiki/Migrating-to-1.0
// This debug logging environment is set in Settings.js
// minify socket.io javascript
// Due to a shitty decision by the SocketIO team minification is
// no longer available, details available at:
// http://stackoverflow.com/questions/23981741/minify-socket-io-socket-io-js-with-1-0
// if(settings.minify) io.enable('browser client minification');
// Initialize the Socket.IO Router
socketIORouter.setSocketIO(io);
socketIORouter.addComponent('pad', padMessageHandler);
hooks.callAll('socketio', {app: args.app, io, server: args.server});
return cb();
};