UNPKG

albertgao.common-electron

Version:

个人常用的nodejs/electron模块

388 lines (382 loc) 12.8 kB
// import AutoEncryptLocalhost from '@small-tech/auto-encrypt-localhost' /** * 获取本地Ip地址 * * @returns {string[]} 地址列表 */ function getIps() { const os = require('os') const _net = Object.values(os.networkInterfaces()) let temp = [] _net.forEach((ele1) => { ele1.forEach((ele2) => { if (ele2.family !== undefined && ele2.family.toUpperCase() === 'IPV4') { temp.push(ele2) } }) }) temp = [...new Set(['localhost', ...temp.map(({ address }) => address)])] return temp } /** * @param {string}url 要检查的url * @returns {boolean} 该url是否基于本地ip */ function isLocalhost(url) { const ips = getIps() for (let ip of ips) { ip = ip.replace(/\./g, '\\.') const reg = new RegExp(`^https?://${ip}`, 'i') if (reg.test(url)) { return true } } return false } /** * 获取被占用额端口号 * * @returns {Promise<number[]>} 返回端口数字合集 */ function getOccupiedPorts() { const os = require('os') const { exec } = require('child_process') const command = /windows/i.test(os.type()) ? 'netstat -ano' : 'netstat -tunlp' return new Promise((resolve, reject) => { // eslint-disable-next-line node/handle-callback-err exec(command, (err, stdout) => { const regs0 = /\s+\d+(\.\d+){3}:(?<port>\d+)\s+/g const regs1 = /\s+(\[::\]|::):(?<port>\d+)\s+/g const arr0 = [ ...(`${stdout}`.match(regs0) || []), ...(`${stdout}`.match(regs1) || []) ] .map((e) => { const groups = [...e.split(':')] return groups.pop() }) .map((e) => +e) const result = [...new Set(arr0)].filter((e) => e) resolve(result) }) }) } /** * @param {number}port 想要使用的端口 * @returns {number}由于占用实际使用的端口 */ async function getUnOccupiedPort(port) { port = +port const occupied = await getOccupiedPorts() while (occupied.includes(port)) { port++ } return port } /** * @param {object}root0 选项 * @param {string}root0.atDir 工作目录,当计算出cert之后,会删除整个目录 * @returns {Promise<object>} 返回mkcrt的tls证书 */ function createLocalCert({ atDir = require('path').join(process.cwd(), 'https-crt') }) { const os = require('os') const { exec } = require('child_process') const Path = require('path') const klawSync = require('klaw-sync') const Fs = require('fs-extra') const ipAddresses = getIps().join(' ') let newMkcerExePath = Path.join(atDir, 'mkcert32.exe') const originMkcerExePath = Path.join(__dirname, 'mkcert32.exe') const isAtOriginalDir = newMkcerExePath === originMkcerExePath newMkcerExePath = newMkcerExePath.replace( /mkcert32.exe$/, `mkcert32${~~(Math.random() * 9999999)}.exe` ) console.log('original mkcert @ ', originMkcerExePath.replace(/.exe$/, '')) if (!isAtOriginalDir) { Fs.removeSync(atDir) Fs.ensureDirSync(atDir) Fs.copySync(originMkcerExePath, newMkcerExePath, { overwrite: true }) Fs.copySync( originMkcerExePath.replace(/\.exe$/, ''), newMkcerExePath.replace(/\.exe$/, ''), { overwrite: true } ) } // eslint-disable-next-line no-async-promise-executor return new Promise(async (resolve, reject) => { const isWin = /windows/i.test(os.type()) let cmd0 = `${newMkcerExePath.replace(/\.exe$/, '')} -install` if (!isWin) { cmd0 = [ `sudo -s`, `apt update`, `apt upgrade`, `apt install libnss3-tools`, `${newMkcerExePath.replace(/\.exe$/, '')} -install` ].join(' && ') } exec(cmd0, { cwd: atDir }, () => { exec( `${newMkcerExePath} ${ipAddresses}`, { cwd: atDir }, // eslint-disable-next-line node/handle-callback-err (err, stdout) => { console.log(stdout) const httpsOptions = {} const files = [...klawSync(atDir, { nodir: true })] .map(({ path }) => path) .filter((path) => { return /\.pem/i.test(Path.extname(path)) }) files.forEach((e) => { if (/key\.pem/i.test(e)) { httpsOptions.key = Fs.readFileSync(e) } else { httpsOptions.cert = Fs.readFileSync(e) } }) if (!isAtOriginalDir) { Fs.removeSync(atDir) } resolve(httpsOptions) } ) }) }) } /** * @param {object}root0 - 选项 * @param {Function[]}[root0.creations=[]] - ({app, wss, broadcast, httpServer})= > { ...todo } * @param {Function[]}[root0.setup=[]] - ({app, wss, broadcast, httpServer})= > { ...todo } * @param {number}[root0.httpPort=3000] - http服务器端口 * @param {number}[root0.httpsPort=3001] - https服务器端口 * @param {object}root0.httpsOptions https的key和cert // * @param {Buffer}root0.httpsOptions.key https-key 默认用localhost // * @param {Buffer}root0.httpsOptions.cert https-cert 默认用localhost * @param {number}[root0.port=8080] 代理的端口() * @param {boolean}root0.CORS 是否跨域 * @param {boolean}root0.changePortWhenOccupied 是否在端口被占用的时候主动更改端口 * @returns {Promise<object>}返回{app, httpServer, wss, wsss, broadcast(msg)=>会将msg转为JSON发送给每个client, wsBoth: [wss, wsss]} */ async function expressWssServer({ creations = [], setup = [], httpPort = 3000, httpsPort = 3001, httpsOptions, port = 8080, CORS = true, changePortWhenOccupied = true }) { const WebSocket = require('ws') const { Server: CreateWSServer } = WebSocket const Http = require('http') const Https = require('https') const Express = require('express') const net = require('net') const httpsOptionsActual = httpsOptions || (await createLocalCert({})) const serverInitialization = { addExpress: function ({ app = Express() }) { app.use(Express.json({ limit: '100mb' })) app.use(Express.urlencoded({ extended: true, limit: '100mb' })) Object.assign(arguments[0], { app }) }, addHttpServer: function ({ app = Express(), httpServer = Http.createServer(app) }) { Object.assign(arguments[0], { app, httpServer }) }, addWss: function ({ app = Express(), httpServer = Http.createServer(app), httpsServer = Https.createServer(httpsOptionsActual, app), // httpsServer = AutoEncryptLocalhost.https.createServer(app), wss = new CreateWSServer({ server: httpServer }), wsss = new CreateWSServer({ server: httpsServer }), broadcast = (data) => { wss.clients.forEach(function each(client) { if (client.readyState === WebSocket.OPEN) { client.send(JSON.stringify(data)) } }) wsss.clients.forEach(function each(client) { if (client.readyState === WebSocket.OPEN) { client.send(JSON.stringify(data)) } }) } }) { Object.assign(arguments[0], { app, httpServer, httpsServer, wss, wsss, broadcast, wsBoth: [wss, wsss] }) } } const serverInstance = {} for (const key in serverInitialization) { serverInitialization[key](serverInstance) } for (const key in setup) { const _result = setup[key](serverInstance) if (Object.prototype.toString.call(_result) === '[object Promise]') { await _result } } for (const key in creations) { const _result = creations[key](serverInstance) if (Object.prototype.toString.call(_result) === '[object Promise]') { await _result } } if (CORS) { // 最后设置设置跨域 serverInstance.app.use((req, res, next) => { // Access-Control-Allow-Credentials res.set({ 'Access-Control-Allow-Credentials': true, 'Access-Control-Max-Age': 1728000, 'Access-Control-Allow-Origin': req.headers.origin || '*', 'Access-Control-Allow-Headers': 'X-Requested-With,Content-Type', 'Access-Control-Allow-Methods': 'PUT,POST,GET,DELETE,OPTIONS', 'Content-Type': 'application/json; charset=utf-8' }) req.method === 'OPTIONS' ? res.status(204).end() : next() }) } const { httpServer } = serverInstance changePortWhenOccupied && (port = await getUnOccupiedPort(port)) changePortWhenOccupied && (httpPort = await getUnOccupiedPort(httpPort)) httpServer.listen(httpPort) changePortWhenOccupied && (httpsPort = await getUnOccupiedPort(httpsPort)) const { httpsServer } = serverInstance httpsServer.listen(httpsPort) net .createServer(function (socket) { socket.once('data', function (buf) { console.log(buf[0]) // https数据流的第一位是十六进制“16”,转换成十进制就是22 const address = buf[0] === 22 ? httpsPort : httpPort // 创建一个指向https或http服务器的链接 const proxy = net.createConnection(address, function () { proxy.write(buf) // 反向代理的过程,tcp接受的数据交给代理链接,代理链接服务器端返回数据交由socket返回给客户端 socket.pipe(proxy).pipe(socket) }) proxy.on('error', console.log) }) socket.on('error', console.log) }) .listen(port) for (const ip of getIps()) { console.log('server listening at:', { // https: `https://${ip}:${httpsPort}`, // http: `http://${ip}:${httpPort}`, proxy: `http://${ip}:${port}` }) } return { ...arguments[0], ...serverInstance, httpPort, httpsPort, proxyPort: port } } /** * @param {object}root0 - 选项 * @param {Function[]}root0.creations - ({app, wss, broadcast, httpServer})= > { ...todo } * @param {number}[root0.httpPort=8080] - http服务器端口 * @param {number}[root0.httpsPort=8081] - https服务器端口 * @param {object}root0.httpsOptions https的key和cert // * @param {Buffer}root0.httpsOptions.key https-key 默认用localhost // * @param {Buffer}root0.httpsOptions.cert https-cert 默认用localhost * @param {number}[root0.proxyPort=3344] 反向代理的端口 * @param {boolean}root0.CORS 是否跨域 * @returns {Promise<object>}返回{app, httpServer, wss, wsss, broadcast, wsBoth: [wss, wsss]} */ async function clusterExpressWssServer(root0) { const cluster = require('cluster') const numCPUs = require('os').cpus().length const process = require('process') const { createLocalCert } = require('albertgao.common-electron') root0.httpsOptions || (root0.httpsOptions = await createLocalCert({})) if (cluster.isPrimary) { console.log(`Primary ${process.pid} is running`) // 衍生工作进程。 for (let i = 0; i < numCPUs; i++) { cluster.fork() } cluster.on('exit', (worker, code, signal) => { console.log(`worker ${worker.process.pid} died`) }) } else { // 工作进程可以共享任何 TCP 连接 console.log(`Worker ${process.pid} started`) return await expressWssServer({ ...root0, changePortWhenOccupied: false }) } } /** * @param {WebSocket.Server[]} wssArr ws服务器列表 * @param {boolean}[onlineOnly=true] 是否只筛选在线的 * @returns {object[]}返回服务器的所有clients */ function getWssClients(wssArr, onlineOnly = true) { const WebSocket = require('ws') let arr = [] for (const { clients } of wssArr) { arr = [...arr, ...clients] } if (onlineOnly) { return arr.filter(({ readyState }) => readyState === WebSocket.OPEN) } else { return arr } } /** * 设置Wss服务器 * * @param {object[]}wssArr wss服务器数组 * @param {Function}broadcast 功能(会将内容转为JSON.stringify) * @param {object[]}handlers 处理信息的数组 * @param {string}[handlers.e="message"] 什么类型的event,默认message * @param {Function}[handlers.func=({wss,broadcast,msg,json,clients})=>0] 处理函数,传入三个参数wss, broadcast, msg,其中msg已尝试JSON.parse */ function handleWsClient(wssArr = [], broadcast = () => 0, handlers = []) { for (const wss of wssArr) { wss.on('connection', function connection(ws) { for (const { e = 'message', func = () => 0 } of handlers) { ws.on(e, (msg) => { // console.log('@configwss msg=', { msg }) try { const json = JSON.parse(msg) func({ wss, broadcast, json, msg, clients: getWssClients(wssArr) }) } catch (error) { func({ wss, broadcast, msg, clients: getWssClients(wssArr) }) } }) } }) } } module.exports = { getIps, isLocalhost, createLocalCert, expressWssServer, getOccupiedPorts, getUnOccupiedPort, clusterExpressWssServer, getWssClients, handleWsClient }