albertgao.common-electron
Version:
个人常用的nodejs/electron模块
388 lines (382 loc) • 12.8 kB
JavaScript
// 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
}