const fs = require('fs-extra'); const { basename, extname } = require('path'); const debug = require('debug')('server-connect:sockets'); const { isEmpty } = require('./util'); const config = require('./config'); const cookieParser = require('cookie-parser'); const { promisify } = require('util'); module.exports = function (server, appSession) { //if (isEmpty('app/sockets')) return null; const io = require('socket.io')(); if (global.redisClient) { const { createAdapter } = require('@socket.io/redis-streams-adapter'); io.adapter(createAdapter(global.redisClient)); } // user hooks if (fs.existsSync('extensions/server_connect/sockets')) { const entries = fs.readdirSync('extensions/server_connect/sockets', { withFileTypes: true }); for (let entry of entries) { if (entry.isFile() && extname(entry.name) == '.js') { const hook = require(`../../extensions/server_connect/sockets/${entry.name}`); if (hook.handler) hook.handler(io); debug(`Custom sockets hook ${entry.name} loaded`); } } } // create socket connections for api endpoints if (fs.existsSync('app/api')) { io.of('/api').on('connection', async (socket) => { socket.onAny(async (event, params, cb) => { try { if (typeof cb == 'function' && global.redisClient && global.redisClient.isReady) { const cached = await global.redisClient.get('ws:' + event + ':' + JSON.stringify(params)); if (cached) return cb(JSON.parse(cached)); } const req = Object.assign({}, socket.handshake); const res = { statusCode: 200, getHeader: () => { }, setHeader: () => { }, sendStatus: (statusCode) => { res.statusCode = statusCode; }, write: () => { }, end: () => { } }; cookieParser(config.secret)(req, res, () => { appSession(req, res, async () => { const App = require('../core/app'); const app = new App(req, res); const action = await fs.readJSON(`app/api/${event}.json`); app.set('$_PARAM', params); app.set('$_GET', params); // fake query params app.socket = socket; await app.define(action, true); if (typeof cb == 'function') { cb({ status: res.statusCode, data: res.statusCode == 200 ? app.data : null }); if (global.redisClient && global.redisClient.isReady) { let ttl = (action.settings && action.settings.options && action.settings.options.ttl) ? action.settings.options.ttl : null; if (ttl && res.statusCode < 400) { // only cache valid response, not error response global.redisClient.setEx('ws:' + event + ':' + JSON.stringify(params), ttl, JSON.stringify({ status: res.statusCode, data: res.statusCode == 200 ? app.data : null })); } } } }); }); } catch (e) { debug(`ERROR: ${e.message}`); console.error(e); } }); }); } if (fs.existsSync('app/sockets')) { parseSockets(); } function parseSockets(namespace = '') { const entries = fs.readdirSync('app/sockets' + namespace, { withFileTypes: true }); io.of(namespace || '/').on('connection', async (socket) => { if (fs.existsSync(`app/sockets${namespace}/connect.json`)) { try { const req = Object.assign({}, socket.handshake); const res = { statusCode: 200, getHeader: () => { }, setHeader: () => { }, sendStatus: (statusCode) => { res.statusCode = statusCode; }, write: () => { }, end: () => { } }; cookieParser(config.secret)(req, res, () => { appSession(req, res, async () => { const App = require('../core/app'); const app = new App(req, res); const action = await fs.readJSON(`app/sockets${namespace}/connect.json`); app.socket = socket; await app.define(action, true); }); }); } catch (e) { debug(`ERROR: ${e.message}`); console.error(e); } } if (fs.existsSync(`app/sockets${namespace}/disconnect.json`)) { socket.on('disconnect', async (event) => { try { const req = Object.assign({}, socket.handshake); const res = { statusCode: 200, getHeader: () => { }, setHeader: () => { }, sendStatus: (statusCode) => { res.statusCode = statusCode; }, write: () => { }, end: () => { } }; cookieParser(config.secret)(req, res, () => { appSession(req, res, async () => { const App = require('../core/app'); const app = new App(req, res); const action = await fs.readJSON(`app/sockets${namespace}/disconnect.json`); app.socket = socket; await app.define(action, true); }); }); } catch (e) { debug(`ERROR: ${e.message}`); console.error(e); } }); } socket.onAny(async (event, params, cb) => { try { const req = Object.assign({}, socket.handshake); const res = { statusCode: 200, getHeader: () => { }, setHeader: () => { }, sendStatus: (statusCode) => { res.statusCode = statusCode; }, write: () => { }, end: () => { } }; cookieParser(config.secret)(req, res, () => { appSession(req, res, async () => { const App = require('../core/app'); const app = new App(req, res); const action = await fs.readJSON(`app/sockets${namespace}/${event}.json`); app.set('$_PARAM', params); app.socket = socket; await app.define(action, true); if (typeof cb == 'function') cb(app.data); }); }); } catch (e) { debug(`ERROR: ${e.message}`); console.error(e); } }); }); for (let entry of entries) { if (entry.isDirectory()) { parseSockets(namespace + '/' + entry.name); } } } io.attach(server, { cors: config.cors }); return io; };