Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 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 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x | 'use strict';
const ws = require('ws');
const Router = require('@hapi/call').Router;
const EventEmitter = require('events');
const helpers = require('./helpers');
const INTERPOLATION_REGEX = /\{([^}]*)\}/g;
async function sendMessage(socket, message) {
Eif (socket.readyState === ws.OPEN) {
// socket.auth.exp must be EPOCH in seconds but not milliseconds
Iif (typeof socket.auth.exp === 'number' && socket.auth.exp * 1000 < Date.now()) {
return socket.close(4403, 'Session expired!');
}
return socket.send(message);
}
}
class SocketServer extends EventEmitter {
constructor(utHttpServer = {}, config = {}) {
super();
this.router = new Router();
this.rooms = {};
this.wss = null;
this.utHttpServer = utHttpServer;
this.utHttpServerConfig = config;
this.disableXsrf = (config.disableXsrf && config.disableXsrf.ws);
this.disablePermissionVerify = (config.disablePermissionVerify && config.disablePermissionVerify.ws);
}
start(httpServerListener) {
this.wss = new ws.Server({
server: httpServerListener
});
this.wss.on('connection', (socket, req) => {
const url = req.url.split('?').shift();
try {
// socket.upgradeReq not supported anymore:
// https://github.com/websockets/ws/pull/1099
// persist only fingerprint (for verification)
socket.fingerprint = this.router.analyze(url).fingerprint;
} catch (e) {
return socket.close(4404, 'Wrong url:' + req.url);
}
Promise.resolve()
.then(() => {
Iif (this.disableXsrf) return {};
const params = {
query: helpers.getTokens([req.url.replace(/[^?]+\?/ig, '')], ['&', '=']),
hashKey: this.utHttpServerConfig.jwt.key,
verifyOptions: {
...this.utHttpServerConfig.jwt.verifyOptions,
ignoreExpiration: false
}
};
Eif (req.headers.authorization) {
const [scheme, token] = req.headers.authorization.split(' ');
Eif (scheme === 'Bearer') params.cookie = token;
} else if (req.headers.cookie) {
params.cookie = helpers.getTokens([req.headers.cookie], [';', '='])[this.utHttpServerConfig.jwt.cookieKey];
}
return helpers.jwtXsrfCheck(params);
})
.then(auth => {
socket.auth = auth;
const context = this.router.route(req.method.toLowerCase(), url);
Iif (context.isBoom) throw context;
Eif (!this.disablePermissionVerify) helpers.permissionVerify(socket, this.utHttpServerConfig.appId);
return context.route.verifyClient(socket);
})
.then(() => {
return this.router
.route(req.method.toLowerCase(), url).route
.handler(socket.fingerprint, socket);
})
.then(() => (this.emit('connection')))
.catch((err) => {
this.utHttpServer.log && this.utHttpServer.log.error && this.utHttpServer.log.error(err);
if (!err.isBoom) return socket.close(4500, '4500');
socket.close(
4000 + parseInt(err.output.payload.statusCode), // based on https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes
(4000 + parseInt(err.output.payload.statusCode)).toString() // Send status code as reason because Firefox/Edge show 1005 only as code
);
});
});
}
registerPath(path, verifyClient) {
this.router.add({
method: 'get',
path
}, {
handler: (roomId, socket) => {
Eif (!this.rooms[roomId]) this.rooms[roomId] = new Set();
this.rooms[roomId].add(socket);
socket.on('close', () => this.rooms[roomId].delete(socket));
},
verifyClient: socket => {
return typeof verifyClient === 'function' ? verifyClient(socket, socket.fingerprint) : false;
}
});
}
publish({path = '', params = {}}, message) {
const room = this.rooms[path.replace(INTERPOLATION_REGEX, (placeholder, label) => (params[label] || placeholder))];
Eif (room && room.size) {
const formattedMessage = helpers.formatMessage(message);
room.forEach(socket => sendMessage(socket, formattedMessage));
}
}
broadcast(message) {
const formattedMessage = helpers.formatMessage(message);
this.wss.clients.forEach(socket => sendMessage(socket, formattedMessage));
}
stop() {
this.wss && this.wss.close();
}
}
module.exports = SocketServer;
|