File
Description
Class of websocket redis adapter for Nest app. Verify jwt token on connection, and decode token data for socket
Extends
Private
Readonly
logger
|
Default value : new Logger(RedisIoAdapter.name)
|
|
|
Methods
createIOServer
|
createIOServer(port: number, options: object)
|
|
Create server method
@param port {number} - server port
@param options {Object} - options object
@return {Server} io server
Parameters :
Name |
Type |
Optional |
Default value |
Description |
port |
number
|
No
|
|
|
options |
object
|
No
|
{}
|
|
|
import { IoAdapter } from '@nestjs/platform-socket.io';
import { createAdapter } from '@socket.io/redis-adapter';
import { Logger } from '@nestjs/common';
import { AbstractGameClient } from '../interfaces/game';
import { verify as JwtVerify } from 'jsonwebtoken';
import Redis from 'ioredis';
const { WEBSOCKET_NO_REDIS } = process.env;
let redisAdapter = null;
if (WEBSOCKET_NO_REDIS !== 'yes') {
const pubClient = new Redis({
host: process.env.WEBSOCKET_REDIS_HOST,
port: parseInt(process.env.WEBSOCKET_REDIS_PORT, 10),
db: parseInt(process.env.WEBSOCKET_REDIS_DB, 10) || 0,
});
const subClient = pubClient.duplicate();
redisAdapter = createAdapter(pubClient, subClient);
pubClient.on('error', () => {
});
subClient.on('error', () => {
});
}
export class RedisIoAdapter extends IoAdapter {
private readonly logger = new Logger(RedisIoAdapter.name);
createIOServer(port: number, options = {}): any {
this.logger.log(
`new redis websocket, <${JSON.stringify(options || null)}>`,
);
const whitelistOrigins = process.env.ALLOWED_CORS_ORIGIN;
const authSecret = process.env.AUTH_PRIVATE_KEY;
const server = super.createIOServer(port, {
pingInterval: parseInt(process.env.WEBSOCKET_PING_INTERVAL, 10) || 1000,
pingTimeout: parseInt(process.env.WEBSOCKET_PING_TIMEOUT, 10) || 1000,
transports: ['websocket'],
allowUpgrades: false,
allowRequest: (req, callback) => {
const { origin } = req.headers;
const { jwt } = req._query;
if (!jwt || jwt === 'null') return callback(null, false);
if (whitelistOrigins.indexOf(origin) === -1)
return callback(null, false);
if (jwt === 'healthcheck') {
return callback(null, true);
}
try {
const user = JwtVerify(jwt, authSecret);
if (!user) return callback('401', false);
} catch (err) {
this.logger.error(err, `Socket io connection jwt error`);
return callback('401', false);
}
return callback(null, true);
},
...options,
});
if (redisAdapter) {
server.adapter(redisAdapter);
server.of('/').adapter.on('error', (err) => {
this.logger.error(err, 'Websockets redis error');
});
server.of('/live').adapter.on('error', (err) => {
this.logger.error(err, 'Live websockets redis error');
});
}
server.of('/live').use((socket, next) => {
const { jwt, clientTime } = socket.handshake.query;
if (!jwt) return next(401);
if (jwt === 'healthcheck') {
const hc: AbstractGameClient = {
clientId: 'health-check',
gameCode: 'health-check',
region: process.env.MATH99_REGION || 'core',
isHost: false,
};
socket.data.user = hc;
next();
return;
}
const user: AbstractGameClient = JwtVerify(
jwt,
authSecret,
) as AbstractGameClient;
if (!user) return next(401);
socket.data = { user };
socket.data.user.gameCode = `${user.gameCode}`;
socket.data.user.clientTime = clientTime;
next();
});
return server;
}
}