From 394b3b568d0c4987a7d7dead953064036c05319f Mon Sep 17 00:00:00 2001 From: Lei OT Date: Tue, 24 Dec 2024 15:26:27 +0800 Subject: [PATCH] =?UTF-8?q?connection=E5=86=99=E6=95=B0=E6=8D=AE=E5=BA=93;?= =?UTF-8?q?=20=E8=AF=B7=E6=B1=82=E6=97=A5=E5=BF=97;=20+=E6=94=AF=E6=8C=81m?= =?UTF-8?q?ulti-form;=20whatsapp=20connection=20event;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- wai-server/.env.development | 3 + wai-server/api/channels/channel.controller.js | 18 +- wai-server/config/components/log4j.config.js | 2 +- wai-server/config/components/server.config.js | 2 + wai-server/core/services/messageHandler.js | 43 -- wai-server/core/services/session.js | 4 +- wai-server/core/services/whatsappHandler.js | 75 ++ wai-server/core/webhook/index.js | 2 +- wai-server/index.js | 4 +- .../{ => components}/request.middleware.js | 1 + .../components/request_log.middleware.js | 17 + wai-server/middleware/index.js | 20 + wai-server/models/connections.js | 81 +++ wai-server/models/init-models.js | 16 + wai-server/models/request_logs.js | 54 ++ wai-server/package-lock.json | 658 +++++++++++++++++- wai-server/package.json | 2 + wai-server/server.js | 7 +- wai-server/services/connections.service.js | 34 + wai-server/services/requestLogs.service.js | 18 + wai-server/utils/commons.util.js | 637 +++++++++++++++++ 21 files changed, 1605 insertions(+), 93 deletions(-) delete mode 100644 wai-server/core/services/messageHandler.js create mode 100644 wai-server/core/services/whatsappHandler.js rename wai-server/middleware/{ => components}/request.middleware.js (93%) create mode 100644 wai-server/middleware/components/request_log.middleware.js create mode 100644 wai-server/middleware/index.js create mode 100644 wai-server/models/connections.js create mode 100644 wai-server/models/init-models.js create mode 100644 wai-server/models/request_logs.js create mode 100644 wai-server/services/connections.service.js create mode 100644 wai-server/services/requestLogs.service.js create mode 100644 wai-server/utils/commons.util.js diff --git a/wai-server/.env.development b/wai-server/.env.development index 2a84488..45152ff 100644 --- a/wai-server/.env.development +++ b/wai-server/.env.development @@ -10,3 +10,6 @@ DB_PORT=3306 NODE_ENV='development' WEBHOOK_URL='https://p9axztuwd7x8a7.mycht.cn/whatsapp_server/wawebhook' + +SERVER_DOMAIN='' +SERVER_NAME='aliyun1' diff --git a/wai-server/api/channels/channel.controller.js b/wai-server/api/channels/channel.controller.js index 01e261b..bbf18a4 100644 --- a/wai-server/api/channels/channel.controller.js +++ b/wai-server/api/channels/channel.controller.js @@ -1,6 +1,7 @@ const { sessionService } = require('../../core'); // Import from core/index.js const { createWhatsApp } = require('../../core/baileys'); // Import from core/index.js -const generateId = require('../../utils/generateId.util'); +const { getConnection } = require('../../services/connections.service'); +const { objectMapper } = require('../../utils/commons.util'); const waInstance = { wa: null, @@ -8,28 +9,29 @@ const waInstance = { exports.newConnect = async ctx => { try { const { phone } = ctx.query; + const findSession = getConnection({ sesson_id: phone, status: 'open' }); + if (findSession) { + return findSession; + } const whatsApp1 = await createWhatsApp(phone); const qr = await whatsApp1.start(); waInstance.wa = whatsApp1; ctx.assert(whatsApp1, 503, 'No available connections'); const { sessionId } = sessionService.createSession(phone, whatsApp1); - return { qr }; + return { qr, phone, sessionId }; } catch (error) { console.error('create connection error', error); ctx.assert(null, 500, 'Failed to create connection or generate QR code.'); - - // const wa = findConnection('from'); - // wa.senedTextMessage(to, text); } }; exports.testSend = async ctx => { - const { from, to, content } = ctx.request.body; + const { to, content } = ctx.request.body; waInstance.wa.sendTextMessage(to, content); return { waInstance, ret: 'Message sent successfully' }; }; -exports.getAll = async ctx => { +exports.getAll = async () => { const sessions = sessionService.sessions; return Array.from(sessions); }; @@ -37,7 +39,7 @@ exports.getAll = async ctx => { /** * @deprecated */ -exports.getIn = async ctx => { +exports.getIn = async () => { // Wait for at least one connection to be established (or handle the case where no connections are available) // await new Promise(resolve => { // const checkConnections = () => { diff --git a/wai-server/config/components/log4j.config.js b/wai-server/config/components/log4j.config.js index a2e2d48..c46902d 100644 --- a/wai-server/config/components/log4j.config.js +++ b/wai-server/config/components/log4j.config.js @@ -42,4 +42,4 @@ const logger = log4js.getLogger(); // console.log = logger.info.bind(logger); // console.error = logger.error.bind(logger); -// module.exports = logger; +module.exports = logger; diff --git a/wai-server/config/components/server.config.js b/wai-server/config/components/server.config.js index cf0dca1..735f9c5 100644 --- a/wai-server/config/components/server.config.js +++ b/wai-server/config/components/server.config.js @@ -29,6 +29,8 @@ const config = { server: { port: envVars.PORT || 3000, apiVersion: envVars.API_VERSION || 'v1', + domain: envVars.SERVER_DOMAIN || '', + name: envVars.SERVER_NAME || '', }, }; diff --git a/wai-server/core/services/messageHandler.js b/wai-server/core/services/messageHandler.js deleted file mode 100644 index 7cda062..0000000 --- a/wai-server/core/services/messageHandler.js +++ /dev/null @@ -1,43 +0,0 @@ -const generateId = require('./../../utils/generateId.util'); -const whatsappEvents = require('../emitter'); -const { callWebhook } = require('../webhook'); - -const logger = console; - -const eventTypeMapped = { - 'message:received': 'wai.message.received', - 'message:updated': 'wai.message.updated', -}; - -const webhookBodyBuilder = (messageData, messageType) => { - const message = { - id: `evt_${generateId().replace(/-/g, '')}`, - type: eventTypeMapped[messageType], - apiVersion: 'v2', - webhooksource: 'wai', - createTime: new Date(new Date().getTime() + 8 * 60 * 60 * 1000).toISOString(), // GMT +8 - waiMessage: messageData, - }; - return message; -}; - -function setupMessageHandler() { - whatsappEvents.on('message:received', async messageData => { - try { - const x = webhookBodyBuilder(messageData, 'message:received'); - await callWebhook(x); - } catch (error) { - logger.error({ messageData, error }, 'error call webhook'); - } - }); - whatsappEvents.on('message:updated', async messageData => { - try { - const x = webhookBodyBuilder(messageData, 'message:updated'); - await callWebhook(x); - } catch (error) { - logger.error({ messageData, error }, 'error call webhook'); - } - }); -} - -module.exports = { setupMessageHandler }; diff --git a/wai-server/core/services/session.js b/wai-server/core/services/session.js index dc2f4ad..88fd5cb 100644 --- a/wai-server/core/services/session.js +++ b/wai-server/core/services/session.js @@ -1,13 +1,11 @@ const whatsappEvents = require('../emitter'); -whatsappEvents.on('connection:added', ({ sock, sessionId }) => {}); -whatsappEvents.on('connection:removed', ({ sessionId }) => {}); - module.exports = () => { const sessions = new Map(); const createSession = (sessionId, ws) => { sessions.set(ws, sessionId); + whatsappEvents.emit('connection:added', ws); return { sessionId }; }; diff --git a/wai-server/core/services/whatsappHandler.js b/wai-server/core/services/whatsappHandler.js new file mode 100644 index 0000000..12f7e78 --- /dev/null +++ b/wai-server/core/services/whatsappHandler.js @@ -0,0 +1,75 @@ +const generateId = require('../../utils/generateId.util'); +const whatsappEvents = require('../emitter'); +const { callWebhook } = require('../webhook'); +const { addConnection, updateConnection } = require('../../services/connections.service'); +const { objectMapper } = require('../../utils/commons.util'); + +const logger = console; + +const connectionEventNames = ['connection:added', 'connection:updated', 'connection:removed']; +const messageEventNames = ['message:received', 'message:updated']; + +const eeventTypeMapped = { + 'message:received': 'wai.message.received', + 'message:updated': 'wai.message.updated', +}; + +const webhookBodyBuilder = (messageData, messageType) => { + const message = { + id: `evt_${generateId().replace(/-/g, '')}`, + type: eeventTypeMapped[messageType], + apiVersion: 'v2', + webhooksource: 'wai', + createTime: new Date(new Date().getTime() + 8 * 60 * 60 * 1000).toISOString(), // GMT +8 + waiMessage: messageData, + }; + return message; +}; + +const setupConnectionHandler = () => { + // connectionEventNames.forEach(eventName => { + logger.info(`Setting up event ${'connection:added'}`); + whatsappEvents.on('connection:added', async connectionData => { + try { + await addConnection({ + ...objectMapper(connectionData, { phone: [{ key: 'wa_id' }, { key: 'sesson_id' }], channelId: 'channel_id', createTimestamp: 'createtime' }), + service_type: 'baileys', + status: 'connecting', + }); + } catch (error) { + logger.error({ connectionData, error }, 'error add connection'); + } + }); + whatsappEvents.on('connection:updated', async connectionData => { + try { + await updateConnection({ + ...objectMapper(connectionData, { phone: [{ key: 'wa_id' }, { key: 'sesson_id' }], channelId: 'channel_id' }), + service_type: 'baileys', + }); + } catch (error) { + logger.error({ connectionData, error }, 'error add connection'); + } + }); + // }); +}; + +const setupMessageHandler = () => { + messageEventNames.forEach(eventName => { + logger.info(`Setting up event ${eventName}`); + whatsappEvents.on(eventName, async messageData => { + try { + const x = webhookBodyBuilder(messageData, eventName); + await callWebhook(x); + } catch (error) { + logger.error({ messageData, error }, 'error call webhook'); + } + }); + }); +}; + +function setupWhatsappHandler() { + setupConnectionHandler(); + setupMessageHandler(); +} + +module.exports = { setupWhatsappHandler }; diff --git a/wai-server/core/webhook/index.js b/wai-server/core/webhook/index.js index 6a6e6cd..7bdf295 100644 --- a/wai-server/core/webhook/index.js +++ b/wai-server/core/webhook/index.js @@ -28,7 +28,7 @@ async function callWebhook(messageData) { return; } await axios.post(webhookUrl, messageData); - logger.info({ webhookUrl: webhookUrl, messageData }, 'Webhook called successfully'); + logger.info(JSON.stringify({ webhookUrl: webhookUrl, messageData }, undefined, 2), 'Webhook called successfully'); } catch (error) { logger.error({ webhookUrl: webhookUrl, messageData, error }, 'Error calling webhook'); } diff --git a/wai-server/index.js b/wai-server/index.js index 1452edc..5ce34fb 100644 --- a/wai-server/index.js +++ b/wai-server/index.js @@ -5,7 +5,7 @@ const server = require('./server'); const { port } = require('./config').server; -const { setupMessageHandler } = require('./core/services/messageHandler'); +const { setupWhatsappHandler } = require('./core/services/whatsappHandler'); async function bootstrap() { /** @@ -14,7 +14,7 @@ async function bootstrap() { * await sequelize.authenticate() */ - setupMessageHandler(); + setupWhatsappHandler(); return http.createServer(server.callback()).listen(port, '0.0.0.0'); } diff --git a/wai-server/middleware/request.middleware.js b/wai-server/middleware/components/request.middleware.js similarity index 93% rename from wai-server/middleware/request.middleware.js rename to wai-server/middleware/components/request.middleware.js index 074f295..1ce8523 100644 --- a/wai-server/middleware/request.middleware.js +++ b/wai-server/middleware/components/request.middleware.js @@ -1,5 +1,6 @@ module.exports = async (ctx, next) => { try { + console.log('request handdle'); const data = await next(); ctx.body = { errcode: 0, diff --git a/wai-server/middleware/components/request_log.middleware.js b/wai-server/middleware/components/request_log.middleware.js new file mode 100644 index 0000000..ac5fb67 --- /dev/null +++ b/wai-server/middleware/components/request_log.middleware.js @@ -0,0 +1,17 @@ +const { createRequestLog } = require('../../services/requestLogs.service'); +const rlog = async (ctx, next) => { + try { + console.log('request log'); + + await next(); + } catch (err) { + } finally { + await createRequestLog({ + method: ctx.method, + path: ctx.method === 'GET' ? ctx.path : ctx.url, + request_data: ctx.method === 'GET' ? JSON.stringify(ctx.query) : JSON.stringify(ctx.request.body), + ip: ctx.ip, + }); + } +}; +module.exports = rlog; diff --git a/wai-server/middleware/index.js b/wai-server/middleware/index.js new file mode 100644 index 0000000..b04dafc --- /dev/null +++ b/wai-server/middleware/index.js @@ -0,0 +1,20 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +const basePath = path.join(__dirname, 'components'); + +function applyMiddleware(app) { + const components = fs.readdirSync(basePath); + const nosorts = components.filter(item => item.indexOf('request.middleware') === -1); + + nosorts.forEach(file => { + const componentMiddleware = require(path.join(basePath, file)); + app.use(componentMiddleware); + }); + const requestHandler = require(path.join(basePath, 'request.middleware.js')); // 必须在最后 + app.use(requestHandler); +} + +module.exports = applyMiddleware; diff --git a/wai-server/models/connections.js b/wai-server/models/connections.js new file mode 100644 index 0000000..10a9fef --- /dev/null +++ b/wai-server/models/connections.js @@ -0,0 +1,81 @@ +const Sequelize = require('sequelize'); +module.exports = function(sequelize, DataTypes) { + return sequelize.define( + 'connections', + { + id: { + autoIncrement: true, + type: DataTypes.BIGINT, + allowNull: false, + primaryKey: true, + }, + opi_sn: { + type: DataTypes.INTEGER, + allowNull: true, + }, + wa_id: { + type: DataTypes.STRING(100), + allowNull: true, + }, + sesson_id: { + type: DataTypes.STRING(255), + allowNull: true, + }, + channel_id: { + type: DataTypes.STRING(255), + allowNull: true, + }, + createtime: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.Sequelize.literal('CURRENT_TIMESTAMP'), + }, + status: { + type: DataTypes.STRING(100), + allowNull: true, + }, + version: { + type: DataTypes.STRING(100), + allowNull: true, + }, + service_type: { + type: DataTypes.STRING(100), + allowNull: true, + }, + updatetime: { + type: DataTypes.DATE, + allowNull: true, + defaultValue: Sequelize.Sequelize.literal('CURRENT_TIMESTAMP'), + }, + connect_domain: { + type: DataTypes.STRING(255), + allowNull: true, + }, + connect_name: { + type: DataTypes.STRING(100), + allowNull: true, + }, + opentime: { + type: DataTypes.DATE, + allowNull: true, + }, + closetime: { + type: DataTypes.DATE, + allowNull: true, + }, + }, + { + sequelize, + tableName: 'connections', + timestamps: false, + indexes: [ + { + name: 'PRIMARY', + unique: true, + using: 'BTREE', + fields: [{ name: 'id' }], + }, + ], + }, + ); +}; diff --git a/wai-server/models/init-models.js b/wai-server/models/init-models.js new file mode 100644 index 0000000..c51bb72 --- /dev/null +++ b/wai-server/models/init-models.js @@ -0,0 +1,16 @@ +var DataTypes = require('sequelize').DataTypes; +var _connections = require('./connections'); +var _request_logs = require('./request_logs'); + +function initModels(sequelize) { + var connections = _connections(sequelize, DataTypes); + var request_logs = _request_logs(sequelize, DataTypes); + + return { + connections, + request_logs, + }; +} +module.exports = initModels; +module.exports.initModels = initModels; +module.exports.default = initModels; diff --git a/wai-server/models/request_logs.js b/wai-server/models/request_logs.js new file mode 100644 index 0000000..9635747 --- /dev/null +++ b/wai-server/models/request_logs.js @@ -0,0 +1,54 @@ +const Sequelize = require('sequelize'); +module.exports = function(sequelize, DataTypes) { + return sequelize.define('request_logs', { + sn: { + autoIncrement: true, + type: DataTypes.BIGINT, + allowNull: false, + primaryKey: true + }, + action: { + type: DataTypes.STRING(100), + allowNull: true + }, + method: { + type: DataTypes.STRING(15), + allowNull: true + }, + path: { + type: DataTypes.STRING(1000), + allowNull: true + }, + request_data: { + type: DataTypes.TEXT, + allowNull: true + }, + snapshots: { + type: DataTypes.TEXT, + allowNull: true + }, + ip: { + type: DataTypes.STRING(200), + allowNull: true + }, + createtime: { + type: DataTypes.DATE, + allowNull: false, + defaultValue: Sequelize.Sequelize.literal('CURRENT_TIMESTAMP') + } + }, { + sequelize, + tableName: 'request_logs', + timestamps: false, + indexes: [ + { + name: "PRIMARY", + unique: true, + using: "BTREE", + fields: [ + { name: "sn" }, + ] + }, + ] + }); +}; diff --git a/wai-server/package-lock.json b/wai-server/package-lock.json index c499411..e418ad5 100644 --- a/wai-server/package-lock.json +++ b/wai-server/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "@koa/cors": "2.2.3", + "@koa/multer": "^3.0.2", "@whiskeysockets/baileys": "^6.7.9", "axios-retry": "^4.5.0", "dotenv": "8.0.0", @@ -20,6 +21,7 @@ "koa-logger": "3.2.0", "koa-router": "7.4.0", "log4js": "^6.9.1", + "multer": "^1.4.5-lts.1", "mysql2": "^3.11.5", "sequelize": "^6.37.5", "uuid": "3.3.2", @@ -46,22 +48,337 @@ "integrity": "sha512-yprSnAtj80/VKuDqRcFFLDYltoNV8tChNwFfIgcf6PGD4sjzWIBgs08pRuTqGH5mk5wgL6PBRSsMCZqtZwzFEw==", "license": "MIT" }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@babel/code-frame": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", "dependencies": { - "@babel/highlight": "^7.0.0" + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@babel/highlight": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", - "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "node_modules/@babel/compat-data": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.3.tgz", + "integrity": "sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", + "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", "dependencies": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.0", + "@babel/generator": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.0", + "@babel/parser": "^7.26.0", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/core/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.3.tgz", + "integrity": "sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==", + "dependencies": { + "@babel/parser": "^7.26.3", + "@babel/types": "^7.26.3", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", + "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", + "dependencies": { + "@babel/compat-data": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", + "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", + "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", + "dependencies": { + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", + "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", + "dependencies": { + "@babel/types": "^7.26.3" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-proposal-export-namespace-from": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", + "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-export-namespace-from instead.", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", + "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", + "dependencies": { + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.26.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.4.tgz", + "integrity": "sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.3", + "@babel/parser": "^7.26.3", + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.3", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/traverse/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/@babel/types": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", + "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@eshaz/web-worker": { @@ -94,6 +411,49 @@ "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", "license": "BSD-3-Clause" }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@koa/cors": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@koa/cors/-/cors-2.2.3.tgz", @@ -105,6 +465,20 @@ "node": ">= 6.0.0" } }, + "node_modules/@koa/multer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@koa/multer/-/multer-3.0.2.tgz", + "integrity": "sha512-Q6WfPpE06mJWyZD1fzxM6zWywaoo+zocAn2YA9QYz4RsecoASr1h/kSzG0c5seDpFVKCMZM9raEfuM7XfqbRLw==", + "dependencies": { + "fix-esm": "1.0.1" + }, + "engines": { + "node": ">= 8" + }, + "peerDependencies": { + "multer": "*" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -827,6 +1201,11 @@ "node": ">=0.10.0" } }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" + }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -1165,6 +1544,53 @@ "node": ">=0.10.0" } }, + "node_modules/browserslist": { + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz", + "integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, "node_modules/bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", @@ -1281,6 +1707,25 @@ "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" }, + "node_modules/caniuse-lite": { + "version": "1.0.30001690", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz", + "integrity": "sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, "node_modules/capture-stack-trace": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", @@ -1483,6 +1928,20 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, "node_modules/configstore": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", @@ -1534,6 +1993,11 @@ "node": ">= 0.6" } }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" + }, "node_modules/cookies": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.7.3.tgz", @@ -1563,8 +2027,7 @@ "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "node_modules/cosmiconfig": { "version": "5.2.1", @@ -1911,6 +2374,11 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, + "node_modules/electron-to-chromium": { + "version": "1.5.76", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.76.tgz", + "integrity": "sha512-CjVQyG7n7Sr+eBXE86HIulnL5N8xZY1sgmOPGuq/F0Rr0FJq63lg0kEtOIDfZBk44FnDLf6FUJ+dsJcuiUDdDQ==" + }, "node_modules/emoji-regex": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", @@ -1969,6 +2437,14 @@ "node": ">= 0.4" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -2794,6 +3270,16 @@ "node": ">=4" } }, + "node_modules/fix-esm": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fix-esm/-/fix-esm-1.0.1.tgz", + "integrity": "sha512-EZtb7wPXZS54GaGxaWxMlhd1DUDCnAg5srlYdu/1ZVeW+7wwR3Tp59nu52dXByFs3MBRq+SByx1wDOJpRvLEXw==", + "dependencies": { + "@babel/core": "^7.14.6", + "@babel/plugin-proposal-export-namespace-from": "^7.14.5", + "@babel/plugin-transform-modules-commonjs": "^7.14.5" + } + }, "node_modules/flat-cache": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", @@ -3642,6 +4128,14 @@ "is-property": "^1.0.2" } }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/get-stdin": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", @@ -4735,6 +5229,17 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", @@ -4751,6 +5256,17 @@ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=" }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -5297,9 +5813,12 @@ } }, "node_modules/minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/mixin-deep": { "version": "1.3.1", @@ -5328,12 +5847,11 @@ } }, "node_modules/mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dependencies": { - "minimist": "0.0.8" + "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" @@ -5376,6 +5894,23 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "node_modules/multer": { + "version": "1.4.5-lts.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", + "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/music-metadata": { "version": "7.14.0", "resolved": "https://registry.npmmirror.com/music-metadata/-/music-metadata-7.14.0.tgz", @@ -5551,6 +6086,11 @@ "node": ">= 8.0.0" } }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==" + }, "node_modules/node-wav": { "version": "0.0.2", "resolved": "https://registry.npmmirror.com/node-wav/-/node-wav-0.0.2.tgz", @@ -5633,6 +6173,14 @@ "node": ">=4" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-copy": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", @@ -5979,6 +6527,11 @@ "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.7.0.tgz", "integrity": "sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==" }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", @@ -6117,8 +6670,7 @@ "node_modules/process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, "node_modules/process-warning": { "version": "1.0.0", @@ -6275,12 +6827,6 @@ "rc": "cli.js" } }, - "node_modules/rc/node_modules/minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, "node_modules/read-pkg": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", @@ -6312,7 +6858,6 @@ "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -6326,8 +6871,7 @@ "node_modules/readable-stream/node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "node_modules/readable-web-to-node-stream": { "version": "3.0.2", @@ -7167,6 +7711,14 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -7539,6 +8091,11 @@ "node": ">= 0.6" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, "node_modules/typescript": { "version": "5.7.2", "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.7.2.tgz", @@ -7720,6 +8277,35 @@ "yarn": "*" } }, + "node_modules/update-browserslist-db": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/update-notifier": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz", @@ -7938,6 +8524,14 @@ "node": ">=4" } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, "node_modules/yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", diff --git a/wai-server/package.json b/wai-server/package.json index d5681ae..d8a6768 100644 --- a/wai-server/package.json +++ b/wai-server/package.json @@ -27,6 +27,7 @@ }, "dependencies": { "@koa/cors": "2.2.3", + "@koa/multer": "^3.0.2", "@whiskeysockets/baileys": "^6.7.9", "axios-retry": "^4.5.0", "dotenv": "8.0.0", @@ -38,6 +39,7 @@ "koa-logger": "3.2.0", "koa-router": "7.4.0", "log4js": "^6.9.1", + "multer": "^1.4.5-lts.1", "mysql2": "^3.11.5", "sequelize": "^6.37.5", "uuid": "3.3.2", diff --git a/wai-server/server.js b/wai-server/server.js index e927807..8e3f3a5 100644 --- a/wai-server/server.js +++ b/wai-server/server.js @@ -1,14 +1,15 @@ const Koa = require('koa'); const server = new Koa(); +const multer = require('@koa/multer')(); const bodyParser = require('koa-bodyparser')(); const compress = require('koa-compress')(); const cors = require('@koa/cors')(/* Add your cors option */); const helmet = require('koa-helmet')(/* Add your security option */); const logger = require('koa-logger')(); -const requestHandler = require('./middleware/request.middleware'); const { isDevelopment } = require('./config'); const applyApiMiddleware = require('./api'); +const applyMiddleware = require('./middleware'); /** * Add here only development middlewares @@ -28,13 +29,13 @@ if (isDevelopment) { * Pass to our server instance middlewares */ server + .use(multer.any()) .use(bodyParser) .use(helmet) .use(compress) .use(cors); -server.use(requestHandler); // 必须在最后 - +applyMiddleware(server); /** * Apply to our server the api router */ diff --git a/wai-server/services/connections.service.js b/wai-server/services/connections.service.js new file mode 100644 index 0000000..f04c206 --- /dev/null +++ b/wai-server/services/connections.service.js @@ -0,0 +1,34 @@ +'use strict'; + +const db = require('../config').database; +const { domain, name } = require('../config').server; +const initModels = require('../models/init-models'); + +const Sequelize = db.sequelize; +const models = initModels(Sequelize); + +const ConnectionsModel = models.connections; + +const addConnection = async data => { + const r = await ConnectionsModel.create({ ...data, connect_domain: domain, connect_name: name }); + return r; +}; + +const updateConnection = async data => { + const r = await ConnectionsModel.update( + { + ...data, + ...(data.status === 'open' ? { opentime: Sequelize.fn('NOW') } : {}), + ...(data.status === 'close' ? { closetime: Sequelize.fn('NOW') } : {}), + }, + { where: { channel_id: data.channel_id, sesson_id: data.sesson_id } }, + ); + return r; +}; + +const getConnection = async data => { + const r = await ConnectionsModel.findOne({ where: data }); + return r; +}; + +module.exports = { addConnection, updateConnection, getConnection }; diff --git a/wai-server/services/requestLogs.service.js b/wai-server/services/requestLogs.service.js new file mode 100644 index 0000000..fd211b2 --- /dev/null +++ b/wai-server/services/requestLogs.service.js @@ -0,0 +1,18 @@ +'use strict'; + +const db = require('./../config').database; +const initModels = require('./../models/init-models'); + +const Sequelize = db.sequelize; +const models = initModels(Sequelize); + +const LogsModel = models.request_logs; + +// LogsModel.sync({ force: false }); + +const createRequestLog = async data => { + const r = await LogsModel.create(data, { logging: false }); + return r; +}; + +module.exports = { createRequestLog }; diff --git a/wai-server/utils/commons.util.js b/wai-server/utils/commons.util.js new file mode 100644 index 0000000..3ffceba --- /dev/null +++ b/wai-server/utils/commons.util.js @@ -0,0 +1,637 @@ +exports.copy = (obj) => { + return JSON.parse(JSON.stringify(obj)); +} + +exports.formatDate = (date) => { + if (isEmpty(date)) { + return 'NaN'; + } + + const year = date.getFullYear(); + const month = date.getMonth() + 1; + const day = date.getDate(); + + const monthStr = ('' + month).padStart(2, 0); + const dayStr = ('' + day).padStart(2, 0); + const formatted = year + '-' + monthStr + '-' + dayStr; + + return formatted; +} + +exports.formatTime = (date) => { + const hours = date.getHours(); + const minutes = date.getMinutes(); + + const hoursStr = ('' + hours).padStart(2, 0); + const minutesStr = ('' + minutes).padStart(2, 0); + const formatted = hoursStr + ':' + minutesStr; + + return formatted; +} + +exports.formatDatetime = (date) => { + const year = date.getFullYear(); + const month = date.getMonth() + 1; + const day = date.getDate(); + + const monthStr = ('' + month).padStart(2, 0); + const dayStr = ('' + day).padStart(2, 0); + + const hours = date.getHours(); + const minutes = date.getMinutes(); + + const hoursStr = ('' + hours).padStart(2, 0); + const minutesStr = ('' + minutes).padStart(2, 0); + + const formatted = year + '-' + monthStr + '-' + dayStr + ' ' + hoursStr + ':' + minutesStr; + + return formatted; +} + +exports.camelCase = (name) => { + return name.substr(0, 1).toLowerCase() + name.substr(1); +} + +class UrlBuilder { + constructor(url) { + this.url = url; + this.paramList = []; + } + + append(name, value) { + if (isNotEmpty(value)) { + this.paramList.push({ name: name, value: value }); + } + return this; + } + + build() { + this.paramList.forEach((e, i, a) => { + if (i === 0) { + this.url += '?'; + } else { + this.url += '&'; + } + this.url += e.name + '=' + e.value; + }); + return this.url; + } +} + +exports.isNotEmpty = (val) => { + return val !== undefined && val !== null && val !== ''; +} + +exports.prepareUrl = (url) => { + return new UrlBuilder(url); +} + +exports.throttle = (fn, delay, atleast) => { + let timeout = null, + startTime = new Date(); + return function () { + let curTime = new Date(); + clearTimeout(timeout); + if (curTime - startTime >= atleast) { + fn(); + startTime = curTime; + } else { + timeout = setTimeout(fn, delay); + } + }; +} + +exports.clickUrl = (url) => { + const httpLink = document.createElement('a'); + httpLink.href = url; + httpLink.target = '_blank'; + httpLink.click(); +} + +exports.escape2Html = (str) => { + var temp = document.createElement('div'); + temp.innerHTML = str; + var output = temp.innerText || temp.textContent; + temp = null; + return output; +} + +exports.formatPrice = (price) => { + return Math.ceil(price).toLocaleString(); +} + +exports.formatPercent = (number) => { + return Math.round(number * 100) + '%'; +} + +/** + * ! 不支持计算 Set 或 Map + * @param {*} val + * @example + * true if: 0, [], {}, null, '', undefined + * false if: 'false', 'undefined' + */ +exports.isEmpty = (val) => { + // return val === undefined || val === null || val === ""; + return [Object, Array].includes((val || {}).constructor) && !Object.entries(val || {}).length; +} +/** + * 数组排序 + */ +exports.sortBy = (key) => { + return (a, b) => (getNestedValue(a, key) > getNestedValue(b, key) ? 1 : getNestedValue(b, key) > getNestedValue(a, key) ? -1 : 0); +}; + +/** + * Object排序keys + */ +exports.sortKeys = (obj) => + Object.keys(obj) + .sort() + .reduce((a, k2) => ({ ...a, [k2]: obj[k2] }), {}); + +/** + * 数组排序, 给定排序数组 + * @param {array} items 需要排序的数组 + * @param {array} keyName 排序的key + * @param {array} keyOrder 给定排序 + * @returns + */ +exports.sortArrayByOrder = (items, keyName, keyOrder) => { + return items.sort((a, b) => { + return keyOrder.indexOf(a[keyName]) - keyOrder.indexOf(b[keyName]); + }); +}; +/** + * 合并Object, 递归地 + */ +exports.merge = (...objects) => { + const isDeep = objects.some((obj) => obj !== null && typeof obj === 'object'); + + const result = objects[0] || (isDeep ? {} : objects[0]); + + for (let i = 1; i < objects.length; i++) { + const obj = objects[i]; + + if (!obj) continue; + + Object.keys(obj).forEach((key) => { + const val = obj[key]; + + if (isDeep) { + if (Array.isArray(val)) { + result[key] = [].concat(Array.isArray(result[key]) ? result[key] : [result[key]], val); + } else if (typeof val === 'object') { + result[key] = merge(result[key], val); + } else { + result[key] = val; + } + } else { + result[key] = typeof val === 'boolean' ? val : result[key]; + } + }); + } + + return result; +} + +/** + * 数组分组 + * - 相当于 lodash 的 _.groupBy + * @see https://www.lodashjs.com/docs/lodash.groupBy#_groupbycollection-iteratee_identity + */ +exports.groupBy = (array = [], callback) => { + return array.reduce((groups, item) => { + const key = typeof callback === 'function' ? callback(item) : item[callback]; + + if (!groups[key]) { + groups[key] = []; + } + + groups[key].push(item); + return groups; + }, {}); +} + +/** + * 创建一个从 object 中选中的属性的对象。 + * @param {*} object + * @param {array} keys + */ +exports.pick = (object, keys) => { + return keys.reduce((obj, key) => { + if (object && Object.prototype.hasOwnProperty.call(object, key)) { + obj[key] = object[key]; + } + return obj; + }, {}); +} + +/** + * 返回对象的副本,经过筛选以省略指定的键。 + * @param {*} object + * @param {string[]} keysToOmit + * @returns + */ +exports.omit = (object, keysToOmit) => { + return Object.fromEntries(Object.entries(object).filter(([key]) => !keysToOmit.includes(key))); +} + +/** + * 深拷贝 + */ +exports.cloneDeep = (value, visited = new WeakMap()) => { + // 处理循环引用 + if (visited.has(value)) { + return visited.get(value); + } + + // 特殊对象和基本类型处理 + if (value instanceof Date) { + return new Date(value); + } + if (value instanceof RegExp) { + return new RegExp(value.source, value.flags); + } + if (value === null || typeof value !== 'object') { + return value; + } + + // 创建一个新的WeakMap项以避免内存泄漏 + let result; + if (Array.isArray(value)) { + result = []; + visited.set(value, result); + } else { + result = {}; + visited.set(value, result); + } + + for (const key of Object.getOwnPropertySymbols(value)) { + // 处理Symbol属性 + result[key] = cloneDeep(value[key], visited); + } + + for (const key in value) { + if (Object.prototype.hasOwnProperty.call(value, key)) { + // 处理普通属性 + result[key] = cloneDeep(value[key], visited); + } + } + + return result; +} +/** + * 向零四舍五入, 固定精度设置 + */ +const curriedFix = (precision = 0) => { + return function (number) { + // Shift number by precision places + const shift = Math.pow(10, precision); + const shiftedNumber = number * shift; + + // Round to nearest integer + const roundedNumber = Math.round(shiftedNumber); + + // Shift back decimal place + return roundedNumber / shift; + }; +} +exports.curriedFix = curriedFix; +/** + * 向零四舍五入, 保留2位小数 + */ +exports.fixTo2Decimals = curriedFix(2); +/** + * 向零四舍五入, 保留4位小数 + */ +exports.fixTo4Decimals = curriedFix(4); + +exports.fixTo1Decimals = curriedFix(1); +exports.fixToInt = curriedFix(0); + +/** + * 创建一个按大小分组的元素数组 + */ +exports.chunk = (input = [], size = 0) => { + return input.reduce((arr, item, idx) => { + return idx % size === 0 ? [...arr, [item]] : [...arr.slice(0, -1), [...arr.slice(-1)[0], item]]; + }, []); +}; + +/** + * 映射 + * @example + * const keyMap = { + a: [{key: 'a1'}, {key: 'a2', transform: v => v * 2}], + b: {key: 'b1'} + }; + const result = objectMapper({a: 1, b: 3}, keyMap); + // result = {a1: 1, a2: 2, b1: 3} + * + */ +exports.objectMapper = (input, keyMap, keep = true) => { + // Loop through array mapping + if (Array.isArray(input)) { + return input.map((obj) => objectMapper(obj, keyMap)); + } + + if (typeof input === 'object') { + const mappedObj = {}; + + Object.keys(input).forEach((key) => { + // Keep original keys not in keyMap + if (!keyMap[key] && keep) { + mappedObj[key] = input[key]; + } + // Handle array of maps + if (Array.isArray(keyMap[key])) { + keyMap[key].forEach((map) => { + let value = input[key]; + if (map.transform) value = map.transform(value); + mappedObj[map.key] = value; + }); + + // Handle single map + } else { + const map = keyMap[key]; + if (map) { + let value = input[key]; + if (map.transform) value = map.transform(value); + mappedObj[map.key || map] = value; + } + } + }); + + return mappedObj; + } + + return input; +} + +/** + * 创建一个对应于对象路径的值数组 + */ +exports.at = (obj, path) => { + let result; + if (Array.isArray(obj)) { + // array case + const indexes = path.split('.').map((i) => parseInt(i)); + result = []; + for (let i = 0; i < indexes.length; i++) { + result.push(obj[indexes[i]]); + } + } else { + // object case + const indexes = path.split('.').map((i) => i); + result = [obj]; + for (let i = 0; i < indexes.length; i++) { + result = [result[0]?.[indexes[i]] || undefined]; + } + } + return result; +} +/** + * 删除 null/undefined + */ +exports.flush = (collection) => { + let result, len, i; + if (!collection) { + return undefined; + } + if (Array.isArray(collection)) { + result = []; + len = collection.length; + for (i = 0; i < len; i++) { + const elem = collection[i]; + if (elem != null) { + result.push(elem); + } + } + return result; + } + if (typeof collection === 'object') { + result = {}; + const keys = Object.keys(collection); + len = keys.length; + for (i = 0; i < len; i++) { + const key = keys[i]; + const value = collection[key]; + if (value != null) { + result[key] = value; + } + } + return result; + } + return undefined; +} + +/** + * 千分位 格式化数字 + */ +exports.numberFormatter = (number) => { + return new Intl.NumberFormat().format(number); +}; + +/** + * @example + * const obj = { a: { b: 'c' } }; + * const keyArr = ['a', 'b']; + * getNestedValue(obj, keyArr); // Returns: 'c' + */ +exports.getNestedValue = (obj, keyArr) => { + return keyArr.reduce((acc, curr) => { + return acc && Object.prototype.hasOwnProperty.call(acc, curr) ? acc[curr] : undefined; + // return acc && acc[curr]; + }, obj); +}; + +/** + * 计算笛卡尔积 + */ +exports.cartesianProductArray = (arr, sep = '_', index = 0, prefix = '') => { + let result = []; + if (index === arr.length) { + return [prefix]; + } + arr[index].forEach((item) => { + result = result.concat(cartesianProductArray(arr, sep, index + 1, prefix ? `${prefix}${sep}${item}` : `${item}`)); + }); + return result; +}; + +exports.stringToColour = (str) => { + var hash = 0; + for (let i = 0; i < str.length; i++) { + hash = str.charCodeAt(i) + ((hash << 5) - hash); + } + var colour = '#'; + for (let i = 0; i < 3; i++) { + var value = (hash >> (i * 8)) & 0xff; + value = (value % 150) + 50; + colour += ('00' + value.toString(16)).substr(-2); + } + return colour; +}; + +exports.debounce = (func, wait, immediate) => { + var timeout; + return function () { + var context = this, + args = arguments; + clearTimeout(timeout); + if (immediate && !timeout) func.apply(context, args); + timeout = setTimeout(function () { + timeout = null; + if (!immediate) func.apply(context, args); + }, wait); + }; +}; + +exports.removeFormattingChars = (str) => { + const regex = /[\r\n\t\v\f]/g; + str = str.replace(regex, ' '); + // Replace more than four consecutive spaces with a single space + str = str.replace(/\s{4,}/g, ' '); + return str; +}; + +exports.olog = (text, ...args) => { + console.log(`%c ${text} `, 'background:#fb923c ; padding: 1px; border-radius: 3px; color: #fff', ...args); +}; + +exports.sanitizeFilename = (str) => { + // Remove whitespace and replace with hyphens + str = str.replace(/\s+/g, '-'); + // Remove invalid characters and replace with hyphens + str = str.replace(/[^a-zA-Z0-9.-]/g, '-'); + // Replace consecutive hyphens with a single hyphen + str = str.replace(/-+/g, '-'); + // Trim leading and trailing hyphens + str = str.replace(/^-+|-+$/g, ''); + return str; +}; + +exports.formatBytes = (bytes, decimals = 2) => { + if (bytes === 0) return ''; + + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; +}; +exports.calcCacheSizes = async () => { + try { + let swCacheSize = 0; + let diskCacheSize = 0; + let indexedDBSize = 0; + + // 1. Get the service worker cache size + if ('caches' in window) { + const cacheNames = await caches.keys(); + for (const name of cacheNames) { + const cache = await caches.open(name); + const requests = await cache.keys(); + for (const request of requests) { + const response = await cache.match(request); + swCacheSize += Number(response.headers.get('Content-Length')) || 0; + } + } + } + + // 2. Get the disk cache size + // const diskCacheName = 'disk-cache'; + // const diskCache = await caches.open(diskCacheName); + // const diskCacheKeys = await diskCache.keys(); + // for (const request of diskCacheKeys) { + // const response = await diskCache.match(request); + // diskCacheSize += Number(response.headers.get('Content-Length')) || 0; + // } + + // 3. Get the IndexedDB cache size + // const indexedDBNames = await window.indexedDB.databases(); + // for (const dbName of indexedDBNames) { + // const db = await window.indexedDB.open(dbName.name); + // const objectStoreNames = db.objectStoreNames; + + // if (objectStoreNames !== undefined) { + // const objectStores = Array.from(objectStoreNames).map((storeName) => db.transaction([storeName], 'readonly').objectStore(storeName)); + + // for (const objectStore of objectStores) { + // const request = objectStore.count(); + // request.onsuccess = () => { + // indexedDBSize += request.result; + // }; + // } + // } + // } + + return { swCacheSize, diskCacheSize, indexedDBSize, totalSize: Number(swCacheSize) + Number(diskCacheSize) + indexedDBSize }; + } catch (error) { + console.error('Error getting cache sizes:', error); + } +}; + +exports.clearAllCaches = async (cb) => { + try { + // 1. Clear the service worker cache + if ('caches' in window) { + // if (navigator.serviceWorker) { + const cacheNames = await caches.keys(); + await Promise.all(cacheNames.map((name) => caches.delete(name))); + } + + // 2. Clear the disk cache (HTTP cache) + // const diskCacheName = 'disk-cache'; + // await window.caches.delete(diskCacheName); + // const diskCache = await window.caches.open(diskCacheName); + // const diskCacheKeys = await diskCache.keys(); + // await Promise.all(diskCacheKeys.map((request) => diskCache.delete(request))); + + // 3. Clear the IndexedDB cache + const indexedDBNames = await window.indexedDB.databases(); + await Promise.all(indexedDBNames.map((dbName) => window.indexedDB.deleteDatabase(dbName.name))); + + // Unregister the service worker + const registration = await navigator.serviceWorker.getRegistration(); + if (registration) { + await registration.unregister(); + console.log('Service worker unregistered'); + } else { + console.log('No service worker registered'); + } + if (typeof cb === 'function') { + cb(); + } + } catch (error) { + console.error('Error clearing caches or unregistering service worker:', error); + } +}; + +exports.loadScript = (src) => { + return new Promise((resolve, reject) => { + const script = document.createElement('script'); + script.type = 'text/javascript'; + script.onload = resolve; + script.onerror = reject; + script.crossOrigin = 'anonymous'; + script.src = src; + if (document.head.append) { + document.head.append(script); + } else { + document.getElementsByTagName('head')[0].appendChild(script); + } + }); +}; + +//格式化为冒号时间,2010转为20:10 +exports.formatColonTime = (text) => { + const hours = text.substring(0, 2); + const minutes = text.substring(2); + return `${hours}:${minutes}`; +}; + +// 生成唯一 36 位数字,用于新增记录 ID 赋值,React key 属性等 +exports.generateId = () => new Date().getTime().toString(36) + Math.random().toString(36).substring(2, 9);