diff --git a/src/config.js b/src/config.js
index b4490ae..d5860dc 100644
--- a/src/config.js
+++ b/src/config.js
@@ -9,7 +9,7 @@
// export const API_HOST = 'http://202.103.68.144:8889/v2';
// export const WS_URL = 'ws://202.103.68.144:8889';
// export const EMAIL_HOST = 'http://202.103.68.231:888/service-mail';
-export const WAI_HOST = 'http://202.103.68.93:3031/api/v1/whatsapp';
+export const WAI_HOST = 'http://202.103.68.93:3031/wai-server/v1';
export const EMAIL_ATTA_HOST = 'https://p9axztuwd7x8a7.mycht.cn/attatchment'; // 邮件附件
// prod:
export const EMAIL_HOST = 'https://p9axztuwd7x8a7.mycht.cn/mail-server/service-mail';
diff --git a/src/views/Conversations/Online/Components/BubbleIM.jsx b/src/views/Conversations/Online/Components/BubbleIM.jsx
index 8623574..7b3bed8 100644
--- a/src/views/Conversations/Online/Components/BubbleIM.jsx
+++ b/src/views/Conversations/Online/Components/BubbleIM.jsx
@@ -118,7 +118,7 @@ const BubbleIM = ({ handlePreview, handleContactClick, setNewChatModalVisible, s
? {
// styles: { backgroundColor: '#ccd4ae' },
notchStyle: { fill: outboundStyle[message.msg_source.toLowerCase()].color },
- title: <> {message.wabaName} - {message.title}>,
+ title: <>{message.wabaName ? ` ${message.wabaName} - ${message.title}` : ` ${message.title || message.from}`}>,
}
: {
// title: <> {message.title}>,
diff --git a/src/views/accounts/Profile.jsx b/src/views/accounts/Profile.jsx
index bb59c0a..316e16d 100644
--- a/src/views/accounts/Profile.jsx
+++ b/src/views/accounts/Profile.jsx
@@ -5,6 +5,7 @@ import useAuthStore from '@/stores/AuthStore'
import { fetchJSON } from '@/utils/request'
import { Conditional } from '@/components/Conditional'
import { PERM_USE_WHATSAPP } from '@/stores/AuthStore'
+import { WAI_HOST } from '@/config'
function Profile() {
const { notification } = App.useApp()
@@ -47,7 +48,7 @@ function Profile() {
const phone = loginUser.whatsAppNo
fetchJSON(
- 'http://localhost:3031/api/v1/whatsapp/channels/qrcode',
+ `${WAI_HOST}/channels/qrcode`,
{ phone })
.then(r => {
setQRCode(r.result.qr)
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/api/index.js b/wai-server/api/index.js
index 06822fa..648510c 100644
--- a/wai-server/api/index.js
+++ b/wai-server/api/index.js
@@ -9,7 +9,7 @@ const baseName = path.basename(__filename);
function applyApiMiddleware(app) {
const router = new Router({
- prefix: `/api/${apiVersion}/whatsapp`,
+ prefix: `/wai-server/${apiVersion}`,
});
// Require all the folders and create a sub-router for each feature api
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);