import yargs from 'yargs/yargs' import { hideBin } from 'yargs/helpers' import mysql from 'mysql2/promise' import { WebSocketServer } from 'ws' import fs from 'fs' import {RedisConnexion} from './redisConnexion.js' import {wssServer} from './wssServer.js' import {configHelper} from './configHelper.js' import express from 'express' import session from 'express-session' import connectMySQL from 'express-mysql-session' const argv = yargs(hideBin(process.argv)).command('wssGateway', 'Redis <=> Websocket message bus gateway', {}) .options({ 'argv.debug': { description: 'shows debug info', alias: 'd', type: 'boolean' }, }).help().version('1.1').argv const debug = Boolean(process.env.DEBUG) || argv.debug const MySQLStore = connectMySQL(session) const app = express(); app.set('trust proxy', 1) // trust first proxy (nginx), so we serve http to nginx, but we still behave as if we're in https app.use(express.json()) const mysqlCreds = { // host: '127.0.0.1', // port: 3306, socketPath: '/var/run/mysqld/mysqld.sock', user: 'p42', password: 'C3h=V9!r>Mvc>skxPf9?W2P3duJTk', database: 'p42', waitForConnections: true, connectionLimit: 10, queueLimit: 0 } const db = await mysql.createConnection(mysqlCreds) const sessionStore = new MySQLStore({ createDatabaseTable: false, clearExpired: true, schema: { tableName: 'p42_sessions', columnNames: { session_id: 'session_id', expires: 'expires', data: 'data' } } }, db) const sessionParser = session({ name: 'p42.api.sid', secret: 'qNhy555Y9vyxj?!3yaYA=aKfgk+Wy5eymNtP*?4i', store: sessionStore, resave: false, saveUninitialized: false, cookie: { maxAge: 1000 * 60 * 60 * 4, secure: true, //See trust proxy above sameSite: 'lax', }, } ) app.use(sessionParser) const cfgh = new configHelper({ localfile: './wssGatewayConfig.json', }) async function startAllRedis(wssGatewayConfig) { if (debug) console.log('Starting all Redis instances...') //1. instantiate all & login all const redisConns = wssGatewayConfig.redis.map(cfg => new RedisConnexion({ debug, config: cfg, redisId:cfg.redisId }) ) const loginResults = await Promise.allSettled( redisConns.map(async cnx => { cnx.redisLogin() return cnx.redisId }) ) 2. //make sure all connected before going any further const failedLogin = loginResults.filter(r => r.status !== 'fulfilled') if (failedLogin.length > 0) { console.error('Redis login failures:') failedLogin.forEach((r, i) => { const id = redisConns[i].redisId console.error(`chansStart failed for redis:[${id}] → ${r.reason}`) }) throw new Error( `Redis login failed for ${failedLogin.length}/${redisConns.length} instances` ) } if (debug) console.log('All Redis logins OK') // --- Phase 2: start channels for all (since all succeeded) const chanResults = await Promise.allSettled( redisConns.map(async cnx => { cnx.redisChansStart() return cnx.redisId }) ) const failedChans = chanResults.filter(r => r.status !== 'fulfilled') if (failedChans.length > 0) { console.error('Redis chansStart failures:') failedChans.forEach((r, i) => { const id = redisConns[i].redisId console.error(`chansStart failed for redis:[${id}] → ${r.reason}`) }) throw new Error( `Redis chansStart failed for ${failedChans.length}/${redisConns.length} instances` ) } if (debug) console.log('All Redis chansStart OK') return redisConns } cfgh.fetchConfig().then( async wssGatewayConfig => { if((!wssGatewayConfig) || (Object.keys(wssGatewayConfig).length<4)) { console.error('Cannot get a valid configuration ! Aaarrghhh...') process.exit() } let httpLib if(wssGatewayConfig.server.unsecure) httpLib = await import('http') else httpLib = await import('https') /////////////////////// Create & Start servers \\\\\\\\\\\\\\\\\\\\\\ console.log(`Debug mode : ${debug ? 'ON' : 'OFF'}`) let options if(!wssGatewayConfig.server.unsecure) { options = { key: fs.readFileSync(wssGatewayConfig.server.certKeyFile), cert: fs.readFileSync(wssGatewayConfig.server.certFile), }; } else options = {} const HTTPserver = httpLib.createServer(app) const wssServerOptions = { // server: HTTPserver, // path: wssGatewayConfig.server.listenPath, noServer: true } if(!wssGatewayConfig.server.unsecure) { wssServerOptions['key'] = fs.readFileSync(wssGatewayConfig.server.certKeyFile) wssServerOptions['cert'] = fs.readFileSync(wssGatewayConfig.server.certFile) } const WSSServer = new WebSocketServer(wssServerOptions); HTTPserver.on('upgrade', (req, socket, head) => { sessionParser(req, {}, () => { if (!req.session) { console.warn('No session, bye bye!') socket.destroy() return } WSSServer.handleUpgrade(req, socket, head, (ws) => { ws.session = req.session // direct access to Express session WSSServer.emit('connection', ws, req) }) }) }) // WSSServer.on('connection', (ws, req) => { // console.log('WS connected with session:', ws.session, ws.session.authenticated, ws.session.userInfos) // // modify session if you want // ws.session.views = (ws.session.views || 0) + 1 // ws.session.save() // ws.send(`You visited ${ws.session.views} times`) // }) HTTPserver.listen(wssGatewayConfig.server.listenPort, () => { console.log(`WS${wssGatewayConfig.server.unsecure ? '': 'S'} server created for ${wssGatewayConfig.server.listenHost}:${wssGatewayConfig.server.listenPort}`) }) startAllRedis(wssGatewayConfig).then((allRediscnx) => { const wssSrv = new wssServer(cfgh, WSSServer, allRediscnx, debug); }); })