UNPKG

conextra

Version:

Conextra for Web Development. Helps create a SPWA (Single-page Progressive Web Application).

163 lines (116 loc) 5.49 kB
'use script'; var { Worker } = require('node:worker_threads'), http = require("node:http"), https = require("node:https"), fs = require('node:fs'), path = require('node:path'), crypto = require('node:crypto'), configSets = require('config-sets'), options_server = configSets('tiny-https-server'), conextraConfig = configSets('conextra', { isDebug: false, pathToResourseMap: 'data/resourse_map.json', pathToDataDir: "data" }), websocketWorkerMap = {}, isTLS = options_server.port !== 80 && options_server.pathToPrivkey, port = (options_server.port || 80) + 1, host = options_server.host || '0.0.0.0', server = (isTLS ? https : http) .createServer(function (req, res) { res.writeHead(403, { 'Content-Type': 'text/plain' }); res.end('403 Forbidden\n'); }) .on('upgrade', upgradeSocket) .on('clientError', (err, socket) => { if (err.code === 'ECONNRESET' || !socket.writable) { return; } socket.end('HTTP/1.1 400 Bad Request\r\n\r\n'); }) .on('error', function (err) { pError('Server error:', err); }) .listen({ port, host, exclusive: false }, function () { pDebug('WebSocket-Main server started on port', port); }); if (isTLS && options_server.pathToCert) { server.setSecureContext({ key: fs.readFileSync(options_server.pathToPrivkey), cert: fs.readFileSync(options_server.pathToCert) }); var certUpdateWaitTimeout; fs.watch(path.dirname(options_server.pathToCert), function (eventType, filename) { if (!fs.existsSync(options_server.pathToPrivkey) || !fs.existsSync(options_server.pathToCert)) { return; } clearTimeout(certUpdateWaitTimeout); certUpdateWaitTimeout = setTimeout(function () { server.setSecureContext({ key: fs.readFileSync(options_server.pathToPrivkey), cert: fs.readFileSync(options_server.pathToCert) }); }, 1000); }); } function upgradeSocket(request, socket, head) { var protocol = request.headers['sec-websocket-protocol'].split(',')[0].trim(), url = request.url, ident = protocol + request.url, //Default port range for WebSocket is 49152-65535 port = 49152; if (!websocketWorkerMap[ident]) { while (Object.keys(websocketWorkerMap).find(function (k) { return websocketWorkerMap[k].port === port; })) { port++; } websocketWorkerMap[ident] = { port, worker: new Worker(path.join(__dirname, './websocket_worker.js'), { workerData: { isTLS, host: options_server.host, port, url, protocol } }) }; pDebug('WebSocket-Main server create new WebSocket-Worker:', ident, port); pDebug('WebSocket-Worker.count:', Object.keys(websocketWorkerMap).length); websocketWorkerMap[ident].worker.on('error', pError); websocketWorkerMap[ident].worker.on('exit', () => { delete websocketWorkerMap[ident]; pDebug('WebSocket-Worker.count:', Object.keys(websocketWorkerMap).length); }); } setTimeout(sendRedirect, 200, socket, request.headers['sec-websocket-key'] || '', websocketWorkerMap[ident].port, protocol); function sendRedirect(socket, wstKey, redirectPort, protocol) { var headers = [ 'HTTP/1.1 101 Switching Protocols', 'Upgrade: websocket', 'Connection: Upgrade', 'Sec-WebSocket-Accept: ' + crypto.createHash('sha1').update(wstKey + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11').digest('base64'), 'Sec-WebSocket-Protocol: ' + protocol, '\r\n' ].join('\r\n') if (!['open', 'writeOnly'].includes(socket.readyState)) { return; } socket.write(headers, 'utf8', function () { if (!['open', 'writeOnly'].includes(socket.readyState)) { return; } writeRedirectFrame(function () { // Send redirect frame if (!['open', 'writeOnly'].includes(socket.readyState)) { return; } writeCloseFrame(function () { // Send close frame //socket.end(); // Close the connection pDebug('WebSocket-Main server redirect_to_port:', redirectPort); }); }); }); function writeRedirectFrame(cb) { if (socket.readyState !== 'open') { return; } socket.write(Buffer.from([129, 23]), 'utf8', function () { if (socket.readyState !== 'open') { return; } socket.write('$redirect_to_port:' + redirectPort, 'utf8', cb); }); } function writeCloseFrame(cb) { if (socket.readyState !== 'open') { return; } socket.write(Buffer.from([136, 0]), 'utf8', cb); } } } // Debugging function pDebug(...args) { if (conextraConfig.isDebug) { console.log(`[ DEBUG ] 'ws-user-link' `, ...args); } } function pError(...args) { if (conextraConfig.isDebug) { console.error(`[ ERROR ] 'ws-user-link' `, ...args); } }