asaihost-nodejs 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -25
- package/dist/InterventionLog-CRP6YJVM.cjs +30 -0
- package/dist/{InterventionLog-25EHE4QR.cjs.map → InterventionLog-CRP6YJVM.cjs.map} +1 -1
- package/dist/{InterventionLog-R6CCPU2B.es.js → InterventionLog-PZT2CCN3.es.js} +5 -5
- package/dist/LogStore-7MTUQA4G.cjs +54 -0
- package/dist/{LogStore-M2AUBNGO.cjs.map → LogStore-7MTUQA4G.cjs.map} +1 -1
- package/dist/{LogStore-ECXX34WC.es.js → LogStore-Q35YFNAY.es.js} +3 -3
- package/dist/asaihost-nodejs.cjs +1304 -1447
- package/dist/asaihost-nodejs.cjs.map +1 -1
- package/dist/asaihost-nodejs.d.cts +154 -17
- package/dist/asaihost-nodejs.d.ts +154 -17
- package/dist/asaihost-nodejs.es.js +921 -1064
- package/dist/asaihost-nodejs.es.js.map +1 -1
- package/dist/{chunk-HHPGUJ75.cjs → chunk-2BCIIKRG.cjs} +13 -6
- package/dist/chunk-2BCIIKRG.cjs.map +1 -0
- package/dist/{chunk-G4WVDUBH.cjs → chunk-4E7Z5K5U.cjs} +7 -7
- package/dist/{chunk-G4WVDUBH.cjs.map → chunk-4E7Z5K5U.cjs.map} +1 -1
- package/dist/{chunk-DROOHKB3.es.js → chunk-57UQJKSL.es.js} +2 -2
- package/dist/{chunk-EOGZPRML.es.js → chunk-A5CDJGJI.es.js} +224 -39
- package/dist/chunk-A5CDJGJI.es.js.map +1 -0
- package/dist/{chunk-E7V3EBDB.es.js → chunk-A6VJPLDD.es.js} +5 -3
- package/dist/chunk-A6VJPLDD.es.js.map +1 -0
- package/dist/{chunk-X7VHW2TD.cjs → chunk-H3K33HBA.cjs} +249 -64
- package/dist/chunk-H3K33HBA.cjs.map +1 -0
- package/dist/{chunk-KOFCBB3N.cjs → chunk-K2QPSQEV.cjs} +9 -9
- package/dist/{chunk-KOFCBB3N.cjs.map → chunk-K2QPSQEV.cjs.map} +1 -1
- package/dist/{chunk-LOMSUPPU.es.js → chunk-LTEP4XB2.es.js} +6 -6
- package/dist/{chunk-QWVR2HNT.cjs → chunk-LXJMGLKW.cjs} +21 -19
- package/dist/chunk-LXJMGLKW.cjs.map +1 -0
- package/dist/{chunk-4WJDC4M4.es.js → chunk-NLZNIGNF.es.js} +2 -2
- package/dist/chunk-NLZNIGNF.es.js.map +1 -0
- package/dist/{chunk-W7DVKGX5.cjs → chunk-OCEBEEP5.cjs} +2 -2
- package/dist/{chunk-W7DVKGX5.cjs.map → chunk-OCEBEEP5.cjs.map} +1 -1
- package/dist/{chunk-NLBFIRDB.cjs → chunk-RIRKIYET.cjs} +12 -12
- package/dist/{chunk-NLBFIRDB.cjs.map → chunk-RIRKIYET.cjs.map} +1 -1
- package/dist/{chunk-XBIN4C2V.es.js → chunk-SB23ESX2.es.js} +2 -2
- package/dist/{chunk-VLMM735Q.cjs → chunk-TKJAHTPO.cjs} +26 -26
- package/dist/{chunk-VLMM735Q.cjs.map → chunk-TKJAHTPO.cjs.map} +1 -1
- package/dist/{chunk-MCMADJQR.es.js → chunk-VUZO7DCA.es.js} +4 -4
- package/dist/chunk-VUZO7DCA.es.js.map +1 -0
- package/dist/{chunk-KUZQ36IJ.es.js → chunk-ZQBHJRAO.es.js} +10 -3
- package/dist/chunk-ZQBHJRAO.es.js.map +1 -0
- package/dist/intervention-hooks-KF3SNB5Q.cjs +21 -0
- package/dist/{intervention-hooks-LE6X4C6N.cjs.map → intervention-hooks-KF3SNB5Q.cjs.map} +1 -1
- package/dist/{intervention-hooks-FI4EHOL5.es.js → intervention-hooks-TCGIC4IV.es.js} +6 -6
- package/dist/log-formats-TUXGMCCU.cjs +31 -0
- package/dist/{log-formats-FKFMQKNO.cjs.map → log-formats-TUXGMCCU.cjs.map} +1 -1
- package/dist/{log-formats-I5E32H5F.es.js → log-formats-WW3TDUAK.es.js} +2 -2
- package/dist/{password-63OY27RX.es.js → password-4BABWAG4.es.js} +4 -4
- package/dist/password-PJHJRGPE.cjs +20 -0
- package/dist/{password-NZ5EGDWG.cjs.map → password-PJHJRGPE.cjs.map} +1 -1
- package/dist/user-store-6J2PSEIN.cjs +12 -0
- package/dist/{user-store-SX4N6PQ4.cjs.map → user-store-6J2PSEIN.cjs.map} +1 -1
- package/dist/user-store-UC5URX3C.es.js +12 -0
- package/package.json +2 -13
- package/dist/InterventionLog-25EHE4QR.cjs +0 -30
- package/dist/LogStore-M2AUBNGO.cjs +0 -54
- package/dist/chunk-4WJDC4M4.es.js.map +0 -1
- package/dist/chunk-E7V3EBDB.es.js.map +0 -1
- package/dist/chunk-EOGZPRML.es.js.map +0 -1
- package/dist/chunk-HHPGUJ75.cjs.map +0 -1
- package/dist/chunk-KUZQ36IJ.es.js.map +0 -1
- package/dist/chunk-MCMADJQR.es.js.map +0 -1
- package/dist/chunk-QWVR2HNT.cjs.map +0 -1
- package/dist/chunk-X7VHW2TD.cjs.map +0 -1
- package/dist/intervention-hooks-LE6X4C6N.cjs +0 -21
- package/dist/log-formats-FKFMQKNO.cjs +0 -31
- package/dist/password-NZ5EGDWG.cjs +0 -20
- package/dist/user-store-J7LWEXSW.es.js +0 -12
- package/dist/user-store-SX4N6PQ4.cjs +0 -12
- /package/dist/{InterventionLog-R6CCPU2B.es.js.map → InterventionLog-PZT2CCN3.es.js.map} +0 -0
- /package/dist/{LogStore-ECXX34WC.es.js.map → LogStore-Q35YFNAY.es.js.map} +0 -0
- /package/dist/{chunk-DROOHKB3.es.js.map → chunk-57UQJKSL.es.js.map} +0 -0
- /package/dist/{chunk-LOMSUPPU.es.js.map → chunk-LTEP4XB2.es.js.map} +0 -0
- /package/dist/{chunk-XBIN4C2V.es.js.map → chunk-SB23ESX2.es.js.map} +0 -0
- /package/dist/{intervention-hooks-FI4EHOL5.es.js.map → intervention-hooks-TCGIC4IV.es.js.map} +0 -0
- /package/dist/{log-formats-I5E32H5F.es.js.map → log-formats-WW3TDUAK.es.js.map} +0 -0
- /package/dist/{password-63OY27RX.es.js.map → password-4BABWAG4.es.js.map} +0 -0
- /package/dist/{user-store-J7LWEXSW.es.js.map → user-store-UC5URX3C.es.js.map} +0 -0
|
@@ -4,10 +4,10 @@ import {
|
|
|
4
4
|
logSecurityConfigIntervention,
|
|
5
5
|
logSessionTerminate,
|
|
6
6
|
logUserRbacChange
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-SB23ESX2.es.js";
|
|
8
8
|
import {
|
|
9
9
|
InterventionLog_exports
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-VUZO7DCA.es.js";
|
|
11
11
|
import {
|
|
12
12
|
LogStore_exports,
|
|
13
13
|
archiveInterventionYear,
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
queryUnifiedLogs,
|
|
29
29
|
readWebLogFromStore,
|
|
30
30
|
searchUnifiedLogs
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-LTEP4XB2.es.js";
|
|
32
32
|
import {
|
|
33
33
|
W3C_ACCESS_FIELDS,
|
|
34
34
|
W3C_SERVER_FIELDS,
|
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
formatW3CHeader,
|
|
39
39
|
formatW3CServerRecord,
|
|
40
40
|
parseCLF
|
|
41
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-NLZNIGNF.es.js";
|
|
42
42
|
import {
|
|
43
43
|
getNowTime
|
|
44
44
|
} from "./chunk-UTKVJE7N.es.js";
|
|
@@ -48,7 +48,7 @@ import {
|
|
|
48
48
|
secureCompare,
|
|
49
49
|
validatePasswordChange,
|
|
50
50
|
verifyPassword
|
|
51
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-57UQJKSL.es.js";
|
|
52
52
|
import {
|
|
53
53
|
USER_LV,
|
|
54
54
|
applySecretsToHostConfig,
|
|
@@ -60,8 +60,9 @@ import {
|
|
|
60
60
|
resolveHttpsCertFromPaths,
|
|
61
61
|
stripForbiddenSecretsFromHostConfig,
|
|
62
62
|
validateAsaiSecrets
|
|
63
|
-
} from "./chunk-
|
|
63
|
+
} from "./chunk-A6VJPLDD.es.js";
|
|
64
64
|
import {
|
|
65
|
+
getDefaultResetPassword,
|
|
65
66
|
getPasswordPolicy,
|
|
66
67
|
getPermissionThreshold,
|
|
67
68
|
getPublicSecurityConfig,
|
|
@@ -70,11 +71,12 @@ import {
|
|
|
70
71
|
isValidUserLv,
|
|
71
72
|
loadUserLevelConfig,
|
|
72
73
|
resolveRoleForLevel,
|
|
73
|
-
saveUserLevelConfig
|
|
74
|
-
|
|
74
|
+
saveUserLevelConfig,
|
|
75
|
+
validatePasswordWithPolicy
|
|
76
|
+
} from "./chunk-A5CDJGJI.es.js";
|
|
75
77
|
import {
|
|
76
78
|
getDataServerForAsDb
|
|
77
|
-
} from "./chunk-
|
|
79
|
+
} from "./chunk-ZQBHJRAO.es.js";
|
|
78
80
|
import {
|
|
79
81
|
__export,
|
|
80
82
|
__name
|
|
@@ -210,7 +212,7 @@ ${reason.name}: ${reason.message}`;
|
|
|
210
212
|
if (restartTimes.length > MAX_RESTARTS) {
|
|
211
213
|
try {
|
|
212
214
|
$asai.$log("NO_RESTART", "Process crashed too frequently, gracefully shutdown and wait for daemon restart.");
|
|
213
|
-
void import("./intervention-hooks-
|
|
215
|
+
void import("./intervention-hooks-TCGIC4IV.es.js").then(
|
|
214
216
|
({ logProcessGuardEvent }) => logProcessGuardEvent($asai, {
|
|
215
217
|
action: "crash_loop_shutdown",
|
|
216
218
|
detail: `${restartTimes.length} crashes in ${RESTART_WINDOW}ms window`,
|
|
@@ -252,7 +254,7 @@ ${reason.name}: ${reason.message}`;
|
|
|
252
254
|
}
|
|
253
255
|
__name(initProcess, "initProcess");
|
|
254
256
|
function gracefulShutdown($asai) {
|
|
255
|
-
void import("./intervention-hooks-
|
|
257
|
+
void import("./intervention-hooks-TCGIC4IV.es.js").then(
|
|
256
258
|
({ logProcessGuardEvent }) => logProcessGuardEvent($asai, {
|
|
257
259
|
action: "graceful_shutdown",
|
|
258
260
|
detail: "SIGTERM/SIGINT or crash guard triggered",
|
|
@@ -395,7 +397,7 @@ var LogBuffer = class _LogBuffer {
|
|
|
395
397
|
}
|
|
396
398
|
};
|
|
397
399
|
function saveLog($asai, src, logstr) {
|
|
398
|
-
return new Promise((
|
|
400
|
+
return new Promise((resolve6, reject) => {
|
|
399
401
|
try {
|
|
400
402
|
const bufferKey = `${$asai.id || "default"}_${src}`;
|
|
401
403
|
let logBuffer = logBuffers.get(bufferKey);
|
|
@@ -404,7 +406,7 @@ function saveLog($asai, src, logstr) {
|
|
|
404
406
|
logBuffers.set(bufferKey, logBuffer);
|
|
405
407
|
}
|
|
406
408
|
logBuffer.add(logstr);
|
|
407
|
-
|
|
409
|
+
resolve6();
|
|
408
410
|
} catch (err) {
|
|
409
411
|
console.error(err);
|
|
410
412
|
reject(err);
|
|
@@ -424,7 +426,7 @@ async function ensureW3CHeader($asai, filePath, dateCur) {
|
|
|
424
426
|
} catch {
|
|
425
427
|
}
|
|
426
428
|
const header = formatW3CHeader({
|
|
427
|
-
software: $asai.hostconfig.website?.title || "
|
|
429
|
+
software: $asai.hostconfig.website?.title || "AsaiHostNodejs",
|
|
428
430
|
version: $asai.hostconfig.website?.ver || "1.0.0.0",
|
|
429
431
|
fields: [...W3C_SERVER_FIELDS],
|
|
430
432
|
date: dateCur
|
|
@@ -507,7 +509,7 @@ async function ensureW3CAccessHeader($asai, filePath, dateCur) {
|
|
|
507
509
|
} catch {
|
|
508
510
|
}
|
|
509
511
|
const header = formatW3CHeader({
|
|
510
|
-
software: $asai.hostconfig.website?.title || "
|
|
512
|
+
software: $asai.hostconfig.website?.title || "AsaiHostNodejs",
|
|
511
513
|
version: $asai.hostconfig.website?.ver || "1.0.0.0",
|
|
512
514
|
fields: [...W3C_ACCESS_FIELDS],
|
|
513
515
|
date: dateCur
|
|
@@ -843,7 +845,7 @@ function verifyConfigManifest(serverRoot) {
|
|
|
843
845
|
__name(verifyConfigManifest, "verifyConfigManifest");
|
|
844
846
|
|
|
845
847
|
// src/core/init.ts
|
|
846
|
-
function
|
|
848
|
+
function initAsaiHostNodejs($asai, _opt) {
|
|
847
849
|
if (!$asai.hostconfig?.ip) {
|
|
848
850
|
$asai.hostconfig.ip = getIp();
|
|
849
851
|
}
|
|
@@ -870,7 +872,7 @@ function initAsaiHost($asai, _opt) {
|
|
|
870
872
|
type: "CONFIG_WARN",
|
|
871
873
|
detail: "Using dev-only password; set ASAI_HOST_PASSWORD for production (IEC 62443-4-2 CR 1.7)"
|
|
872
874
|
});
|
|
873
|
-
void import("./intervention-hooks-
|
|
875
|
+
void import("./intervention-hooks-TCGIC4IV.es.js").then(
|
|
874
876
|
({ logSecurityConfigIntervention: logSecurityConfigIntervention2 }) => logSecurityConfigIntervention2($asai, {
|
|
875
877
|
section: "password",
|
|
876
878
|
detail: "dev-default-password detected at startup"
|
|
@@ -890,13 +892,13 @@ function initAsaiHost($asai, _opt) {
|
|
|
890
892
|
if (!ntp.ok) logSecurityEvent($asai, { type: "CONFIG_WARN", detail: `NTP: ${ntp.detail}` });
|
|
891
893
|
});
|
|
892
894
|
}
|
|
893
|
-
__name(
|
|
895
|
+
__name(initAsaiHostNodejs, "initAsaiHostNodejs");
|
|
894
896
|
function scheduleLogComplianceCheck($asai) {
|
|
895
897
|
const hours = Number($asai?.hostconfig?.logger?.complianceIntervalHours) || 6;
|
|
896
898
|
if (hours <= 0) return;
|
|
897
899
|
const ms = hours * 3600 * 1e3;
|
|
898
900
|
setInterval(() => {
|
|
899
|
-
void import("./LogStore-
|
|
901
|
+
void import("./LogStore-Q35YFNAY.es.js").then(
|
|
900
902
|
({ getComplianceReport: getComplianceReport2 }) => getComplianceReport2($asai, "prod")
|
|
901
903
|
).catch(() => void 0);
|
|
902
904
|
}, ms);
|
|
@@ -947,16 +949,61 @@ function getWsUserinfo(req, $asai) {
|
|
|
947
949
|
}
|
|
948
950
|
}
|
|
949
951
|
__name(getWsUserinfo, "getWsUserinfo");
|
|
952
|
+
function listOnlineWsUsers($asai, hostdir) {
|
|
953
|
+
const users = [];
|
|
954
|
+
const map = $asai.connectionsws || {};
|
|
955
|
+
const dirs = hostdir ? [hostdir] : Object.keys(map);
|
|
956
|
+
for (const dir of dirs) {
|
|
957
|
+
for (const [us, ws2] of Object.entries(map[dir] || {})) {
|
|
958
|
+
if (!us || us === "asai") continue;
|
|
959
|
+
if (isWsStillActive(ws2)) {
|
|
960
|
+
users.push(us);
|
|
961
|
+
} else if (map[dir]?.[us] === ws2) {
|
|
962
|
+
delete map[dir][us];
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
return [...new Set(users)];
|
|
967
|
+
}
|
|
968
|
+
__name(listOnlineWsUsers, "listOnlineWsUsers");
|
|
950
969
|
function isUserOnlineOnWs($asai, us, hostdir) {
|
|
951
970
|
if (!us || us === "asai") return false;
|
|
952
971
|
const map = $asai.connectionsws || {};
|
|
953
972
|
const dirs = hostdir ? [hostdir] : Object.keys(map);
|
|
954
973
|
for (const dir of dirs) {
|
|
955
|
-
|
|
974
|
+
const ws2 = map[dir]?.[us];
|
|
975
|
+
if (!ws2) continue;
|
|
976
|
+
if (isWsStillActive(ws2)) return true;
|
|
977
|
+
if (map[dir]?.[us] === ws2) delete map[dir][us];
|
|
956
978
|
}
|
|
957
979
|
return false;
|
|
958
980
|
}
|
|
959
981
|
__name(isUserOnlineOnWs, "isUserOnlineOnWs");
|
|
982
|
+
function kickUserWs($asai, us, hostdir) {
|
|
983
|
+
if (!us || us === "asai") return;
|
|
984
|
+
const map = $asai.connectionsws || {};
|
|
985
|
+
const dirs = hostdir ? [hostdir] : Object.keys(map);
|
|
986
|
+
for (const dir of dirs) {
|
|
987
|
+
const ws2 = map[dir]?.[us];
|
|
988
|
+
if (!ws2) continue;
|
|
989
|
+
try {
|
|
990
|
+
ws2.close();
|
|
991
|
+
} catch {
|
|
992
|
+
}
|
|
993
|
+
if (map[dir]?.[us] === ws2) delete map[dir][us];
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
__name(kickUserWs, "kickUserWs");
|
|
997
|
+
async function removeSessionOnWsClose($asai, us, token) {
|
|
998
|
+
try {
|
|
999
|
+
const store = createUserStore($asai);
|
|
1000
|
+
const sessions = await store.getSessions();
|
|
1001
|
+
const match = sessions.find((s) => s.us === us && s.pub === token);
|
|
1002
|
+
if (match) await store.removeSession(match.id);
|
|
1003
|
+
} catch {
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
__name(removeSessionOnWsClose, "removeSessionOnWsClose");
|
|
960
1007
|
function isWsStillActive(ws2) {
|
|
961
1008
|
if (!ws2) return false;
|
|
962
1009
|
const state = ws2.readyState;
|
|
@@ -1039,8 +1086,11 @@ function isOkWs($asai, hostdir, ws2, _req, hostcfg) {
|
|
|
1039
1086
|
__name(isOkWs, "isOkWs");
|
|
1040
1087
|
function loginWs($asai, hostdir, ws2, req, hostcfg, wsWorkHandler, logWsMessage) {
|
|
1041
1088
|
const [us] = ws2.Userinfo || [];
|
|
1042
|
-
if (
|
|
1043
|
-
|
|
1089
|
+
if (!$asai.connectionsws[hostdir]) {
|
|
1090
|
+
$asai.connectionsws[hostdir] = {};
|
|
1091
|
+
}
|
|
1092
|
+
if (us && us !== "asai") {
|
|
1093
|
+
if (hostcfg.ckws && !ws2.tasksws) ws2.tasksws = {};
|
|
1044
1094
|
$asai.connectionsws[hostdir][us] = ws2;
|
|
1045
1095
|
}
|
|
1046
1096
|
ws2.on("message", (msg) => {
|
|
@@ -1048,12 +1098,14 @@ function loginWs($asai, hostdir, ws2, req, hostcfg, wsWorkHandler, logWsMessage)
|
|
|
1048
1098
|
wsWorkHandler?.(msg, ws2, hostdir, req);
|
|
1049
1099
|
});
|
|
1050
1100
|
ws2.on("close", () => {
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1101
|
+
const token = ws2.Userinfo?.[2];
|
|
1102
|
+
if (hostcfg.ckws && ws2.tasksws) {
|
|
1103
|
+
Object.values(ws2.tasksws).forEach((el) => el && clearInterval(el));
|
|
1104
|
+
ws2.tasksws = void 0;
|
|
1105
|
+
}
|
|
1106
|
+
if (us && $asai.connectionsws[hostdir]?.[us] === ws2) delete $asai.connectionsws[hostdir][us];
|
|
1107
|
+
if (us && token && !isUserOnlineOnWs($asai, us, hostdir)) {
|
|
1108
|
+
void removeSessionOnWsClose($asai, us, String(token));
|
|
1057
1109
|
}
|
|
1058
1110
|
if (ws2.readyState !== WebSocket.CLOSED && ws2.readyState !== WebSocket.CLOSING) ws2.close();
|
|
1059
1111
|
});
|
|
@@ -1406,11 +1458,11 @@ function createSkipChecker($asai) {
|
|
|
1406
1458
|
}
|
|
1407
1459
|
if (exact.size === 0 && prefixes.length === 0) return () => false;
|
|
1408
1460
|
return (url) => {
|
|
1409
|
-
const
|
|
1410
|
-
if (!
|
|
1411
|
-
if (exact.has(
|
|
1461
|
+
const path7 = normalizeRequestPath(url);
|
|
1462
|
+
if (!path7) return false;
|
|
1463
|
+
if (exact.has(path7)) return true;
|
|
1412
1464
|
for (let i = 0; i < prefixes.length; i++) {
|
|
1413
|
-
if (
|
|
1465
|
+
if (path7.startsWith(prefixes[i])) return true;
|
|
1414
1466
|
}
|
|
1415
1467
|
return false;
|
|
1416
1468
|
};
|
|
@@ -1795,12 +1847,6 @@ __name(setupHttpConnectionMonitor, "setupHttpConnectionMonitor");
|
|
|
1795
1847
|
import { execSync } from "child_process";
|
|
1796
1848
|
import process2 from "process";
|
|
1797
1849
|
var OWN_PID = process2.pid;
|
|
1798
|
-
function sleepMs(ms) {
|
|
1799
|
-
const deadline = Date.now() + ms;
|
|
1800
|
-
while (Date.now() < deadline) {
|
|
1801
|
-
}
|
|
1802
|
-
}
|
|
1803
|
-
__name(sleepMs, "sleepMs");
|
|
1804
1850
|
function findListeningPids(port) {
|
|
1805
1851
|
if (!Number.isInteger(port) || port < 1 || port > 65535) return [];
|
|
1806
1852
|
try {
|
|
@@ -1836,32 +1882,20 @@ function findListeningPids(port) {
|
|
|
1836
1882
|
}
|
|
1837
1883
|
}
|
|
1838
1884
|
__name(findListeningPids, "findListeningPids");
|
|
1839
|
-
function
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
if (process2.platform === "win32") {
|
|
1843
|
-
execSync(`taskkill /PID ${pid} /F`, { stdio: "ignore" });
|
|
1844
|
-
} else {
|
|
1845
|
-
execSync(`kill -9 ${pid}`, { stdio: "ignore" });
|
|
1846
|
-
}
|
|
1847
|
-
return true;
|
|
1848
|
-
} catch {
|
|
1849
|
-
return false;
|
|
1850
|
-
}
|
|
1885
|
+
function formatOccupants(port, exclude) {
|
|
1886
|
+
const occupants = findListeningPids(port).filter((p) => p !== exclude && p !== OWN_PID);
|
|
1887
|
+
return [...new Set(occupants)].join(", ");
|
|
1851
1888
|
}
|
|
1852
|
-
__name(
|
|
1889
|
+
__name(formatOccupants, "formatOccupants");
|
|
1853
1890
|
function releasePort(port, opts) {
|
|
1854
1891
|
const exclude = opts?.excludePid ?? OWN_PID;
|
|
1855
|
-
const occupants =
|
|
1856
|
-
if (!occupants
|
|
1892
|
+
const occupants = formatOccupants(port, exclude);
|
|
1893
|
+
if (!occupants) return true;
|
|
1857
1894
|
const label = opts?.label ? `[${opts.label}] ` : "";
|
|
1858
|
-
console.warn(
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
sleepMs(500);
|
|
1863
|
-
const remaining = findListeningPids(port).filter((p) => p !== exclude && p !== OWN_PID);
|
|
1864
|
-
return remaining.length === 0;
|
|
1895
|
+
console.warn(
|
|
1896
|
+
`[AsaiHostNodejs] ${label}\u7AEF\u53E3 ${port} \u88AB\u5360\u7528\uFF08PID: ${occupants}\uFF09\uFF0C\u8BF7\u624B\u52A8\u7ED3\u675F\u5360\u7528\u8FDB\u7A0B\u540E\u91CD\u8BD5`
|
|
1897
|
+
);
|
|
1898
|
+
return false;
|
|
1865
1899
|
}
|
|
1866
1900
|
__name(releasePort, "releasePort");
|
|
1867
1901
|
function collectStartupPorts($asai) {
|
|
@@ -2014,7 +2048,6 @@ function startHTTPServer($asai, hostdir) {
|
|
|
2014
2048
|
${err.stack}` : err);
|
|
2015
2049
|
}
|
|
2016
2050
|
});
|
|
2017
|
-
releasePort(hostcfg.port, { excludePid: process.pid, label: hostdir });
|
|
2018
2051
|
if ($asai.hostconfig.ip === "asai" || !hostcfg.ip && !$asai.hostconfig.ip) {
|
|
2019
2052
|
server.listen(hostcfg.port, () => {
|
|
2020
2053
|
console.log(`[${hostdir}] Start At ${getNowTime()}.`);
|
|
@@ -2066,19 +2099,13 @@ function asaiGuard($asai, failedHostdir = "asai") {
|
|
|
2066
2099
|
const sameProcessOwner = findSameProcessPortOwner($asai, port, failedHostdir);
|
|
2067
2100
|
if (sameProcessOwner) {
|
|
2068
2101
|
console.error(
|
|
2069
|
-
`[
|
|
2102
|
+
`[AsaiHostNodejs] \u7AEF\u53E3 ${port} \u5DF2\u88AB\u540C\u8FDB\u7A0B\u7AD9\u70B9 [${sameProcessOwner}] \u5360\u7528\uFF0C[${failedHostdir}] \u505C\u6B62\u91CD\u8BD5\uFF08\u8BF7\u68C0\u67E5 asaihost.json \u662F\u5426\u91CD\u590D\u914D\u7F6E port\uFF09`
|
|
2070
2103
|
);
|
|
2071
2104
|
return;
|
|
2072
2105
|
}
|
|
2073
|
-
|
|
2106
|
+
releasePort(port, { excludePid: process.pid, label: failedHostdir });
|
|
2074
2107
|
$asai.hostconfig.asaiport = conflictPorts.filter((p) => p !== port);
|
|
2075
|
-
|
|
2076
|
-
console.warn(`[AsaiHost] \u7AEF\u53E3 ${port} \u5DF2\u91CA\u653E\uFF0C\u6B63\u5728\u91CD\u65B0\u542F\u52A8 [${failedHostdir}]`);
|
|
2077
|
-
scheduleHttpRetry($asai, failedHostdir);
|
|
2078
|
-
return;
|
|
2079
|
-
}
|
|
2080
|
-
console.error(`[AsaiHost] \u7AEF\u53E3 ${port} \u65E0\u6CD5\u91CA\u653E\uFF0C\u542F\u52A8\u5931\u8D25\uFF08netstat -ano | findstr :${port}\uFF09`);
|
|
2081
|
-
process.exit(1);
|
|
2108
|
+
scheduleHttpRetry($asai, failedHostdir);
|
|
2082
2109
|
}
|
|
2083
2110
|
__name(asaiGuard, "asaiGuard");
|
|
2084
2111
|
|
|
@@ -2179,21 +2206,17 @@ function startProxyServer($asai, proxyhostdir) {
|
|
|
2179
2206
|
if (err.code === "EADDRINUSE") {
|
|
2180
2207
|
$asai.$log("PROXY", `\x1B[41mPort ${proxycfg.proxyport} is already start!\x1B[0m`);
|
|
2181
2208
|
delete $asai.serversproxy?.[proxyhostdir];
|
|
2182
|
-
|
|
2209
|
+
releasePort(proxycfg.proxyport, {
|
|
2183
2210
|
excludePid: process.pid,
|
|
2184
2211
|
label: proxyhostdir
|
|
2185
2212
|
});
|
|
2186
|
-
|
|
2187
|
-
console.warn(`[AsaiHost] \u4EE3\u7406\u7AEF\u53E3 ${proxycfg.proxyport} \u5DF2\u91CA\u653E\uFF0C\u6B63\u5728\u91CD\u65B0\u542F\u52A8 [${proxyhostdir}]`);
|
|
2188
|
-
setTimeout(() => startProxyServer($asai, proxyhostdir), $asai.hostconfig.tm?.d || 2e3);
|
|
2189
|
-
}
|
|
2213
|
+
setTimeout(() => startProxyServer($asai, proxyhostdir), $asai.hostconfig.tm?.d || 2e3);
|
|
2190
2214
|
} else {
|
|
2191
2215
|
$asai.$log("PROXY", "Proxy server error:", err);
|
|
2192
2216
|
}
|
|
2193
2217
|
});
|
|
2194
2218
|
if (!$asai.serversproxy) $asai.serversproxy = {};
|
|
2195
2219
|
$asai.serversproxy[proxyhostdir] = proxyServer;
|
|
2196
|
-
releasePort(proxycfg.proxyport, { excludePid: process.pid, label: proxyhostdir });
|
|
2197
2220
|
const listenIp = getHostIP(proxycfg.proxyip, $asai);
|
|
2198
2221
|
const listenCallback = /* @__PURE__ */ __name(() => {
|
|
2199
2222
|
console.log(`[${proxyhostdir}] Start At ${getNowTime()}.`);
|
|
@@ -2223,8 +2246,8 @@ function src_default($asai, opt) {
|
|
|
2223
2246
|
workHandlers.set("apiWork", opt.apiWork);
|
|
2224
2247
|
workHandlers.set("wsWork", opt.wsWork);
|
|
2225
2248
|
workHandlers.set("jsonpWork", opt.jsonpWork);
|
|
2226
|
-
function
|
|
2227
|
-
|
|
2249
|
+
function StartAsaiHostNodejs() {
|
|
2250
|
+
initAsaiHostNodejs($asai, opt);
|
|
2228
2251
|
console.log(`\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B\u203B`);
|
|
2229
2252
|
releaseConfiguredPorts($asai);
|
|
2230
2253
|
if ($asai.hostconfig?.hosts) {
|
|
@@ -2246,8 +2269,8 @@ function src_default($asai, opt) {
|
|
|
2246
2269
|
});
|
|
2247
2270
|
}
|
|
2248
2271
|
}
|
|
2249
|
-
__name(
|
|
2250
|
-
return {
|
|
2272
|
+
__name(StartAsaiHostNodejs, "StartAsaiHostNodejs");
|
|
2273
|
+
return { StartAsaiHostNodejs, asaiWs: ws };
|
|
2251
2274
|
}
|
|
2252
2275
|
__name(src_default, "default");
|
|
2253
2276
|
|
|
@@ -2319,7 +2342,7 @@ async function attachServerUserLevel(req, $asai) {
|
|
|
2319
2342
|
if (!Array.isArray(userinfo) || !userinfo[0]) return;
|
|
2320
2343
|
const us = String(userinfo[0]);
|
|
2321
2344
|
try {
|
|
2322
|
-
const { createUserStore: createUserStore2 } = await import("./user-store-
|
|
2345
|
+
const { createUserStore: createUserStore2 } = await import("./user-store-UC5URX3C.es.js");
|
|
2323
2346
|
const user = await createUserStore2($asai).findByUsername(us);
|
|
2324
2347
|
if (user && user.status === "active") {
|
|
2325
2348
|
req._serverUser = { us: user.us, lv: user.lv, status: user.status };
|
|
@@ -2350,11 +2373,11 @@ function getSecurityConfig($asai) {
|
|
|
2350
2373
|
return $asai?.hostconfig?.security || {};
|
|
2351
2374
|
}
|
|
2352
2375
|
__name(getSecurityConfig, "getSecurityConfig");
|
|
2353
|
-
function pathMatchesPattern(
|
|
2376
|
+
function pathMatchesPattern(path7, pattern) {
|
|
2354
2377
|
if (pattern.endsWith("*")) {
|
|
2355
|
-
return
|
|
2378
|
+
return path7.startsWith(pattern.slice(0, -1));
|
|
2356
2379
|
}
|
|
2357
|
-
return
|
|
2380
|
+
return path7 === pattern;
|
|
2358
2381
|
}
|
|
2359
2382
|
__name(pathMatchesPattern, "pathMatchesPattern");
|
|
2360
2383
|
function isAuthSkipped($asai, pathname) {
|
|
@@ -2502,6 +2525,54 @@ function isApiCorsAllowed(origin, hostcfg, hostHeader) {
|
|
|
2502
2525
|
__name(isApiCorsAllowed, "isApiCorsAllowed");
|
|
2503
2526
|
|
|
2504
2527
|
// src/common/server/Api.ts
|
|
2528
|
+
function readRequestBody(req, maxBody) {
|
|
2529
|
+
return new Promise((resolve6, reject) => {
|
|
2530
|
+
const chunks = [];
|
|
2531
|
+
let received = 0;
|
|
2532
|
+
let settled = false;
|
|
2533
|
+
const finish = /* @__PURE__ */ __name((err, data) => {
|
|
2534
|
+
if (settled) return;
|
|
2535
|
+
settled = true;
|
|
2536
|
+
if (err) reject(err);
|
|
2537
|
+
else resolve6(data ?? "");
|
|
2538
|
+
}, "finish");
|
|
2539
|
+
req.on("data", (chunk) => {
|
|
2540
|
+
if (settled) return;
|
|
2541
|
+
received += chunk.length;
|
|
2542
|
+
if (received > maxBody) {
|
|
2543
|
+
req.destroy();
|
|
2544
|
+
finish(new Error("PAYLOAD_TOO_LARGE"));
|
|
2545
|
+
return;
|
|
2546
|
+
}
|
|
2547
|
+
chunks.push(chunk);
|
|
2548
|
+
});
|
|
2549
|
+
req.on("end", () => finish(null, Buffer.concat(chunks).toString("utf8")));
|
|
2550
|
+
req.on("error", (err) => finish(err));
|
|
2551
|
+
});
|
|
2552
|
+
}
|
|
2553
|
+
__name(readRequestBody, "readRequestBody");
|
|
2554
|
+
function processPostPayload($asai, req, postdata, pm) {
|
|
2555
|
+
let isAes = false;
|
|
2556
|
+
let out = postdata;
|
|
2557
|
+
if ($asai.$lib.aesfns && out?.startsWith?.('{"aes":')) {
|
|
2558
|
+
isAes = true;
|
|
2559
|
+
const aesData = JSON.parse(out);
|
|
2560
|
+
out = $asai.$lib.aesfns.reqAES({ data: aesData });
|
|
2561
|
+
if (aesData?.aes === 2 && typeof out === "string") {
|
|
2562
|
+
try {
|
|
2563
|
+
out = JSON.parse(out);
|
|
2564
|
+
} catch {
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
const plainJson = typeof out === "string" && out.trimStart().startsWith("{");
|
|
2569
|
+
if (req?.Userinfo?.[2] && !plainJson) {
|
|
2570
|
+
pm = req.Userinfo[2];
|
|
2571
|
+
out = $asai.$lib.AsCode.asdecode(out, pm);
|
|
2572
|
+
}
|
|
2573
|
+
return { postdata: out, pm, isAes };
|
|
2574
|
+
}
|
|
2575
|
+
__name(processPostPayload, "processPostPayload");
|
|
2505
2576
|
var Api_default = /* @__PURE__ */ __name(($asai, opt = {}) => {
|
|
2506
2577
|
opt?.fn?.($asai);
|
|
2507
2578
|
function getHeader(req, key) {
|
|
@@ -2581,8 +2652,24 @@ var Api_default = /* @__PURE__ */ __name(($asai, opt = {}) => {
|
|
|
2581
2652
|
const releaseSlot = /* @__PURE__ */ __name(() => releaseApiSlot(hostdir, clientIp), "releaseSlot");
|
|
2582
2653
|
res.once("finish", releaseSlot);
|
|
2583
2654
|
res.once("close", releaseSlot);
|
|
2655
|
+
const maxBody = $asai.hostconfig?.maxbody || 100 * 1024 * 1024;
|
|
2656
|
+
const isTextPost = req.method === "POST" && req.url.indexOf("binary") === -1;
|
|
2657
|
+
const bodyPromise = isTextPost ? readRequestBody(req, maxBody) : null;
|
|
2584
2658
|
void (async () => {
|
|
2585
|
-
|
|
2659
|
+
let authResult;
|
|
2660
|
+
let rawBody = "";
|
|
2661
|
+
try {
|
|
2662
|
+
[authResult, rawBody] = await Promise.all([
|
|
2663
|
+
validateHttpAuthAsync(req, $asai, pathname),
|
|
2664
|
+
bodyPromise ?? Promise.resolve("")
|
|
2665
|
+
]);
|
|
2666
|
+
} catch (err) {
|
|
2667
|
+
releaseSlot();
|
|
2668
|
+
if (err?.message === "PAYLOAD_TOO_LARGE") {
|
|
2669
|
+
return sendError(res, 413, "Payload Too Large");
|
|
2670
|
+
}
|
|
2671
|
+
return sendError(res, 400, err?.message || "Bad Request");
|
|
2672
|
+
}
|
|
2586
2673
|
if (!authResult.ok) {
|
|
2587
2674
|
releaseSlot();
|
|
2588
2675
|
return sendAuthError(res, authResult);
|
|
@@ -2596,52 +2683,25 @@ var Api_default = /* @__PURE__ */ __name(($asai, opt = {}) => {
|
|
|
2596
2683
|
let qrdata;
|
|
2597
2684
|
let postdata;
|
|
2598
2685
|
if (req.url.indexOf("binary") === -1) {
|
|
2599
|
-
postdata = "";
|
|
2600
2686
|
qrdata = query;
|
|
2601
2687
|
if (req.method === "POST") {
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
}
|
|
2615
|
-
chunks.push(chunk);
|
|
2616
|
-
});
|
|
2617
|
-
req.on("end", () => {
|
|
2618
|
-
if (aborted) return;
|
|
2619
|
-
postdata = Buffer.concat(chunks).toString();
|
|
2620
|
-
isAes = $asai.$lib.aesfns && postdata?.startsWith?.('{"aes":');
|
|
2621
|
-
if (isAes) {
|
|
2622
|
-
const aesData = JSON.parse(postdata);
|
|
2623
|
-
postdata = $asai.$lib.aesfns.reqAES({ data: aesData });
|
|
2624
|
-
if (aesData?.aes === 2) {
|
|
2625
|
-
try {
|
|
2626
|
-
postdata = JSON.parse(postdata);
|
|
2627
|
-
} catch (err) {
|
|
2628
|
-
}
|
|
2629
|
-
}
|
|
2630
|
-
}
|
|
2631
|
-
if (req?.Userinfo && req?.Userinfo[2]) {
|
|
2632
|
-
pm = req.Userinfo[2];
|
|
2633
|
-
postdata = $asai.$lib.AsCode.asdecode(postdata, pm);
|
|
2634
|
-
}
|
|
2635
|
-
if (apiFn?.post && typeof apiFn?.post === "function") {
|
|
2636
|
-
const optTmp = { qrdata, data: postdata, pm };
|
|
2637
|
-
if (isAes || $asai.$lib.aesfns?.allaes) {
|
|
2638
|
-
optTmp.resAES = $asai.$lib.aesfns?.resAES;
|
|
2639
|
-
}
|
|
2640
|
-
apiFn.post(req, res, hostdir, optTmp);
|
|
2641
|
-
} else {
|
|
2642
|
-
res.end(postdata);
|
|
2688
|
+
try {
|
|
2689
|
+
const processed = processPostPayload($asai, req, rawBody, pm);
|
|
2690
|
+
postdata = processed.postdata;
|
|
2691
|
+
pm = processed.pm;
|
|
2692
|
+
isAes = processed.isAes;
|
|
2693
|
+
} catch {
|
|
2694
|
+
return sendError(res, 400, "AES decrypt failed");
|
|
2695
|
+
}
|
|
2696
|
+
if (apiFn?.post && typeof apiFn?.post === "function") {
|
|
2697
|
+
const optTmp = { qrdata, data: postdata, pm };
|
|
2698
|
+
if (isAes || $asai.$lib.aesfns?.allaes) {
|
|
2699
|
+
optTmp.resAES = $asai.$lib.aesfns?.resAES;
|
|
2643
2700
|
}
|
|
2644
|
-
|
|
2701
|
+
apiFn.post(req, res, hostdir, optTmp);
|
|
2702
|
+
} else {
|
|
2703
|
+
res.end(typeof postdata === "string" ? postdata : JSON.stringify(postdata ?? ""));
|
|
2704
|
+
}
|
|
2645
2705
|
} else if (req.method === "GET") {
|
|
2646
2706
|
if (apiFn?.get && typeof apiFn?.get === "function") {
|
|
2647
2707
|
const optTmp = { qrdata, data: qrdata, pm };
|
|
@@ -2845,7 +2905,7 @@ var WsClient_default = /* @__PURE__ */ __name(($asai, wsname) => {
|
|
|
2845
2905
|
}
|
|
2846
2906
|
__name(getKeyByData, "getKeyByData");
|
|
2847
2907
|
function clientwsInit() {
|
|
2848
|
-
return new Promise((
|
|
2908
|
+
return new Promise((resolve6, reject) => {
|
|
2849
2909
|
$asai.clientws[wsname].socket = new $asai.asaiWs(
|
|
2850
2910
|
$asai.command.commandconfig.clientws[wsname]
|
|
2851
2911
|
);
|
|
@@ -2875,7 +2935,7 @@ var WsClient_default = /* @__PURE__ */ __name(($asai, wsname) => {
|
|
|
2875
2935
|
`${wsname}=${$asai.command.commandconfig.clientws[wsname]} is open.`
|
|
2876
2936
|
);
|
|
2877
2937
|
$asai.clientws[wsname].open = 1;
|
|
2878
|
-
|
|
2938
|
+
resolve6();
|
|
2879
2939
|
};
|
|
2880
2940
|
});
|
|
2881
2941
|
}
|
|
@@ -2905,7 +2965,7 @@ var WsClient_default = /* @__PURE__ */ __name(($asai, wsname) => {
|
|
|
2905
2965
|
}
|
|
2906
2966
|
__name(clientwsOnmessage, "clientwsOnmessage");
|
|
2907
2967
|
function clientwsApi(messageData) {
|
|
2908
|
-
return new Promise((
|
|
2968
|
+
return new Promise((resolve6, reject) => {
|
|
2909
2969
|
if ($asai.clientws[wsname].open) {
|
|
2910
2970
|
try {
|
|
2911
2971
|
const taskOpt = {};
|
|
@@ -2917,7 +2977,7 @@ var WsClient_default = /* @__PURE__ */ __name(($asai, wsname) => {
|
|
|
2917
2977
|
getKeyByData(messageData),
|
|
2918
2978
|
{
|
|
2919
2979
|
callback: /* @__PURE__ */ __name((res) => {
|
|
2920
|
-
|
|
2980
|
+
resolve6(res);
|
|
2921
2981
|
}, "callback"),
|
|
2922
2982
|
...messageData,
|
|
2923
2983
|
...taskOpt
|
|
@@ -3434,7 +3494,7 @@ var unifiedlog_default = /* @__PURE__ */ __name(($asai) => {
|
|
|
3434
3494
|
if (url.startsWith("/api/asailog/store/formats/")) {
|
|
3435
3495
|
if (!await ensureLogReadAccess(req, $asai, getPermissionThreshold($asai, "logRead"))) return sendFail(res, "Forbidden", opt);
|
|
3436
3496
|
try {
|
|
3437
|
-
const { getLogFormatTaxonomy } = await import("./log-formats-
|
|
3497
|
+
const { getLogFormatTaxonomy } = await import("./log-formats-WW3TDUAK.es.js");
|
|
3438
3498
|
return sendSuccess(res, getLogFormatTaxonomy($asai), opt);
|
|
3439
3499
|
} catch (err) {
|
|
3440
3500
|
return sendFail(res, err?.message || err, opt);
|
|
@@ -3798,7 +3858,7 @@ __name(buildSessionRecord, "buildSessionRecord");
|
|
|
3798
3858
|
var API_ADMIN_PREFIXES = ["/api/user/login/admin/"];
|
|
3799
3859
|
function getMinLevelForApiPath(pathname, $asai) {
|
|
3800
3860
|
if (API_ADMIN_PREFIXES.some((p) => pathname.startsWith(p))) {
|
|
3801
|
-
if (pathname.includes("/admin/update") || pathname.includes("/admin/kick") || pathname.includes("/admin/delete")) {
|
|
3861
|
+
if (pathname.includes("/admin/update") || pathname.includes("/admin/kick") || pathname.includes("/admin/delete") || pathname.includes("/admin/resetpass")) {
|
|
3802
3862
|
return $asai ? getPermissionThreshold($asai, "adminWrite") : USER_LV.DEVELOPER;
|
|
3803
3863
|
}
|
|
3804
3864
|
return $asai ? getPermissionThreshold($asai, "adminRead") : USER_LV.ADMIN;
|
|
@@ -3811,19 +3871,98 @@ function hasLevel(userLevel, required) {
|
|
|
3811
3871
|
}
|
|
3812
3872
|
__name(hasLevel, "hasLevel");
|
|
3813
3873
|
|
|
3814
|
-
// src/sysserver/api/login/
|
|
3815
|
-
function
|
|
3816
|
-
|
|
3817
|
-
|
|
3874
|
+
// src/sysserver/api/login/service/request-body.ts
|
|
3875
|
+
function isAesEnvelope(body) {
|
|
3876
|
+
return typeof body.data === "string" && typeof body.iv === "string" && typeof body.auth === "string" && body.aes != null && body.us == null && body.pass == null && body.password == null;
|
|
3877
|
+
}
|
|
3878
|
+
__name(isAesEnvelope, "isAesEnvelope");
|
|
3879
|
+
function unwrapJsonString(value) {
|
|
3880
|
+
if (typeof value !== "string") return value;
|
|
3881
|
+
const text = value.trim();
|
|
3882
|
+
if (!text.startsWith("{") && !text.startsWith("[")) return value;
|
|
3883
|
+
try {
|
|
3884
|
+
return JSON.parse(text);
|
|
3885
|
+
} catch {
|
|
3886
|
+
return value;
|
|
3887
|
+
}
|
|
3888
|
+
}
|
|
3889
|
+
__name(unwrapJsonString, "unwrapJsonString");
|
|
3890
|
+
function parseAuthBody(opt) {
|
|
3891
|
+
let raw = opt?.data;
|
|
3892
|
+
if (raw == null || raw === "") return {};
|
|
3893
|
+
if (typeof raw === "string") {
|
|
3894
|
+
const text = raw.trim();
|
|
3895
|
+
if (!text) return {};
|
|
3818
3896
|
try {
|
|
3819
|
-
|
|
3897
|
+
raw = JSON.parse(text);
|
|
3820
3898
|
} catch {
|
|
3821
|
-
|
|
3899
|
+
if (text.includes("=") && !text.startsWith("{") && !text.startsWith("[")) {
|
|
3900
|
+
raw = Object.fromEntries(new URLSearchParams(text));
|
|
3901
|
+
} else {
|
|
3902
|
+
return {};
|
|
3903
|
+
}
|
|
3822
3904
|
}
|
|
3905
|
+
raw = unwrapJsonString(raw);
|
|
3823
3906
|
}
|
|
3824
|
-
|
|
3907
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
|
|
3908
|
+
const body = raw;
|
|
3909
|
+
if (isAesEnvelope(body)) return {};
|
|
3910
|
+
return body;
|
|
3911
|
+
}
|
|
3912
|
+
__name(parseAuthBody, "parseAuthBody");
|
|
3913
|
+
function pickAuthField(body, ...keys) {
|
|
3914
|
+
for (const key of keys) {
|
|
3915
|
+
const val = body[key];
|
|
3916
|
+
if (val != null && String(val).length > 0) return String(val);
|
|
3917
|
+
}
|
|
3918
|
+
return "";
|
|
3825
3919
|
}
|
|
3826
|
-
__name(
|
|
3920
|
+
__name(pickAuthField, "pickAuthField");
|
|
3921
|
+
|
|
3922
|
+
// src/sysserver/api/login/service/online-sessions.ts
|
|
3923
|
+
function resolveOnlineSessions($asai, hostdir, allSessions) {
|
|
3924
|
+
const onlineUsers = new Set(listOnlineWsUsers($asai, hostdir));
|
|
3925
|
+
if (!onlineUsers.size) return [];
|
|
3926
|
+
const byUser = /* @__PURE__ */ new Map();
|
|
3927
|
+
for (const s of allSessions) {
|
|
3928
|
+
if (!onlineUsers.has(s.us)) continue;
|
|
3929
|
+
const prev = byUser.get(s.us);
|
|
3930
|
+
const sTime = s.lastActivityAt ?? s.createdAt;
|
|
3931
|
+
const prevTime = prev ? prev.lastActivityAt ?? prev.createdAt : 0;
|
|
3932
|
+
if (!prev || sTime > prevTime) byUser.set(s.us, s);
|
|
3933
|
+
}
|
|
3934
|
+
return [...byUser.values()].sort((a, b) => b.createdAt - a.createdAt);
|
|
3935
|
+
}
|
|
3936
|
+
__name(resolveOnlineSessions, "resolveOnlineSessions");
|
|
3937
|
+
function isSessionUserOnline($asai, us, hostdir) {
|
|
3938
|
+
if (!us) return false;
|
|
3939
|
+
return isUserOnlineOnWs($asai, us, hostdir);
|
|
3940
|
+
}
|
|
3941
|
+
__name(isSessionUserOnline, "isSessionUserOnline");
|
|
3942
|
+
async function countOnlineSessions($asai, hostdir, store) {
|
|
3943
|
+
const all = await store.getSessions();
|
|
3944
|
+
return resolveOnlineSessions($asai, hostdir, all).length;
|
|
3945
|
+
}
|
|
3946
|
+
__name(countOnlineSessions, "countOnlineSessions");
|
|
3947
|
+
async function countOnlineSessionsByUser($asai, hostdir, store, us) {
|
|
3948
|
+
const all = await store.getSessions();
|
|
3949
|
+
return resolveOnlineSessions($asai, hostdir, all).filter((s) => s.us === us).length;
|
|
3950
|
+
}
|
|
3951
|
+
__name(countOnlineSessionsByUser, "countOnlineSessionsByUser");
|
|
3952
|
+
async function purgeOfflineSessions($asai, hostdir, store) {
|
|
3953
|
+
const onlineUsers = new Set(listOnlineWsUsers($asai, hostdir));
|
|
3954
|
+
const all = await store.getSessions();
|
|
3955
|
+
let purged = 0;
|
|
3956
|
+
for (const s of all) {
|
|
3957
|
+
if (onlineUsers.has(s.us)) continue;
|
|
3958
|
+
await store.removeSession(s.id);
|
|
3959
|
+
purged += 1;
|
|
3960
|
+
}
|
|
3961
|
+
return purged;
|
|
3962
|
+
}
|
|
3963
|
+
__name(purgeOfflineSessions, "purgeOfflineSessions");
|
|
3964
|
+
|
|
3965
|
+
// src/sysserver/api/login/index.ts
|
|
3827
3966
|
function getSecurity($asai) {
|
|
3828
3967
|
return $asai?.hostconfig?.security || {};
|
|
3829
3968
|
}
|
|
@@ -3833,16 +3972,16 @@ function getQuota2($asai) {
|
|
|
3833
3972
|
return sec.quota || {};
|
|
3834
3973
|
}
|
|
3835
3974
|
__name(getQuota2, "getQuota");
|
|
3836
|
-
async function canLogin($asai, us) {
|
|
3975
|
+
async function canLogin($asai, us, hostdir) {
|
|
3837
3976
|
const quota = getQuota2($asai);
|
|
3838
3977
|
if (!quota.enabled) return { ok: true };
|
|
3839
3978
|
const store = createUserStore($asai);
|
|
3840
|
-
const total = await store
|
|
3979
|
+
const total = await countOnlineSessions($asai, hostdir, store);
|
|
3841
3980
|
const maxLogin = quota.maxLoginSessions ?? $asai?.hostconfig?.hosts?.default?.maxonline ?? 100;
|
|
3842
3981
|
if (total >= maxLogin) {
|
|
3843
3982
|
return { ok: false, message: "LOGIN_QUOTA_FULL" };
|
|
3844
3983
|
}
|
|
3845
|
-
const perUser = await store
|
|
3984
|
+
const perUser = await countOnlineSessionsByUser($asai, hostdir, store, us);
|
|
3846
3985
|
const maxPerUser = quota.maxSessionsPerUser ?? 3;
|
|
3847
3986
|
if (perUser >= maxPerUser) {
|
|
3848
3987
|
return { ok: false, message: "USER_SESSION_LIMIT" };
|
|
@@ -3882,13 +4021,13 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
|
|
|
3882
4021
|
async function post(req, res, hostdir, opt) {
|
|
3883
4022
|
const url = req.url || "";
|
|
3884
4023
|
const ip = getClientIp(req);
|
|
3885
|
-
const body =
|
|
4024
|
+
const body = parseAuthBody(opt);
|
|
3886
4025
|
if (url.startsWith("/api/user/login/changepass/")) {
|
|
3887
4026
|
await ensureReady($asai);
|
|
3888
4027
|
try {
|
|
3889
|
-
const us = sanitizeString(body
|
|
3890
|
-
const pass = sanitizeString(body
|
|
3891
|
-
const newPass = sanitizeString(body
|
|
4028
|
+
const us = sanitizeString(pickAuthField(body, "us", "username"), 64);
|
|
4029
|
+
const pass = sanitizeString(pickAuthField(body, "pass", "password", "pwd"), 128);
|
|
4030
|
+
const newPass = sanitizeString(pickAuthField(body, "newPass", "newPassword", "newpass"), 128);
|
|
3892
4031
|
if (!us || !pass || !newPass) return sendErr(res, "AUTH_CREDENTIALS_REQUIRED", void 0, opt);
|
|
3893
4032
|
const user = await store.findByUsername(us);
|
|
3894
4033
|
if (!user || !verifyPassword(pass, user.passHash, user.passSalt)) {
|
|
@@ -3922,9 +4061,11 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
|
|
|
3922
4061
|
return sendErr(res, "AUTH_REG_DISABLED", void 0, opt);
|
|
3923
4062
|
}
|
|
3924
4063
|
try {
|
|
3925
|
-
const us = sanitizeString(body
|
|
3926
|
-
const pass = sanitizeString(body
|
|
3927
|
-
const
|
|
4064
|
+
const us = sanitizeString(pickAuthField(body, "us", "username"), 64);
|
|
4065
|
+
const pass = sanitizeString(pickAuthField(body, "pass", "password", "pwd"), 128);
|
|
4066
|
+
const emailRaw = pickAuthField(body, "email");
|
|
4067
|
+
const email = emailRaw ? sanitizeString(emailRaw, 128) : void 0;
|
|
4068
|
+
if (!us || !pass) return sendErr(res, "AUTH_CREDENTIALS_REQUIRED", void 0, opt);
|
|
3928
4069
|
const policyErr = validatePasswordChange(pass, $asai);
|
|
3929
4070
|
if (policyErr === "PASSWORD_TOO_SHORT") {
|
|
3930
4071
|
return sendErr(res, policyErr, [String(getPasswordPolicy($asai).minLength)], opt);
|
|
@@ -3953,8 +4094,8 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
|
|
|
3953
4094
|
if (url.startsWith("/api/user/login/auth/")) {
|
|
3954
4095
|
await ensureReady($asai);
|
|
3955
4096
|
try {
|
|
3956
|
-
const us = sanitizeString(body
|
|
3957
|
-
const pass = sanitizeString(body
|
|
4097
|
+
const us = sanitizeString(pickAuthField(body, "us", "username"), 64);
|
|
4098
|
+
const pass = sanitizeString(pickAuthField(body, "pass", "password", "pwd"), 128);
|
|
3958
4099
|
if (!us || !pass) return sendErr(res, "AUTH_CREDENTIALS_REQUIRED", void 0, opt);
|
|
3959
4100
|
const user = await store.findByUsername(us);
|
|
3960
4101
|
if (!user || !verifyPassword(pass, user.passHash, user.passSalt)) {
|
|
@@ -3983,17 +4124,9 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
|
|
|
3983
4124
|
const mustChange = !!user.mustChangePassword || isPasswordExpired(user.passwordChangedAt, policy.maxAgeDays);
|
|
3984
4125
|
const hostcfg = $asai.getHost?.(hostdir) || {};
|
|
3985
4126
|
if (hostcfg.ckws && isUserOnlineOnWs($asai, us, hostdir)) {
|
|
3986
|
-
|
|
3987
|
-
type: "AUTH_FAIL",
|
|
3988
|
-
path: url,
|
|
3989
|
-
method: "POST",
|
|
3990
|
-
user: us,
|
|
3991
|
-
ip,
|
|
3992
|
-
detail: "user_already_online"
|
|
3993
|
-
});
|
|
3994
|
-
return sendErr(res, "USER_ALREADY_LOGGED", void 0, opt);
|
|
4127
|
+
kickUserWs($asai, us, hostdir);
|
|
3995
4128
|
}
|
|
3996
|
-
const quotaCheck = await canLogin($asai, us);
|
|
4129
|
+
const quotaCheck = await canLogin($asai, us, hostdir);
|
|
3997
4130
|
if (!quotaCheck.ok) {
|
|
3998
4131
|
logSecurityEvent($asai, { type: "RATE_LIMIT", path: url, method: "POST", user: us, ip, detail: quotaCheck.message });
|
|
3999
4132
|
return sendErr(res, quotaCheck.message || "LOGIN_QUOTA_FULL", void 0, opt);
|
|
@@ -4037,12 +4170,17 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
|
|
|
4037
4170
|
if (!sessionId) return sendErr(res, "AUTH_CREDENTIALS_REQUIRED", void 0, opt);
|
|
4038
4171
|
const sessions = await store.getSessions();
|
|
4039
4172
|
const target = sessions.find((s) => s.id === sessionId);
|
|
4173
|
+
if (!target) return sendErr(res, "AUTH_SESSION_NOT_FOUND", void 0, opt);
|
|
4174
|
+
if (!isSessionUserOnline($asai, target.us, hostdir)) {
|
|
4175
|
+
return sendErr(res, "SESSION_NOT_ONLINE", void 0, opt);
|
|
4176
|
+
}
|
|
4177
|
+
kickUserWs($asai, target.us, hostdir);
|
|
4040
4178
|
await store.removeSession(sessionId);
|
|
4041
4179
|
const operator = String(req?.Userinfo?.[0] || "");
|
|
4042
4180
|
await logSessionTerminate($asai, {
|
|
4043
4181
|
operator,
|
|
4044
4182
|
sessionId,
|
|
4045
|
-
targetUs: target
|
|
4183
|
+
targetUs: target.us,
|
|
4046
4184
|
ip
|
|
4047
4185
|
});
|
|
4048
4186
|
return sendSuccess(res, { ok: true }, opt);
|
|
@@ -4074,10 +4212,16 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
|
|
|
4074
4212
|
const levels = Array.isArray(body.levels) ? body.levels : void 0;
|
|
4075
4213
|
const thresholds = body.thresholds && typeof body.thresholds === "object" ? body.thresholds : void 0;
|
|
4076
4214
|
const routeRights = Array.isArray(body.routeRights) ? body.routeRights : void 0;
|
|
4215
|
+
const defaultResetPassword = typeof body.defaultResetPassword === "string" ? body.defaultResetPassword : void 0;
|
|
4077
4216
|
const operator = String(req?.Userinfo?.[0] || "");
|
|
4078
4217
|
const cfg = await saveUserLevelConfig(
|
|
4079
4218
|
$asai,
|
|
4080
|
-
{
|
|
4219
|
+
{
|
|
4220
|
+
levels,
|
|
4221
|
+
thresholds,
|
|
4222
|
+
routeRights,
|
|
4223
|
+
defaultResetPassword
|
|
4224
|
+
},
|
|
4081
4225
|
operator
|
|
4082
4226
|
);
|
|
4083
4227
|
await logSecurityConfigIntervention($asai, {
|
|
@@ -4091,6 +4235,57 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
|
|
|
4091
4235
|
return sendFail(res, err?.message || err, opt);
|
|
4092
4236
|
}
|
|
4093
4237
|
}
|
|
4238
|
+
if (url.startsWith("/api/user/login/admin/resetpass/")) {
|
|
4239
|
+
await ensureReady($asai);
|
|
4240
|
+
if (!await checkAdmin(req, url, getPermissionThreshold($asai, "adminWrite"))) {
|
|
4241
|
+
logSecurityEvent($asai, { type: "ACCESS_DENIED", path: url, method: "POST", ip });
|
|
4242
|
+
return sendErr(res, "AUTH_FORBIDDEN", void 0, opt);
|
|
4243
|
+
}
|
|
4244
|
+
try {
|
|
4245
|
+
const targetUs = sanitizeString(body.us, 64);
|
|
4246
|
+
if (!targetUs) return sendErr(res, "AUTH_USER_NOT_FOUND", void 0, opt);
|
|
4247
|
+
let newPass = sanitizeString(pickAuthField(body, "newPass", "newPassword", "newpass"), 128);
|
|
4248
|
+
if (!newPass) newPass = getDefaultResetPassword($asai);
|
|
4249
|
+
const policy = getPasswordPolicy($asai);
|
|
4250
|
+
const policyErr = validatePasswordWithPolicy(newPass, policy);
|
|
4251
|
+
if (policyErr === "PASSWORD_TOO_SHORT") {
|
|
4252
|
+
return sendErr(res, policyErr, [String(policy.minLength)], opt);
|
|
4253
|
+
}
|
|
4254
|
+
if (policyErr) return sendErr(res, policyErr, void 0, opt);
|
|
4255
|
+
const user = await store.findByUsername(targetUs);
|
|
4256
|
+
if (!user) return sendErr(res, "AUTH_USER_NOT_FOUND", void 0, opt);
|
|
4257
|
+
const { passHash, passSalt } = hashPassword(newPass);
|
|
4258
|
+
const updated = await store.updateUser(targetUs, {
|
|
4259
|
+
passHash,
|
|
4260
|
+
passSalt,
|
|
4261
|
+
mustChangePassword: 1,
|
|
4262
|
+
passwordChangedAt: Date.now(),
|
|
4263
|
+
failCount: 0,
|
|
4264
|
+
status: "active",
|
|
4265
|
+
lockedUntil: void 0,
|
|
4266
|
+
passHistory: "[]"
|
|
4267
|
+
});
|
|
4268
|
+
if (!updated) return sendErr(res, "AUTH_USER_NOT_FOUND", void 0, opt);
|
|
4269
|
+
const operator = String(req?.Userinfo?.[0] || "");
|
|
4270
|
+
await logUserRbacChange($asai, {
|
|
4271
|
+
operator,
|
|
4272
|
+
targetUs,
|
|
4273
|
+
patch: { resetPassword: true, mustChangePassword: 1 },
|
|
4274
|
+
ip
|
|
4275
|
+
});
|
|
4276
|
+
logSecurityEvent($asai, {
|
|
4277
|
+
type: "AUTH_FAIL",
|
|
4278
|
+
path: url,
|
|
4279
|
+
method: "POST",
|
|
4280
|
+
user: targetUs,
|
|
4281
|
+
ip,
|
|
4282
|
+
detail: "admin_password_reset"
|
|
4283
|
+
});
|
|
4284
|
+
return sendSuccess(res, { user: publicUser($asai, updated) }, opt);
|
|
4285
|
+
} catch (err) {
|
|
4286
|
+
return sendFail(res, err?.message || err, opt);
|
|
4287
|
+
}
|
|
4288
|
+
}
|
|
4094
4289
|
if (url.startsWith("/api/user/login/admin/update/")) {
|
|
4095
4290
|
await ensureReady($asai);
|
|
4096
4291
|
if (!await checkAdmin(req, url)) {
|
|
@@ -4136,11 +4331,12 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
|
|
|
4136
4331
|
try {
|
|
4137
4332
|
const user = await store.findByUsername(String(us));
|
|
4138
4333
|
if (!user) return sendErr(res, "AUTH_USER_NOT_FOUND", void 0, opt);
|
|
4139
|
-
if (body
|
|
4140
|
-
const newPass = sanitizeString(body
|
|
4334
|
+
if (pickAuthField(body, "newPass", "newPassword", "newpass")) {
|
|
4335
|
+
const newPass = sanitizeString(pickAuthField(body, "newPass", "newPassword", "newpass"), 128);
|
|
4141
4336
|
const policy = getPasswordPolicy($asai);
|
|
4142
|
-
|
|
4143
|
-
|
|
4337
|
+
const oldPass = pickAuthField(body, "pass", "password", "pwd");
|
|
4338
|
+
if (oldPass) {
|
|
4339
|
+
if (!verifyPassword(oldPass, user.passHash, user.passSalt)) {
|
|
4144
4340
|
return sendErr(res, "AUTH_INVALID_PASSWORD", void 0, opt);
|
|
4145
4341
|
}
|
|
4146
4342
|
} else if (!user.mustChangePassword) {
|
|
@@ -4152,7 +4348,7 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
|
|
|
4152
4348
|
}
|
|
4153
4349
|
if (policyErr) return sendErr(res, policyErr, void 0, opt);
|
|
4154
4350
|
const { passHash, passSalt } = hashPassword(newPass);
|
|
4155
|
-
const passHistory = nextPassHistory(
|
|
4351
|
+
const passHistory = nextPassHistory(oldPass || newPass, user.passHistory, policy.historyCount);
|
|
4156
4352
|
await store.updateUser(String(us), {
|
|
4157
4353
|
passHash,
|
|
4158
4354
|
passSalt,
|
|
@@ -4174,6 +4370,7 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
|
|
|
4174
4370
|
const ip = getClientIp(req);
|
|
4175
4371
|
if (url.startsWith("/api/user/login/config/")) {
|
|
4176
4372
|
const lang = String(req?.headers?.["accept-language"] || opt?.headers?.["accept-language"] || "zh-CN").split(",")[0];
|
|
4373
|
+
await loadUserLevelConfig($asai);
|
|
4177
4374
|
return sendSuccess(res, getPublicSecurityConfig($asai, lang), opt);
|
|
4178
4375
|
}
|
|
4179
4376
|
if (url.startsWith("/api/user/login/admin/levels/")) {
|
|
@@ -4187,7 +4384,8 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
|
|
|
4187
4384
|
if (!await checkAdmin(req, url)) {
|
|
4188
4385
|
return sendErr(res, "AUTH_FORBIDDEN", void 0, opt);
|
|
4189
4386
|
}
|
|
4190
|
-
const
|
|
4387
|
+
const allSessions = await store.getSessions();
|
|
4388
|
+
const sessions = resolveOnlineSessions($asai, hostdir, allSessions);
|
|
4191
4389
|
return sendSuccess(
|
|
4192
4390
|
res,
|
|
4193
4391
|
{
|
|
@@ -4197,7 +4395,8 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
|
|
|
4197
4395
|
ip: s.ip,
|
|
4198
4396
|
createdAt: s.createdAt,
|
|
4199
4397
|
expiresAt: s.expiresAt,
|
|
4200
|
-
lastActivityAt: s.lastActivityAt
|
|
4398
|
+
lastActivityAt: s.lastActivityAt,
|
|
4399
|
+
online: true
|
|
4201
4400
|
}))
|
|
4202
4401
|
},
|
|
4203
4402
|
opt
|
|
@@ -4215,12 +4414,13 @@ var login_default = /* @__PURE__ */ __name(($asai) => {
|
|
|
4215
4414
|
if (!await checkAdmin(req, url)) {
|
|
4216
4415
|
return sendErr(res, "AUTH_FORBIDDEN", void 0, opt);
|
|
4217
4416
|
}
|
|
4218
|
-
|
|
4417
|
+
await purgeOfflineSessions($asai, hostdir, store);
|
|
4418
|
+
const onlineCount = await countOnlineSessions($asai, hostdir, store);
|
|
4219
4419
|
const quota = getQuota2($asai);
|
|
4220
4420
|
return sendSuccess(
|
|
4221
4421
|
res,
|
|
4222
4422
|
{
|
|
4223
|
-
sessions:
|
|
4423
|
+
sessions: onlineCount,
|
|
4224
4424
|
maxLoginSessions: quota.maxLoginSessions ?? 100,
|
|
4225
4425
|
quotaEnabled: !!quota.enabled,
|
|
4226
4426
|
http: $asai.connectionshttp,
|
|
@@ -4295,10 +4495,10 @@ var As_default = {
|
|
|
4295
4495
|
return relativePath;
|
|
4296
4496
|
},
|
|
4297
4497
|
//创建多层文件夹 同步
|
|
4298
|
-
ensureDir(
|
|
4498
|
+
ensureDir(fs12, relativePath) {
|
|
4299
4499
|
const absPathDir = this.toDirPath(relativePath);
|
|
4300
4500
|
const absPath = this.toAbsolutePath(relativePath);
|
|
4301
|
-
if (!
|
|
4501
|
+
if (!fs12.existsSync(absPath)) {
|
|
4302
4502
|
let pathTmp = "";
|
|
4303
4503
|
absPathDir.split("/").forEach((el) => {
|
|
4304
4504
|
if (el) {
|
|
@@ -4306,8 +4506,8 @@ var As_default = {
|
|
|
4306
4506
|
pathTmp += "/";
|
|
4307
4507
|
}
|
|
4308
4508
|
pathTmp += el;
|
|
4309
|
-
if (!
|
|
4310
|
-
if (!
|
|
4509
|
+
if (!fs12.existsSync(pathTmp)) {
|
|
4510
|
+
if (!fs12.mkdirSync(pathTmp, { recursive: true })) {
|
|
4311
4511
|
return absPath;
|
|
4312
4512
|
}
|
|
4313
4513
|
}
|
|
@@ -4316,11 +4516,11 @@ var As_default = {
|
|
|
4316
4516
|
}
|
|
4317
4517
|
return absPath;
|
|
4318
4518
|
},
|
|
4319
|
-
makeDir(
|
|
4320
|
-
return new Promise((
|
|
4519
|
+
makeDir(fs12, filePaths) {
|
|
4520
|
+
return new Promise((resolve6, reject) => {
|
|
4321
4521
|
try {
|
|
4322
|
-
const newPath = this.ensureDir(
|
|
4323
|
-
|
|
4522
|
+
const newPath = this.ensureDir(fs12, filePaths);
|
|
4523
|
+
resolve6(newPath);
|
|
4324
4524
|
} catch (err) {
|
|
4325
4525
|
reject(err);
|
|
4326
4526
|
}
|
|
@@ -4543,7 +4743,10 @@ var AsAES_default = /* @__PURE__ */ __name(($asai, cfg = null) => {
|
|
|
4543
4743
|
return fn(reqData);
|
|
4544
4744
|
}
|
|
4545
4745
|
} catch (error) {
|
|
4546
|
-
|
|
4746
|
+
if (validateFields(reqData, ["data", "iv", "auth"])) {
|
|
4747
|
+
throw error;
|
|
4748
|
+
}
|
|
4749
|
+
return fn(reqData);
|
|
4547
4750
|
}
|
|
4548
4751
|
}
|
|
4549
4752
|
__name(reqAES, "reqAES");
|
|
@@ -4627,6 +4830,48 @@ var AsDb_default = /* @__PURE__ */ __name(($asai, cfg) => {
|
|
|
4627
4830
|
return limittmp;
|
|
4628
4831
|
}
|
|
4629
4832
|
__name(reqlimit, "reqlimit");
|
|
4833
|
+
function decodePathSegment(seg) {
|
|
4834
|
+
try {
|
|
4835
|
+
return decodeURIComponent(seg);
|
|
4836
|
+
} catch {
|
|
4837
|
+
return seg;
|
|
4838
|
+
}
|
|
4839
|
+
}
|
|
4840
|
+
__name(decodePathSegment, "decodePathSegment");
|
|
4841
|
+
function bareFileName(name) {
|
|
4842
|
+
const base = String(name).replace(/\.json$/i, "");
|
|
4843
|
+
const slash = base.lastIndexOf("/");
|
|
4844
|
+
return slash > -1 ? base.substring(slash + 1) : base;
|
|
4845
|
+
}
|
|
4846
|
+
__name(bareFileName, "bareFileName");
|
|
4847
|
+
function parseFileBody(raw) {
|
|
4848
|
+
let body = raw;
|
|
4849
|
+
if (typeof body === "string") {
|
|
4850
|
+
try {
|
|
4851
|
+
body = JSON.parse(body);
|
|
4852
|
+
} catch {
|
|
4853
|
+
return { content: body };
|
|
4854
|
+
}
|
|
4855
|
+
}
|
|
4856
|
+
if (body && typeof body === "object" && !Array.isArray(body)) {
|
|
4857
|
+
return {
|
|
4858
|
+
name: body.name != null ? String(body.name) : void 0,
|
|
4859
|
+
content: body.content !== void 0 ? body.content : body
|
|
4860
|
+
};
|
|
4861
|
+
}
|
|
4862
|
+
return { content: body };
|
|
4863
|
+
}
|
|
4864
|
+
__name(parseFileBody, "parseFileBody");
|
|
4865
|
+
function fileContentString(content) {
|
|
4866
|
+
if (content == null) return "";
|
|
4867
|
+
if (typeof content === "string") return content;
|
|
4868
|
+
return JSON.stringify(content);
|
|
4869
|
+
}
|
|
4870
|
+
__name(fileContentString, "fileContentString");
|
|
4871
|
+
function fileKeyFromSegment(seg, bodyName) {
|
|
4872
|
+
return bareFileName(decodePathSegment(seg || bodyName || ""));
|
|
4873
|
+
}
|
|
4874
|
+
__name(fileKeyFromSegment, "fileKeyFromSegment");
|
|
4630
4875
|
function getSqlParamsByUrl(req, opt) {
|
|
4631
4876
|
let tmpSqlParams;
|
|
4632
4877
|
const reqArr = req?.url?.split("?")[0]?.split("/") || [];
|
|
@@ -4640,8 +4885,10 @@ var AsDb_default = /* @__PURE__ */ __name(($asai, cfg) => {
|
|
|
4640
4885
|
if (reqArr[4] === "insert") {
|
|
4641
4886
|
tmpSqlParams.type = reqArr[4];
|
|
4642
4887
|
if (cfg?.type === "file") {
|
|
4888
|
+
const { name: bodyName, content } = parseFileBody(opt.data);
|
|
4889
|
+
const fileKey = fileKeyFromSegment(reqArr[5], bodyName);
|
|
4643
4890
|
tmpSqlParams.field = cfg.field.data;
|
|
4644
|
-
tmpSqlParams.value = [[
|
|
4891
|
+
tmpSqlParams.value = [[fileKey, fileContentString(content)]];
|
|
4645
4892
|
} else {
|
|
4646
4893
|
tmpSqlParams.field = [];
|
|
4647
4894
|
tmpSqlParams.value = [];
|
|
@@ -4661,9 +4908,12 @@ var AsDb_default = /* @__PURE__ */ __name(($asai, cfg) => {
|
|
|
4661
4908
|
}
|
|
4662
4909
|
} else if (reqArr[4] === "update") {
|
|
4663
4910
|
tmpSqlParams.type = reqArr[4];
|
|
4664
|
-
|
|
4911
|
+
const rowKey = cfg?.type === "file" ? fileKeyFromSegment(reqArr[5]) : decodePathSegment(reqArr[5]);
|
|
4912
|
+
tmpSqlParams.where = [[cfg.field.key, "=", reqwherestr(rowKey)]];
|
|
4665
4913
|
if (cfg?.type === "file") {
|
|
4666
|
-
|
|
4914
|
+
const { content } = parseFileBody(opt.data);
|
|
4915
|
+
const contentField = cfg.field.data?.[1] || "content";
|
|
4916
|
+
tmpSqlParams.set = [[contentField, fileContentString(content)]];
|
|
4667
4917
|
} else {
|
|
4668
4918
|
tmpSqlParams.set = [];
|
|
4669
4919
|
const dbData = typeof opt.data === "string" ? JSON.parse(opt.data) : opt.data;
|
|
@@ -4679,13 +4929,15 @@ var AsDb_default = /* @__PURE__ */ __name(($asai, cfg) => {
|
|
|
4679
4929
|
}
|
|
4680
4930
|
} else if (reqArr[4] === "delete") {
|
|
4681
4931
|
tmpSqlParams.type = reqArr[4];
|
|
4682
|
-
|
|
4932
|
+
const rowKey = cfg?.type === "file" ? fileKeyFromSegment(reqArr[5]) : decodePathSegment(reqArr[5]);
|
|
4933
|
+
tmpSqlParams.where = [[cfg.field.key, "=", reqwherestr(rowKey)]];
|
|
4683
4934
|
} else {
|
|
4684
4935
|
if (cfg?.as > 1) {
|
|
4685
4936
|
tmpSqlParams.as = +cfg.as - 1;
|
|
4686
4937
|
}
|
|
4687
4938
|
tmpSqlParams.field = cfg.field.data;
|
|
4688
|
-
|
|
4939
|
+
const rowKey = cfg?.type === "file" ? fileKeyFromSegment(reqArr[5]) : decodePathSegment(reqArr[5]);
|
|
4940
|
+
tmpSqlParams.where = [[cfg.field.key, "=", reqwherestr(rowKey)]];
|
|
4689
4941
|
}
|
|
4690
4942
|
} else {
|
|
4691
4943
|
tmpSqlParams.as = cfg?.as;
|
|
@@ -4701,19 +4953,50 @@ var AsDb_default = /* @__PURE__ */ __name(($asai, cfg) => {
|
|
|
4701
4953
|
if (req?.url?.includes("/selectlist/")) {
|
|
4702
4954
|
const list = Array.isArray(resdb) ? resdb : resdb?.data;
|
|
4703
4955
|
if (Array.isArray(list)) {
|
|
4704
|
-
const
|
|
4956
|
+
const names = list.map((el) => {
|
|
4957
|
+
if (typeof el === "string") {
|
|
4958
|
+
const dot2 = el.lastIndexOf(".");
|
|
4959
|
+
return dot2 > -1 ? el.substring(0, dot2) : el;
|
|
4960
|
+
}
|
|
4705
4961
|
const rawName = el.name ?? el.file ?? "";
|
|
4706
4962
|
let tmpName = String(rawName).substring(String(rawName).lastIndexOf("/") + 1);
|
|
4707
4963
|
const dot = tmpName.lastIndexOf(".");
|
|
4708
4964
|
return dot > -1 ? tmpName.substring(0, dot) : tmpName;
|
|
4709
4965
|
});
|
|
4710
|
-
|
|
4711
|
-
resdb
|
|
4966
|
+
const children = names.map((name) => ({ name, path: name }));
|
|
4967
|
+
if (Array.isArray(resdb)) return { children };
|
|
4968
|
+
resdb.data = children;
|
|
4969
|
+
return resdb;
|
|
4712
4970
|
}
|
|
4713
4971
|
}
|
|
4714
4972
|
return resdb;
|
|
4715
4973
|
}
|
|
4716
4974
|
__name(dbFresh, "dbFresh");
|
|
4975
|
+
function normalizeSelectResult(params, resdb) {
|
|
4976
|
+
let data = resdb;
|
|
4977
|
+
if (params?.where?.length && Array.isArray(data) && data.length === 1) {
|
|
4978
|
+
data = data[0];
|
|
4979
|
+
}
|
|
4980
|
+
if (cfg?.type === "file" && data && typeof data === "object" && !Array.isArray(data)) {
|
|
4981
|
+
const nameKey = cfg.field?.data?.[0] || "name";
|
|
4982
|
+
const contentKey = cfg.field?.data?.[1] || "content";
|
|
4983
|
+
if (data[nameKey] != null) {
|
|
4984
|
+
data[nameKey] = bareFileName(String(data[nameKey]));
|
|
4985
|
+
}
|
|
4986
|
+
const raw = data[contentKey];
|
|
4987
|
+
if (typeof raw === "string") {
|
|
4988
|
+
const text = raw.trim();
|
|
4989
|
+
if (text.startsWith("{") || text.startsWith("[")) {
|
|
4990
|
+
try {
|
|
4991
|
+
data[contentKey] = JSON.parse(text);
|
|
4992
|
+
} catch {
|
|
4993
|
+
}
|
|
4994
|
+
}
|
|
4995
|
+
}
|
|
4996
|
+
}
|
|
4997
|
+
return data;
|
|
4998
|
+
}
|
|
4999
|
+
__name(normalizeSelectResult, "normalizeSelectResult");
|
|
4717
5000
|
function dbCodePm(req, resdb) {
|
|
4718
5001
|
if (req?.Userinfo && req?.Userinfo[2]) {
|
|
4719
5002
|
resdb.data = $asai.$lib.AsCode.asencode(JSON.stringify(resdb.data), req.Userinfo[2]);
|
|
@@ -4744,7 +5027,7 @@ var AsDb_default = /* @__PURE__ */ __name(($asai, cfg) => {
|
|
|
4744
5027
|
if (params && typeof DataServer.sqlDb === "function") {
|
|
4745
5028
|
try {
|
|
4746
5029
|
DataServer.sqlDb(params).then((resdb) => {
|
|
4747
|
-
let resData = dbFresh(req, resdb);
|
|
5030
|
+
let resData = normalizeSelectResult(params, dbFresh(req, resdb));
|
|
4748
5031
|
if (params?.as === 2 && params?.field?.length > 1) {
|
|
4749
5032
|
let limitArr = [];
|
|
4750
5033
|
if (params.limit?.split(",")?.length < 2) {
|
|
@@ -5055,19 +5338,31 @@ var lib_default = {
|
|
|
5055
5338
|
};
|
|
5056
5339
|
|
|
5057
5340
|
// src/sysserver/db/src/common.ts
|
|
5341
|
+
var common_exports = {};
|
|
5342
|
+
__export(common_exports, {
|
|
5343
|
+
fieldHasId: () => fieldHasId,
|
|
5344
|
+
isPathInside: () => isPathInside,
|
|
5345
|
+
isTableMissingError: () => isTableMissingError,
|
|
5346
|
+
joinTablePath: () => joinTablePath,
|
|
5347
|
+
promisifySqlDb: () => promisifySqlDb,
|
|
5348
|
+
toFieldArray: () => toFieldArray,
|
|
5349
|
+
toInsertRows: () => toInsertRows,
|
|
5350
|
+
toSetObject: () => toSetObject,
|
|
5351
|
+
toWherePairs: () => toWherePairs
|
|
5352
|
+
});
|
|
5058
5353
|
import * as path5 from "path";
|
|
5059
5354
|
function promisifySqlDb(query, sql) {
|
|
5060
|
-
return new Promise((
|
|
5355
|
+
return new Promise((resolve6, reject) => {
|
|
5061
5356
|
query(sql, (err, result) => {
|
|
5062
5357
|
if (err) {
|
|
5063
5358
|
reject(err);
|
|
5064
5359
|
return;
|
|
5065
5360
|
}
|
|
5066
5361
|
if (!result || Array.isArray(result) && result.length === 0) {
|
|
5067
|
-
|
|
5362
|
+
resolve6("");
|
|
5068
5363
|
return;
|
|
5069
5364
|
}
|
|
5070
|
-
|
|
5365
|
+
resolve6(result);
|
|
5071
5366
|
});
|
|
5072
5367
|
});
|
|
5073
5368
|
}
|
|
@@ -5077,6 +5372,48 @@ function toFieldArray(field) {
|
|
|
5077
5372
|
return typeof field === "string" ? field.split(",") : field;
|
|
5078
5373
|
}
|
|
5079
5374
|
__name(toFieldArray, "toFieldArray");
|
|
5375
|
+
function toWherePairs(where) {
|
|
5376
|
+
if (!where || !Array.isArray(where)) return [];
|
|
5377
|
+
const pairs = [];
|
|
5378
|
+
for (const item of where) {
|
|
5379
|
+
if (typeof item === "string") continue;
|
|
5380
|
+
if (Array.isArray(item) && item.length >= 3) {
|
|
5381
|
+
pairs.push([item[0], item[2]]);
|
|
5382
|
+
}
|
|
5383
|
+
}
|
|
5384
|
+
return pairs;
|
|
5385
|
+
}
|
|
5386
|
+
__name(toWherePairs, "toWherePairs");
|
|
5387
|
+
function toSetObject(set) {
|
|
5388
|
+
if (!set) return {};
|
|
5389
|
+
if (typeof set === "string") return {};
|
|
5390
|
+
const obj = {};
|
|
5391
|
+
for (const item of set) {
|
|
5392
|
+
if (typeof item === "string") continue;
|
|
5393
|
+
if (Array.isArray(item)) {
|
|
5394
|
+
if (item.length >= 3 && typeof item[1] === "string") {
|
|
5395
|
+
obj[item[0]] = item[2];
|
|
5396
|
+
} else if (item.length >= 2) {
|
|
5397
|
+
obj[item[0]] = item[1];
|
|
5398
|
+
}
|
|
5399
|
+
}
|
|
5400
|
+
}
|
|
5401
|
+
return obj;
|
|
5402
|
+
}
|
|
5403
|
+
__name(toSetObject, "toSetObject");
|
|
5404
|
+
function toInsertRows(sql) {
|
|
5405
|
+
const fields = toFieldArray(sql.field);
|
|
5406
|
+
const values = sql.value;
|
|
5407
|
+
if (!fields.length || !Array.isArray(values)) return [];
|
|
5408
|
+
return values.map((row) => {
|
|
5409
|
+
const rowObj = {};
|
|
5410
|
+
fields.forEach((field, index) => {
|
|
5411
|
+
rowObj[field] = row[index];
|
|
5412
|
+
});
|
|
5413
|
+
return rowObj;
|
|
5414
|
+
});
|
|
5415
|
+
}
|
|
5416
|
+
__name(toInsertRows, "toInsertRows");
|
|
5080
5417
|
function isPathInside(baseDir, targetPath) {
|
|
5081
5418
|
const base = path5.resolve(baseDir);
|
|
5082
5419
|
const target = path5.resolve(targetPath);
|
|
@@ -5262,936 +5599,426 @@ var Sql_default = {
|
|
|
5262
5599
|
}
|
|
5263
5600
|
};
|
|
5264
5601
|
|
|
5265
|
-
// src/sysserver/db/src/
|
|
5266
|
-
import mysql from "mysql";
|
|
5267
|
-
var DbMySQL = class {
|
|
5268
|
-
static {
|
|
5269
|
-
__name(this, "DbMySQL");
|
|
5270
|
-
}
|
|
5271
|
-
constructor(config) {
|
|
5272
|
-
this.config = config;
|
|
5273
|
-
this.pool = mysql.createPool(config);
|
|
5274
|
-
}
|
|
5275
|
-
getErr(sql, err) {
|
|
5276
|
-
if (this.config.create && isTableMissingError(err)) {
|
|
5277
|
-
this.createTable(sql);
|
|
5278
|
-
}
|
|
5279
|
-
return err?.message || null;
|
|
5280
|
-
}
|
|
5281
|
-
getResult(rows) {
|
|
5282
|
-
return rows ?? null;
|
|
5283
|
-
}
|
|
5284
|
-
query(sql, callback) {
|
|
5285
|
-
this.pool.getConnection((error, connection) => {
|
|
5286
|
-
if (error) {
|
|
5287
|
-
callback(this.getErr(sql, error), this.getResult(null));
|
|
5288
|
-
return;
|
|
5289
|
-
}
|
|
5290
|
-
connection.query(Sql_default.makeSql(sql), (err, rows) => {
|
|
5291
|
-
try {
|
|
5292
|
-
callback(this.getErr(sql, err), this.getResult(rows));
|
|
5293
|
-
} finally {
|
|
5294
|
-
connection.release();
|
|
5295
|
-
}
|
|
5296
|
-
});
|
|
5297
|
-
});
|
|
5298
|
-
}
|
|
5299
|
-
sqlDb(sql) {
|
|
5300
|
-
return promisifySqlDb(this.query.bind(this), sql);
|
|
5301
|
-
}
|
|
5302
|
-
/** 关闭连接池,释放所有连接 */
|
|
5303
|
-
close(callback) {
|
|
5304
|
-
this.pool.end(callback);
|
|
5305
|
-
}
|
|
5306
|
-
/** 表不存在时自动创建数据库与表(需 config.create 为 true) */
|
|
5307
|
-
createTable(sql) {
|
|
5308
|
-
if (!this.config.create) return;
|
|
5309
|
-
const conn = mysql.createConnection({
|
|
5310
|
-
host: this.config.host,
|
|
5311
|
-
user: this.config.user,
|
|
5312
|
-
password: this.config.password
|
|
5313
|
-
});
|
|
5314
|
-
conn.connect((err) => {
|
|
5315
|
-
if (err) {
|
|
5316
|
-
conn.end();
|
|
5317
|
-
return;
|
|
5318
|
-
}
|
|
5319
|
-
conn.query(`CREATE DATABASE IF NOT EXISTS ${this.config.database};`, (dbErr) => {
|
|
5320
|
-
if (dbErr) {
|
|
5321
|
-
conn.end();
|
|
5322
|
-
return;
|
|
5323
|
-
}
|
|
5324
|
-
this.pool = mysql.createPool(this.config);
|
|
5325
|
-
this.pool.getConnection((poolErr, connection) => {
|
|
5326
|
-
if (poolErr || !connection) {
|
|
5327
|
-
conn.end();
|
|
5328
|
-
return;
|
|
5329
|
-
}
|
|
5330
|
-
connection.query(Sql_default.createSql(sql, "mysql"), () => {
|
|
5331
|
-
connection.release();
|
|
5332
|
-
conn.end();
|
|
5333
|
-
});
|
|
5334
|
-
});
|
|
5335
|
-
});
|
|
5336
|
-
});
|
|
5337
|
-
}
|
|
5338
|
-
};
|
|
5339
|
-
var Index_default2 = DbMySQL;
|
|
5340
|
-
|
|
5341
|
-
// src/sysserver/db/src/sqlite/Index.ts
|
|
5602
|
+
// src/sysserver/db/src/file/Index.ts
|
|
5342
5603
|
import * as fs11 from "fs";
|
|
5343
5604
|
import * as path6 from "path";
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
}
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
const absolutePath = path6.resolve(config.database);
|
|
5383
|
-
const dir = path6.dirname(absolutePath);
|
|
5384
|
-
if (!fs11.existsSync(dir)) {
|
|
5385
|
-
fs11.mkdirSync(dir, { recursive: true });
|
|
5386
|
-
dbLog("MKDIR", dir);
|
|
5387
|
-
}
|
|
5388
|
-
return new Promise((resolve7, reject) => {
|
|
5389
|
-
const db = new sqlite3.Database(absolutePath, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, async (err) => {
|
|
5390
|
-
if (err) {
|
|
5391
|
-
dbLog("open failed", "[ERROR] Failed to open database:", absolutePath, err.message);
|
|
5392
|
-
reject(err);
|
|
5393
|
-
return;
|
|
5394
|
-
}
|
|
5395
|
-
try {
|
|
5396
|
-
await configureSqliteConnection(db, config.busyTimeout ?? DEFAULT_BUSY_TIMEOUT_MS);
|
|
5397
|
-
dbLog("OPEN", absolutePath);
|
|
5398
|
-
resolve7(new _PooledConnection(db, config));
|
|
5399
|
-
} catch (pragmaErr) {
|
|
5400
|
-
db.close(() => {
|
|
5401
|
-
});
|
|
5402
|
-
reject(pragmaErr);
|
|
5403
|
-
}
|
|
5404
|
-
});
|
|
5405
|
-
});
|
|
5406
|
-
}
|
|
5407
|
-
/** 测试连接是否有效(执行 SELECT 1) */
|
|
5408
|
-
async test() {
|
|
5409
|
-
return new Promise((resolve7) => {
|
|
5410
|
-
if (!this.isValid) return resolve7(false);
|
|
5411
|
-
this.db.get("SELECT 1", (err) => {
|
|
5412
|
-
this.isValid = !err;
|
|
5413
|
-
resolve7(this.isValid);
|
|
5414
|
-
});
|
|
5415
|
-
});
|
|
5416
|
-
}
|
|
5417
|
-
/** 关闭连接 */
|
|
5418
|
-
async close() {
|
|
5419
|
-
return new Promise((resolve7, reject) => {
|
|
5420
|
-
this.db.close((err) => {
|
|
5421
|
-
if (err) reject(err);
|
|
5422
|
-
else resolve7();
|
|
5423
|
-
});
|
|
5424
|
-
});
|
|
5425
|
-
}
|
|
5426
|
-
/** 标记连接为无效 */
|
|
5427
|
-
invalidate() {
|
|
5428
|
-
this.isValid = false;
|
|
5429
|
-
}
|
|
5430
|
-
};
|
|
5431
|
-
var SQLitePool = class {
|
|
5432
|
-
// 初始化完成 Promise
|
|
5433
|
-
constructor(config) {
|
|
5434
|
-
this.pool = [];
|
|
5435
|
-
this.waitingQueue = [];
|
|
5436
|
-
this.idleCheckerTimer = null;
|
|
5437
|
-
this.isDestroyed = false;
|
|
5438
|
-
this.config = {
|
|
5439
|
-
database: config.database,
|
|
5440
|
-
poolMin: config.poolMin ?? 1,
|
|
5441
|
-
poolMax: config.poolMax ?? 1,
|
|
5442
|
-
idleTimeout: config.idleTimeout ?? 3e4,
|
|
5443
|
-
acquireTimeout: config.acquireTimeout ?? DEFAULT_ACQUIRE_TIMEOUT_MS,
|
|
5444
|
-
busyTimeout: config.busyTimeout ?? DEFAULT_BUSY_TIMEOUT_MS,
|
|
5445
|
-
enableCreateTable: config.enableCreateTable ?? true,
|
|
5446
|
-
$asai: config.$asai ?? null
|
|
5447
|
-
};
|
|
5448
|
-
this.initPromise = this.initializePool();
|
|
5449
|
-
this.startIdleChecker();
|
|
5450
|
-
}
|
|
5451
|
-
static {
|
|
5452
|
-
__name(this, "SQLitePool");
|
|
5453
|
-
}
|
|
5454
|
-
/** 统一日志输出(前缀 ♣ 标识来自 SQLite 连接池模块) */
|
|
5455
|
-
dbLog(...args) {
|
|
5456
|
-
(this.config?.$asai?.$log || console.log)("sqlite", ...args);
|
|
5457
|
-
}
|
|
5458
|
-
/** 初始化连接池,创建最小连接数 */
|
|
5459
|
-
async initializePool() {
|
|
5460
|
-
const promises = [];
|
|
5461
|
-
for (let i = 0; i < this.config.poolMin; i++) {
|
|
5462
|
-
promises.push(this.createConnection());
|
|
5463
|
-
}
|
|
5464
|
-
const results = await Promise.allSettled(promises);
|
|
5465
|
-
const successCount = results.filter((r) => r.status === "fulfilled").length;
|
|
5466
|
-
if (successCount === 0) {
|
|
5467
|
-
this.dbLog("Initialization failed", "[POOL] Initialization failed: no connections created");
|
|
5468
|
-
} else {
|
|
5469
|
-
this.dbLog("Initialized", "[POOL] Initialized, size:", this.pool.length);
|
|
5470
|
-
}
|
|
5471
|
-
}
|
|
5472
|
-
/** 创建一个新连接并加入池中 */
|
|
5473
|
-
async createConnection() {
|
|
5474
|
-
if (this.pool.length >= this.config.poolMax) return;
|
|
5475
|
-
try {
|
|
5476
|
-
const conn = await PooledConnection.create(this.config);
|
|
5477
|
-
const isValid = await conn.test();
|
|
5478
|
-
if (isValid) {
|
|
5479
|
-
this.pool.push(conn);
|
|
5480
|
-
} else {
|
|
5481
|
-
await conn.close().catch(() => {
|
|
5482
|
-
});
|
|
5483
|
-
throw new Error("Connection test failed");
|
|
5605
|
+
function dbDriver(opt = {}) {
|
|
5606
|
+
const { Sql, DbCommon } = opt;
|
|
5607
|
+
const { isPathInside: isPathInside2, joinTablePath: joinTablePath2 } = DbCommon;
|
|
5608
|
+
class DbFile {
|
|
5609
|
+
static {
|
|
5610
|
+
__name(this, "DbFile");
|
|
5611
|
+
}
|
|
5612
|
+
constructor(config) {
|
|
5613
|
+
this.config = config;
|
|
5614
|
+
this.fileExt = config.ext === ".*" ? "" : config.ext || "";
|
|
5615
|
+
this.fileDel = (config.del || 0) !== 0;
|
|
5616
|
+
this.createDir = config.create || false;
|
|
5617
|
+
this.baseDirAbs = path6.resolve(process.cwd(), config.dir);
|
|
5618
|
+
}
|
|
5619
|
+
dbLog(...args) {
|
|
5620
|
+
(this.config?.$asai?.$log || console.log)("FILE", ...args);
|
|
5621
|
+
}
|
|
5622
|
+
checkFile(file) {
|
|
5623
|
+
return fs11.existsSync(this.getAbsPath(file, false));
|
|
5624
|
+
}
|
|
5625
|
+
/** 解析相对路径并限制在数据根目录内,防止路径穿越(跨平台) */
|
|
5626
|
+
resolvePath(relPath) {
|
|
5627
|
+
if (!relPath) {
|
|
5628
|
+
return this.baseDirAbs;
|
|
5629
|
+
}
|
|
5630
|
+
const abs = path6.isAbsolute(relPath) ? path6.normalize(relPath) : path6.resolve(this.baseDirAbs, relPath);
|
|
5631
|
+
if (!isPathInside2(this.baseDirAbs, abs)) {
|
|
5632
|
+
return this.baseDirAbs;
|
|
5633
|
+
}
|
|
5634
|
+
return abs;
|
|
5635
|
+
}
|
|
5636
|
+
/** 将 table 与文件名片段拼为平台无关相对路径 */
|
|
5637
|
+
tableFilePath(table, ...segments) {
|
|
5638
|
+
return joinTablePath2(table, ...segments);
|
|
5639
|
+
}
|
|
5640
|
+
normalizeRelPath(relPath) {
|
|
5641
|
+
if (!relPath) {
|
|
5642
|
+
return "";
|
|
5484
5643
|
}
|
|
5485
|
-
|
|
5486
|
-
this.dbLog("Database create failed", "[CONN] Failed to create connection:", err.message);
|
|
5487
|
-
throw err;
|
|
5644
|
+
return path6.isAbsolute(relPath) ? path6.relative(this.baseDirAbs, relPath) : relPath;
|
|
5488
5645
|
}
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
5498
|
-
if (this.isDestroyed) {
|
|
5499
|
-
throw new Error("Pool has been destroyed");
|
|
5646
|
+
ensureDir(relPath) {
|
|
5647
|
+
const relative4 = this.normalizeRelPath(relPath);
|
|
5648
|
+
const dirPath = path6.dirname(relative4);
|
|
5649
|
+
const absDir = this.resolvePath(dirPath === "." ? "" : dirPath);
|
|
5650
|
+
if (this.createDir && !fs11.existsSync(absDir)) {
|
|
5651
|
+
this.dbLog("mkdir", "[MKDIR]", absDir);
|
|
5652
|
+
fs11.mkdirSync(absDir, { recursive: true });
|
|
5653
|
+
}
|
|
5654
|
+
return absDir;
|
|
5500
5655
|
}
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
if (now - conn.lastUsed > staleCheckMs) {
|
|
5506
|
-
const isValid = await conn.test();
|
|
5507
|
-
if (!isValid) {
|
|
5508
|
-
this.removeConnection(conn);
|
|
5509
|
-
continue;
|
|
5510
|
-
}
|
|
5511
|
-
}
|
|
5512
|
-
conn.inUse = true;
|
|
5513
|
-
conn.lastUsed = now;
|
|
5514
|
-
return conn;
|
|
5656
|
+
getAbsPath(file, autoCreateDir = true) {
|
|
5657
|
+
let rel = file;
|
|
5658
|
+
if (this.fileExt && !rel.endsWith(this.fileExt)) {
|
|
5659
|
+
rel += this.fileExt;
|
|
5515
5660
|
}
|
|
5661
|
+
if (autoCreateDir) {
|
|
5662
|
+
this.ensureDir(rel);
|
|
5663
|
+
}
|
|
5664
|
+
return this.resolvePath(rel);
|
|
5516
5665
|
}
|
|
5517
|
-
|
|
5518
|
-
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
|
|
5525
|
-
|
|
5666
|
+
deldir(relativePath) {
|
|
5667
|
+
const absPath = this.resolvePath(relativePath);
|
|
5668
|
+
if (!fs11.existsSync(absPath)) {
|
|
5669
|
+
return;
|
|
5670
|
+
}
|
|
5671
|
+
this.dbLog("delete", "[RMDIR]", absPath);
|
|
5672
|
+
const entries = fs11.readdirSync(absPath, { withFileTypes: true });
|
|
5673
|
+
for (const entry of entries) {
|
|
5674
|
+
const current = path6.join(absPath, entry.name);
|
|
5675
|
+
if (entry.isDirectory()) {
|
|
5676
|
+
this.deldir(path6.join(relativePath, entry.name));
|
|
5526
5677
|
} else {
|
|
5527
|
-
|
|
5528
|
-
|
|
5678
|
+
this.dbLog("delete", "[UNLINK]", current);
|
|
5679
|
+
fs11.unlinkSync(current);
|
|
5529
5680
|
}
|
|
5530
|
-
} catch (e) {
|
|
5531
|
-
this.dbLog("create failed", "[ACQUIRE] Failed to create new connection, waiting:", e.message);
|
|
5532
5681
|
}
|
|
5682
|
+
fs11.rmdirSync(absPath);
|
|
5533
5683
|
}
|
|
5534
|
-
|
|
5535
|
-
const
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
if (!conn) return;
|
|
5548
|
-
conn.inUse = false;
|
|
5549
|
-
conn.lastUsed = Date.now();
|
|
5550
|
-
if (this.waitingQueue.length > 0) {
|
|
5551
|
-
const next = this.waitingQueue.shift();
|
|
5552
|
-
clearTimeout(next.timer);
|
|
5553
|
-
conn.inUse = true;
|
|
5554
|
-
conn.lastUsed = Date.now();
|
|
5555
|
-
next.resolve(conn);
|
|
5556
|
-
}
|
|
5557
|
-
}
|
|
5558
|
-
/** 从池中移除并关闭指定连接 */
|
|
5559
|
-
removeConnection(conn) {
|
|
5560
|
-
const index = this.pool.indexOf(conn);
|
|
5561
|
-
if (index !== -1) {
|
|
5562
|
-
this.pool.splice(index, 1);
|
|
5563
|
-
conn.close().catch(() => {
|
|
5564
|
-
});
|
|
5565
|
-
}
|
|
5566
|
-
}
|
|
5567
|
-
/** 启动空闲连接回收定时器(每 60 秒检查一次) */
|
|
5568
|
-
startIdleChecker() {
|
|
5569
|
-
this.idleCheckerTimer = setInterval(async () => {
|
|
5570
|
-
if (this.isDestroyed) return;
|
|
5571
|
-
const now = Date.now();
|
|
5572
|
-
const toRemove = [];
|
|
5573
|
-
for (const conn of this.pool) {
|
|
5574
|
-
if (!conn.inUse && now - conn.lastUsed > this.config.idleTimeout && this.pool.length > this.config.poolMin) {
|
|
5575
|
-
toRemove.push(conn);
|
|
5684
|
+
getReadPath(file, sql) {
|
|
5685
|
+
const candidates = [];
|
|
5686
|
+
if (sql?.filename) {
|
|
5687
|
+
candidates.push({ dir: path6.join(file, sql.filename) });
|
|
5688
|
+
candidates.push({ dir: path6.join(file + ".delete" + this.fileExt, sql.filename) });
|
|
5689
|
+
} else {
|
|
5690
|
+
candidates.push({ dir: file });
|
|
5691
|
+
candidates.push({ dir: file + this.fileExt + ".delete" });
|
|
5692
|
+
}
|
|
5693
|
+
for (const candidate of candidates) {
|
|
5694
|
+
const abs = this.getAbsPath(candidate.dir, false);
|
|
5695
|
+
if (fs11.existsSync(abs)) {
|
|
5696
|
+
return { path: abs, dir: candidate.dir };
|
|
5576
5697
|
}
|
|
5577
5698
|
}
|
|
5578
|
-
|
|
5579
|
-
|
|
5699
|
+
return { path: "", dir: "" };
|
|
5700
|
+
}
|
|
5701
|
+
getDelPath(file, sql) {
|
|
5702
|
+
const read2 = this.getReadPath(file, sql);
|
|
5703
|
+
if (!read2.path) {
|
|
5704
|
+
return { path: "", dir: read2.dir };
|
|
5580
5705
|
}
|
|
5581
|
-
if (
|
|
5582
|
-
|
|
5706
|
+
if (sql?.filename) {
|
|
5707
|
+
return { path: path6.dirname(read2.path), dir: path6.dirname(read2.dir) };
|
|
5583
5708
|
}
|
|
5584
|
-
|
|
5585
|
-
}
|
|
5586
|
-
/** 获取连接池当前状态 */
|
|
5587
|
-
getStatus() {
|
|
5588
|
-
const inUse = this.pool.filter((c) => c.inUse).length;
|
|
5589
|
-
return {
|
|
5590
|
-
total: this.pool.length,
|
|
5591
|
-
inUse,
|
|
5592
|
-
idle: this.pool.length - inUse,
|
|
5593
|
-
waiting: this.waitingQueue.length,
|
|
5594
|
-
min: this.config.poolMin,
|
|
5595
|
-
max: this.config.poolMax
|
|
5596
|
-
};
|
|
5597
|
-
}
|
|
5598
|
-
/** 销毁连接池,释放所有资源 */
|
|
5599
|
-
async destroy() {
|
|
5600
|
-
if (this.isDestroyed) return;
|
|
5601
|
-
this.isDestroyed = true;
|
|
5602
|
-
if (this.idleCheckerTimer) {
|
|
5603
|
-
clearInterval(this.idleCheckerTimer);
|
|
5604
|
-
this.idleCheckerTimer = null;
|
|
5605
|
-
}
|
|
5606
|
-
for (const waiter of this.waitingQueue) {
|
|
5607
|
-
clearTimeout(waiter.timer);
|
|
5608
|
-
waiter.reject(new Error("Pool has been closed"));
|
|
5709
|
+
return { path: read2.path, dir: read2.dir };
|
|
5609
5710
|
}
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
};
|
|
5616
|
-
var DbSQLite3 = class {
|
|
5617
|
-
constructor(config) {
|
|
5618
|
-
this.pool = null;
|
|
5619
|
-
this.singleDb = null;
|
|
5620
|
-
this.singleDbReady = null;
|
|
5621
|
-
/** 串行化同库 CRUD,避免 node-sqlite3 回调并发导致锁等待与连接泄漏 */
|
|
5622
|
-
this.opChain = Promise.resolve();
|
|
5623
|
-
this.config = config;
|
|
5624
|
-
const verbose = sqlite3.verbose();
|
|
5625
|
-
if (config.usePool === true) {
|
|
5626
|
-
this.pool = new SQLitePool({
|
|
5627
|
-
database: config.database,
|
|
5628
|
-
poolMin: config.poolMin ?? 1,
|
|
5629
|
-
poolMax: config.poolMax ?? 1,
|
|
5630
|
-
idleTimeout: config.idleTimeout,
|
|
5631
|
-
acquireTimeout: config.acquireTimeout ?? DEFAULT_ACQUIRE_TIMEOUT_MS,
|
|
5632
|
-
busyTimeout: config.busyTimeout ?? DEFAULT_BUSY_TIMEOUT_MS,
|
|
5633
|
-
enableCreateTable: config.create,
|
|
5634
|
-
$asai: config.$asai
|
|
5635
|
-
});
|
|
5636
|
-
} else {
|
|
5637
|
-
this.singleDbReady = this.initSingleConnection(config.database, verbose);
|
|
5711
|
+
getNewPath(file, sql) {
|
|
5712
|
+
if (sql?.filename) {
|
|
5713
|
+
return this.getAbsPath(path6.join(file, sql.filename), true);
|
|
5714
|
+
}
|
|
5715
|
+
return this.getAbsPath(file, true);
|
|
5638
5716
|
}
|
|
5639
|
-
|
|
5640
|
-
static {
|
|
5641
|
-
__name(this, "DbSQLite3");
|
|
5642
|
-
}
|
|
5643
|
-
runSerialized(task) {
|
|
5644
|
-
const run = this.opChain.then(() => task());
|
|
5645
|
-
this.opChain = run.then(
|
|
5646
|
-
() => void 0,
|
|
5647
|
-
() => void 0
|
|
5648
|
-
);
|
|
5649
|
-
return run;
|
|
5650
|
-
}
|
|
5651
|
-
dbLog(...args) {
|
|
5652
|
-
(this.config?.$asai?.$log || console.log)("sqlite", ...args);
|
|
5653
|
-
}
|
|
5654
|
-
async initSingleConnection(databasePath, verbose) {
|
|
5655
|
-
const absolutePath = path6.resolve(databasePath);
|
|
5656
|
-
const dir = path6.dirname(absolutePath);
|
|
5657
|
-
if (!fs11.existsSync(dir)) {
|
|
5658
|
-
fs11.mkdirSync(dir, { recursive: true });
|
|
5659
|
-
this.dbLog("mkdir", "[MKDIR]", dir);
|
|
5660
|
-
}
|
|
5661
|
-
return new Promise((resolve7, reject) => {
|
|
5662
|
-
this.singleDb = new verbose.Database(absolutePath, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, async (err) => {
|
|
5663
|
-
if (err) {
|
|
5664
|
-
this.dbLog("open failed", "[ERROR] Failed to open single connection:", absolutePath, err.message);
|
|
5665
|
-
reject(err);
|
|
5666
|
-
return;
|
|
5667
|
-
}
|
|
5668
|
-
try {
|
|
5669
|
-
await configureSqliteConnection(this.singleDb, this.config.busyTimeout ?? DEFAULT_BUSY_TIMEOUT_MS);
|
|
5670
|
-
this.dbLog("open", "[OPEN] Single connection:", absolutePath);
|
|
5671
|
-
resolve7();
|
|
5672
|
-
} catch (pragmaErr) {
|
|
5673
|
-
this.singleDb.close(() => {
|
|
5674
|
-
});
|
|
5675
|
-
reject(pragmaErr);
|
|
5676
|
-
}
|
|
5677
|
-
});
|
|
5678
|
-
});
|
|
5679
|
-
}
|
|
5680
|
-
getErr(_sql, err) {
|
|
5681
|
-
return err?.message || null;
|
|
5682
|
-
}
|
|
5683
|
-
getResult(rows) {
|
|
5684
|
-
return rows ?? "OK";
|
|
5685
|
-
}
|
|
5686
|
-
/**
|
|
5687
|
-
* 执行 SQL 查询(回调风格)
|
|
5688
|
-
*/
|
|
5689
|
-
query(sql, callback) {
|
|
5690
|
-
const sqlStr = Sql_default.makeSql(sql);
|
|
5691
|
-
const executeQuery = /* @__PURE__ */ __name((db, release) => {
|
|
5692
|
-
const finish = /* @__PURE__ */ __name((err, rows) => {
|
|
5693
|
-
release?.();
|
|
5694
|
-
callback(this.getErr(sql, err), this.getResult(rows));
|
|
5695
|
-
}, "finish");
|
|
5717
|
+
write(file, data, sql) {
|
|
5696
5718
|
try {
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
finish(err, "OK");
|
|
5702
|
-
});
|
|
5703
|
-
}
|
|
5719
|
+
const filePath = sql?.type === "insert" ? this.getNewPath(file, sql) : this.getReadPath(file, sql).path || this.getNewPath(file, sql);
|
|
5720
|
+
this.dbLog("write", "[WRITE]", filePath);
|
|
5721
|
+
fs11.writeFileSync(filePath, data, "utf8");
|
|
5722
|
+
return "ok";
|
|
5704
5723
|
} catch (e) {
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
}
|
|
5708
|
-
}, "executeQuery");
|
|
5709
|
-
if (this.pool) {
|
|
5710
|
-
this.pool.acquire().then((conn) => {
|
|
5711
|
-
executeQuery(conn.db, () => this.pool.release(conn));
|
|
5712
|
-
}).catch((err) => {
|
|
5713
|
-
callback(this.getErr(sql, err), this.getResult("pool error"));
|
|
5714
|
-
});
|
|
5715
|
-
} else {
|
|
5716
|
-
(this.singleDbReady || Promise.resolve()).then(() => {
|
|
5717
|
-
executeQuery(this.singleDb);
|
|
5718
|
-
}).catch((err) => {
|
|
5719
|
-
callback(this.getErr(sql, err), this.getResult("connection error"));
|
|
5720
|
-
});
|
|
5721
|
-
}
|
|
5722
|
-
}
|
|
5723
|
-
/** 执行 SQL 操作(Promise 风格,串行 + 缺表时自动建表并重试一次) */
|
|
5724
|
-
sqlDb(sql) {
|
|
5725
|
-
return this.runSerialized(() => this.executeSqlDb(sql));
|
|
5726
|
-
}
|
|
5727
|
-
async executeSqlDb(sql) {
|
|
5728
|
-
try {
|
|
5729
|
-
return await promisifySqlDb(this.query.bind(this), sql);
|
|
5730
|
-
} catch (err) {
|
|
5731
|
-
const errObj = { message: String(err?.message || err || "") };
|
|
5732
|
-
if (this.config.create && sql.type === "insert" && isTableMissingError(errObj)) {
|
|
5733
|
-
await this.createTable(sql);
|
|
5734
|
-
return promisifySqlDb(this.query.bind(this), sql);
|
|
5724
|
+
this.dbLog("write failed", "[WRITE ERROR]", e);
|
|
5725
|
+
return e;
|
|
5735
5726
|
}
|
|
5736
|
-
throw err;
|
|
5737
5727
|
}
|
|
5738
|
-
|
|
5739
|
-
/**
|
|
5740
|
-
* 自动建表(当表不存在时触发)
|
|
5741
|
-
*/
|
|
5742
|
-
async createTable(sql) {
|
|
5743
|
-
const createSql = Sql_default.createSql(sql, "sqlite");
|
|
5744
|
-
if (!createSql) return;
|
|
5745
|
-
if (this.pool) {
|
|
5746
|
-
let conn = null;
|
|
5728
|
+
read(file, sql, encoding = "utf8") {
|
|
5747
5729
|
try {
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5730
|
+
const read2 = this.getReadPath(file, sql);
|
|
5731
|
+
if (!read2.path) {
|
|
5732
|
+
return { error: "empty" };
|
|
5733
|
+
}
|
|
5734
|
+
this.dbLog("read", "[READ]", read2.path);
|
|
5735
|
+
const data = fs11.readFileSync(read2.path, { encoding }) || "";
|
|
5736
|
+
const fileName = this.fileExt && read2.dir.endsWith(this.fileExt) ? read2.dir : read2.dir + this.fileExt;
|
|
5737
|
+
return [{ data, file: fileName }];
|
|
5756
5738
|
} catch (e) {
|
|
5757
|
-
this.dbLog("
|
|
5758
|
-
|
|
5759
|
-
if (conn) this.pool.release(conn);
|
|
5760
|
-
}
|
|
5761
|
-
} else {
|
|
5762
|
-
await this.singleDbReady;
|
|
5763
|
-
await new Promise((resolve7, reject) => {
|
|
5764
|
-
this.singleDb.run(createSql, (err) => {
|
|
5765
|
-
if (err) reject(err);
|
|
5766
|
-
else resolve7();
|
|
5767
|
-
});
|
|
5768
|
-
});
|
|
5769
|
-
}
|
|
5770
|
-
}
|
|
5771
|
-
/** 获取连接池状态(仅连接池模式有效) */
|
|
5772
|
-
getPoolStatus() {
|
|
5773
|
-
if (this.pool) {
|
|
5774
|
-
return this.pool.getStatus();
|
|
5775
|
-
}
|
|
5776
|
-
return { mode: "single-connection" };
|
|
5777
|
-
}
|
|
5778
|
-
/** 关闭所有连接 */
|
|
5779
|
-
async close() {
|
|
5780
|
-
if (this.pool) {
|
|
5781
|
-
await this.pool.destroy();
|
|
5782
|
-
} else if (this.singleDb) {
|
|
5783
|
-
await new Promise((resolve7, reject) => {
|
|
5784
|
-
this.singleDb.close((err) => {
|
|
5785
|
-
if (err) reject(err);
|
|
5786
|
-
else resolve7();
|
|
5787
|
-
});
|
|
5788
|
-
});
|
|
5789
|
-
}
|
|
5790
|
-
}
|
|
5791
|
-
};
|
|
5792
|
-
var Index_default3 = DbSQLite3;
|
|
5793
|
-
|
|
5794
|
-
// src/sysserver/db/src/file/Index.ts
|
|
5795
|
-
import * as fs12 from "fs";
|
|
5796
|
-
import * as path7 from "path";
|
|
5797
|
-
var DbFile = class {
|
|
5798
|
-
static {
|
|
5799
|
-
__name(this, "DbFile");
|
|
5800
|
-
}
|
|
5801
|
-
constructor(config) {
|
|
5802
|
-
this.config = config;
|
|
5803
|
-
this.fileExt = config.ext === ".*" ? "" : config.ext || "";
|
|
5804
|
-
this.fileDel = (config.del || 0) !== 0;
|
|
5805
|
-
this.createDir = config.create || false;
|
|
5806
|
-
this.baseDirAbs = path7.resolve(process.cwd(), config.dir);
|
|
5807
|
-
}
|
|
5808
|
-
dbLog(...args) {
|
|
5809
|
-
(this.config?.$asai?.$log || console.log)("FILE", ...args);
|
|
5810
|
-
}
|
|
5811
|
-
checkFile(file) {
|
|
5812
|
-
return fs12.existsSync(this.getAbsPath(file, false));
|
|
5813
|
-
}
|
|
5814
|
-
/** 解析相对路径并限制在数据根目录内,防止路径穿越(跨平台) */
|
|
5815
|
-
resolvePath(relPath) {
|
|
5816
|
-
if (!relPath) {
|
|
5817
|
-
return this.baseDirAbs;
|
|
5818
|
-
}
|
|
5819
|
-
const abs = path7.isAbsolute(relPath) ? path7.normalize(relPath) : path7.resolve(this.baseDirAbs, relPath);
|
|
5820
|
-
if (!isPathInside(this.baseDirAbs, abs)) {
|
|
5821
|
-
return this.baseDirAbs;
|
|
5822
|
-
}
|
|
5823
|
-
return abs;
|
|
5824
|
-
}
|
|
5825
|
-
/** 将 table 与文件名片段拼为平台无关相对路径 */
|
|
5826
|
-
tableFilePath(table, ...segments) {
|
|
5827
|
-
return joinTablePath(table, ...segments);
|
|
5828
|
-
}
|
|
5829
|
-
normalizeRelPath(relPath) {
|
|
5830
|
-
if (!relPath) {
|
|
5831
|
-
return "";
|
|
5832
|
-
}
|
|
5833
|
-
return path7.isAbsolute(relPath) ? path7.relative(this.baseDirAbs, relPath) : relPath;
|
|
5834
|
-
}
|
|
5835
|
-
ensureDir(relPath) {
|
|
5836
|
-
const relative4 = this.normalizeRelPath(relPath);
|
|
5837
|
-
const dirPath = path7.dirname(relative4);
|
|
5838
|
-
const absDir = this.resolvePath(dirPath === "." ? "" : dirPath);
|
|
5839
|
-
if (this.createDir && !fs12.existsSync(absDir)) {
|
|
5840
|
-
this.dbLog("mkdir", "[MKDIR]", absDir);
|
|
5841
|
-
fs12.mkdirSync(absDir, { recursive: true });
|
|
5842
|
-
}
|
|
5843
|
-
return absDir;
|
|
5844
|
-
}
|
|
5845
|
-
getAbsPath(file, autoCreateDir = true) {
|
|
5846
|
-
let rel = file;
|
|
5847
|
-
if (this.fileExt && !rel.endsWith(this.fileExt)) {
|
|
5848
|
-
rel += this.fileExt;
|
|
5849
|
-
}
|
|
5850
|
-
if (autoCreateDir) {
|
|
5851
|
-
this.ensureDir(rel);
|
|
5852
|
-
}
|
|
5853
|
-
return this.resolvePath(rel);
|
|
5854
|
-
}
|
|
5855
|
-
deldir(relativePath) {
|
|
5856
|
-
const absPath = this.resolvePath(relativePath);
|
|
5857
|
-
if (!fs12.existsSync(absPath)) {
|
|
5858
|
-
return;
|
|
5859
|
-
}
|
|
5860
|
-
this.dbLog("delete", "[RMDIR]", absPath);
|
|
5861
|
-
const entries = fs12.readdirSync(absPath, { withFileTypes: true });
|
|
5862
|
-
for (const entry of entries) {
|
|
5863
|
-
const current = path7.join(absPath, entry.name);
|
|
5864
|
-
if (entry.isDirectory()) {
|
|
5865
|
-
this.deldir(path7.join(relativePath, entry.name));
|
|
5866
|
-
} else {
|
|
5867
|
-
this.dbLog("delete", "[UNLINK]", current);
|
|
5868
|
-
fs12.unlinkSync(current);
|
|
5739
|
+
this.dbLog("read failed", "[READ ERROR]", e);
|
|
5740
|
+
return e;
|
|
5869
5741
|
}
|
|
5870
5742
|
}
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
const read2 = this.getReadPath(file, sql);
|
|
5892
|
-
if (!read2.path) {
|
|
5893
|
-
return { path: "", dir: read2.dir };
|
|
5894
|
-
}
|
|
5895
|
-
if (sql?.filename) {
|
|
5896
|
-
return { path: path7.dirname(read2.path), dir: path7.dirname(read2.dir) };
|
|
5897
|
-
}
|
|
5898
|
-
return { path: read2.path, dir: read2.dir };
|
|
5899
|
-
}
|
|
5900
|
-
getNewPath(file, sql) {
|
|
5901
|
-
if (sql?.filename) {
|
|
5902
|
-
return this.getAbsPath(path7.join(file, sql.filename), true);
|
|
5903
|
-
}
|
|
5904
|
-
return this.getAbsPath(file, true);
|
|
5905
|
-
}
|
|
5906
|
-
write(file, data, sql) {
|
|
5907
|
-
try {
|
|
5908
|
-
const filePath = sql?.type === "insert" ? this.getNewPath(file, sql) : this.getReadPath(file, sql).path || this.getNewPath(file, sql);
|
|
5909
|
-
this.dbLog("write", "[WRITE]", filePath);
|
|
5910
|
-
fs12.writeFileSync(filePath, data, "utf8");
|
|
5911
|
-
return "ok";
|
|
5912
|
-
} catch (e) {
|
|
5913
|
-
this.dbLog("write failed", "[WRITE ERROR]", e);
|
|
5914
|
-
return e;
|
|
5915
|
-
}
|
|
5916
|
-
}
|
|
5917
|
-
read(file, sql, encoding = "utf8") {
|
|
5918
|
-
try {
|
|
5919
|
-
const read2 = this.getReadPath(file, sql);
|
|
5920
|
-
if (!read2.path) {
|
|
5921
|
-
return { error: "empty" };
|
|
5922
|
-
}
|
|
5923
|
-
this.dbLog("read", "[READ]", read2.path);
|
|
5924
|
-
const data = fs12.readFileSync(read2.path, { encoding }) || "";
|
|
5925
|
-
const fileName = this.fileExt && read2.dir.endsWith(this.fileExt) ? read2.dir : read2.dir + this.fileExt;
|
|
5926
|
-
return [{ data, file: fileName }];
|
|
5927
|
-
} catch (e) {
|
|
5928
|
-
this.dbLog("read failed", "[READ ERROR]", e);
|
|
5929
|
-
return e;
|
|
5930
|
-
}
|
|
5931
|
-
}
|
|
5932
|
-
del(file, sql) {
|
|
5933
|
-
try {
|
|
5934
|
-
const del = this.getDelPath(file, sql);
|
|
5935
|
-
if (!del.path) {
|
|
5936
|
-
return "";
|
|
5937
|
-
}
|
|
5938
|
-
if (this.fileDel && sql?.deltrue) {
|
|
5939
|
-
const target = del.path + ".delete" + this.fileExt;
|
|
5940
|
-
this.dbLog("soft delete", "[SOFT DELETE]", `${del.path} -> ${target}`);
|
|
5941
|
-
fs12.renameSync(del.path, target);
|
|
5942
|
-
} else if (sql?.filename) {
|
|
5943
|
-
this.deldir(del.dir);
|
|
5944
|
-
} else {
|
|
5945
|
-
this.dbLog("delete", "[DELETE]", del.path);
|
|
5946
|
-
fs12.unlinkSync(del.path);
|
|
5743
|
+
del(file, sql) {
|
|
5744
|
+
try {
|
|
5745
|
+
const del = this.getDelPath(file, sql);
|
|
5746
|
+
if (!del.path) {
|
|
5747
|
+
return "";
|
|
5748
|
+
}
|
|
5749
|
+
if (this.fileDel && sql?.deltrue) {
|
|
5750
|
+
const target = del.path + ".delete" + this.fileExt;
|
|
5751
|
+
this.dbLog("soft delete", "[SOFT DELETE]", `${del.path} -> ${target}`);
|
|
5752
|
+
fs11.renameSync(del.path, target);
|
|
5753
|
+
} else if (sql?.filename) {
|
|
5754
|
+
this.deldir(del.dir);
|
|
5755
|
+
} else {
|
|
5756
|
+
this.dbLog("delete", "[DELETE]", del.path);
|
|
5757
|
+
fs11.unlinkSync(del.path);
|
|
5758
|
+
}
|
|
5759
|
+
return "ok";
|
|
5760
|
+
} catch (e) {
|
|
5761
|
+
this.dbLog("delete failed", "[DELETE ERROR]", e);
|
|
5762
|
+
return e;
|
|
5947
5763
|
}
|
|
5948
|
-
return "ok";
|
|
5949
|
-
} catch (e) {
|
|
5950
|
-
this.dbLog("delete failed", "[DELETE ERROR]", e);
|
|
5951
|
-
return e;
|
|
5952
5764
|
}
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
return "";
|
|
5959
|
-
}
|
|
5960
|
-
if (this.fileDel) {
|
|
5961
|
-
const extPattern = this.fileExt ? this.fileExt.replace(/\./g, "\\.") : "";
|
|
5962
|
-
const original = del.path.replace(new RegExp(`.delete${extPattern}$`), "");
|
|
5963
|
-
if (original !== del.path) {
|
|
5964
|
-
this.dbLog("restore", "[RESTORE]", `${del.path} -> ${original}`);
|
|
5965
|
-
fs12.renameSync(del.path, original);
|
|
5765
|
+
re(file, sql) {
|
|
5766
|
+
try {
|
|
5767
|
+
const del = this.getDelPath(file, sql);
|
|
5768
|
+
if (!del.path) {
|
|
5769
|
+
return "";
|
|
5966
5770
|
}
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5771
|
+
if (this.fileDel) {
|
|
5772
|
+
const extPattern = this.fileExt ? this.fileExt.replace(/\./g, "\\.") : "";
|
|
5773
|
+
const original = del.path.replace(new RegExp(`.delete${extPattern}$`), "");
|
|
5774
|
+
if (original !== del.path) {
|
|
5775
|
+
this.dbLog("restore", "[RESTORE]", `${del.path} -> ${original}`);
|
|
5776
|
+
fs11.renameSync(del.path, original);
|
|
5777
|
+
}
|
|
5778
|
+
} else {
|
|
5779
|
+
this.dbLog("force delete", "[FORCE DELETE]", del.path);
|
|
5780
|
+
fs11.unlinkSync(del.path);
|
|
5781
|
+
}
|
|
5782
|
+
return "ok";
|
|
5783
|
+
} catch (e) {
|
|
5784
|
+
this.dbLog("restore failed", "[RESTORE ERROR]", e);
|
|
5785
|
+
return e;
|
|
5970
5786
|
}
|
|
5971
|
-
return "ok";
|
|
5972
|
-
} catch (e) {
|
|
5973
|
-
this.dbLog("restore failed", "[RESTORE ERROR]", e);
|
|
5974
|
-
return e;
|
|
5975
5787
|
}
|
|
5976
|
-
|
|
5977
|
-
list(dir, sql) {
|
|
5978
|
-
try {
|
|
5979
|
-
const absDir = this.resolvePath(dir);
|
|
5980
|
-
this.dbLog("list", "[LIST]", absDir);
|
|
5981
|
-
let entries;
|
|
5788
|
+
list(dir, sql) {
|
|
5982
5789
|
try {
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
}
|
|
5987
|
-
const result = { open: [], delete: [] };
|
|
5988
|
-
const deleteSuffix = ".delete" + this.fileExt;
|
|
5989
|
-
for (const entry of entries) {
|
|
5990
|
-
if (!entry.isFile()) continue;
|
|
5991
|
-
const fileName = entry.name;
|
|
5992
|
-
const isDelete = fileName.endsWith(deleteSuffix);
|
|
5993
|
-
const baseName = isDelete ? fileName.slice(0, -deleteSuffix.length) : fileName;
|
|
5994
|
-
if (this.fileExt && !baseName.endsWith(this.fileExt)) continue;
|
|
5995
|
-
const fullPath = path7.join(absDir, fileName);
|
|
5790
|
+
const absDir = this.resolvePath(dir);
|
|
5791
|
+
this.dbLog("list", "[LIST]", absDir);
|
|
5792
|
+
let entries;
|
|
5996
5793
|
try {
|
|
5997
|
-
|
|
5998
|
-
const entryData = { data, file: baseName };
|
|
5999
|
-
if (isDelete) {
|
|
6000
|
-
if (sql?.list) result.delete.push(entryData);
|
|
6001
|
-
} else {
|
|
6002
|
-
result.open.push(entryData);
|
|
6003
|
-
}
|
|
5794
|
+
entries = fs11.readdirSync(absDir, { withFileTypes: true });
|
|
6004
5795
|
} catch {
|
|
5796
|
+
return sql?.list ? { open: [], delete: [] } : [];
|
|
5797
|
+
}
|
|
5798
|
+
const result = { open: [], delete: [] };
|
|
5799
|
+
const deleteSuffix = ".delete" + this.fileExt;
|
|
5800
|
+
for (const entry of entries) {
|
|
5801
|
+
if (!entry.isFile()) continue;
|
|
5802
|
+
const fileName = entry.name;
|
|
5803
|
+
const isDelete = fileName.endsWith(deleteSuffix);
|
|
5804
|
+
const baseName = isDelete ? fileName.slice(0, -deleteSuffix.length) : fileName;
|
|
5805
|
+
if (this.fileExt && !baseName.endsWith(this.fileExt)) continue;
|
|
5806
|
+
const fullPath = path6.join(absDir, fileName);
|
|
5807
|
+
try {
|
|
5808
|
+
const data = fs11.readFileSync(fullPath, "utf8");
|
|
5809
|
+
const entryData = { data, file: baseName };
|
|
5810
|
+
if (isDelete) {
|
|
5811
|
+
if (sql?.list) result.delete.push(entryData);
|
|
5812
|
+
} else {
|
|
5813
|
+
result.open.push(entryData);
|
|
5814
|
+
}
|
|
5815
|
+
} catch {
|
|
5816
|
+
}
|
|
6005
5817
|
}
|
|
5818
|
+
return sql?.list ? result : result.open;
|
|
5819
|
+
} catch (e) {
|
|
5820
|
+
this.dbLog("list failed", "[LIST ERROR]", e);
|
|
5821
|
+
return e;
|
|
6006
5822
|
}
|
|
6007
|
-
return sql?.list ? result : result.open;
|
|
6008
|
-
} catch (e) {
|
|
6009
|
-
this.dbLog("list failed", "[LIST ERROR]", e);
|
|
6010
|
-
return e;
|
|
6011
5823
|
}
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
5824
|
+
/** 从 where 首条条件中提取文件名(去掉 SQL 风格引号) */
|
|
5825
|
+
getFileNameFromWhere(where) {
|
|
5826
|
+
if (!where || !Array.isArray(where)) return "";
|
|
5827
|
+
const first = where.find((item) => Array.isArray(item));
|
|
5828
|
+
const raw = first?.[2];
|
|
5829
|
+
if (typeof raw === "string") {
|
|
5830
|
+
return raw.replace(/^'+|'+$/g, "");
|
|
5831
|
+
}
|
|
5832
|
+
return String(raw ?? "");
|
|
6020
5833
|
}
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
result = this.write(this.tableFilePath(sql.table, file), data, sql);
|
|
6031
|
-
} else if (sql.type === "delete") {
|
|
6032
|
-
const file = this.getFileNameFromWhere(sql.where);
|
|
6033
|
-
result = this.del(this.tableFilePath(sql.table, file), sql);
|
|
6034
|
-
} else if (sql.type === "re") {
|
|
6035
|
-
const file = this.getFileNameFromWhere(sql.where);
|
|
6036
|
-
result = this.re(this.tableFilePath(sql.table, file), sql);
|
|
6037
|
-
} else if (sql.type === "update") {
|
|
6038
|
-
const file = this.getFileNameFromWhere(sql.where);
|
|
6039
|
-
const data = sql.set[0][1];
|
|
6040
|
-
result = this.write(this.tableFilePath(sql.table, file), data, sql);
|
|
6041
|
-
} else if (sql.type === "select") {
|
|
6042
|
-
if (sql?.where?.length && sql?.as !== 2) {
|
|
5834
|
+
sqlDb(sql) {
|
|
5835
|
+
return new Promise((resolve6, reject) => {
|
|
5836
|
+
try {
|
|
5837
|
+
let result;
|
|
5838
|
+
const tableDir = this.tableFilePath(sql.table);
|
|
5839
|
+
if (sql.type === "insert") {
|
|
5840
|
+
const [file, data] = sql.value[0];
|
|
5841
|
+
result = this.write(this.tableFilePath(sql.table, file), data, sql);
|
|
5842
|
+
} else if (sql.type === "delete") {
|
|
6043
5843
|
const file = this.getFileNameFromWhere(sql.where);
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
5844
|
+
result = this.del(this.tableFilePath(sql.table, file), sql);
|
|
5845
|
+
} else if (sql.type === "re") {
|
|
5846
|
+
const file = this.getFileNameFromWhere(sql.where);
|
|
5847
|
+
result = this.re(this.tableFilePath(sql.table, file), sql);
|
|
5848
|
+
} else if (sql.type === "update") {
|
|
5849
|
+
const file = this.getFileNameFromWhere(sql.where);
|
|
5850
|
+
const data = sql.set[0][1];
|
|
5851
|
+
result = this.write(this.tableFilePath(sql.table, file), data, sql);
|
|
5852
|
+
} else if (sql.type === "select") {
|
|
5853
|
+
if (sql?.where?.length && sql?.as !== 2) {
|
|
5854
|
+
const file = this.getFileNameFromWhere(sql.where);
|
|
5855
|
+
const readRes = this.read(this.tableFilePath(sql.table, file), sql);
|
|
5856
|
+
const resObj = {};
|
|
5857
|
+
if (readRes?.[0]?.file && !readRes.error) {
|
|
5858
|
+
if (sql.as === 1) {
|
|
5859
|
+
try {
|
|
5860
|
+
const parsed = JSON.parse(readRes[0].data);
|
|
5861
|
+
if (sql.field?.length) {
|
|
5862
|
+
sql.field.forEach((f) => {
|
|
5863
|
+
resObj[f] = parsed[f] ?? "";
|
|
5864
|
+
});
|
|
5865
|
+
} else {
|
|
5866
|
+
Object.assign(resObj, parsed);
|
|
5867
|
+
}
|
|
5868
|
+
} catch {
|
|
5869
|
+
}
|
|
5870
|
+
} else {
|
|
5871
|
+
const fields = sql.field;
|
|
5872
|
+
resObj[fields[0]] = readRes[0].file;
|
|
5873
|
+
if (fields[1]) {
|
|
5874
|
+
resObj[fields[1]] = readRes[0].data;
|
|
6056
5875
|
}
|
|
6057
|
-
} catch {
|
|
6058
|
-
}
|
|
6059
|
-
} else {
|
|
6060
|
-
const fields = sql.field;
|
|
6061
|
-
resObj[fields[0]] = readRes[0].file;
|
|
6062
|
-
if (fields[1]) {
|
|
6063
|
-
resObj[fields[1]] = readRes[0].data;
|
|
6064
5876
|
}
|
|
6065
5877
|
}
|
|
6066
|
-
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
|
|
5878
|
+
result = [resObj];
|
|
5879
|
+
} else {
|
|
5880
|
+
const listRes = this.list(tableDir, sql);
|
|
5881
|
+
if (listRes && !listRes.error) {
|
|
5882
|
+
if (sql?.list) {
|
|
5883
|
+
const mapEntry = /* @__PURE__ */ __name((el) => {
|
|
5884
|
+
const fields = sql.field;
|
|
5885
|
+
const item = { [fields[0]]: el.file };
|
|
5886
|
+
if (fields[1]) item[fields[1]] = el.data;
|
|
5887
|
+
return item;
|
|
5888
|
+
}, "mapEntry");
|
|
5889
|
+
if (listRes.open) listRes.open = listRes.open.map(mapEntry);
|
|
5890
|
+
if (listRes.delete) listRes.delete = listRes.delete.map(mapEntry);
|
|
5891
|
+
result = listRes;
|
|
5892
|
+
} else {
|
|
5893
|
+
if (sql?.as === 2) {
|
|
5894
|
+
let items = listRes;
|
|
5895
|
+
if (sql.where?.length) {
|
|
5896
|
+
const where = sql.where;
|
|
5897
|
+
const whereType = typeof where[0] === "string" ? where[0] : "and";
|
|
5898
|
+
items = items.filter((el) => {
|
|
5899
|
+
let match = whereType === "and";
|
|
5900
|
+
for (let i = 0; i < where.length; i++) {
|
|
5901
|
+
if (typeof where[i] === "string") continue;
|
|
5902
|
+
const cond = where[i];
|
|
5903
|
+
if (!cond) continue;
|
|
5904
|
+
const field = cond[0];
|
|
5905
|
+
const searchVal = String(cond[2]).replace(/%/g, "");
|
|
5906
|
+
let fieldVal = "";
|
|
5907
|
+
try {
|
|
5908
|
+
fieldVal = JSON.parse(el.data)[field] ?? "";
|
|
5909
|
+
} catch {
|
|
5910
|
+
}
|
|
5911
|
+
const condMatch = String(fieldVal).includes(searchVal);
|
|
5912
|
+
if (whereType === "and") {
|
|
5913
|
+
match = match && condMatch;
|
|
5914
|
+
if (!match) break;
|
|
5915
|
+
} else {
|
|
5916
|
+
match = match || condMatch;
|
|
5917
|
+
if (match) break;
|
|
5918
|
+
}
|
|
6099
5919
|
}
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
|
|
5920
|
+
return match;
|
|
5921
|
+
});
|
|
5922
|
+
}
|
|
5923
|
+
items = items.map((el) => {
|
|
5924
|
+
const obj = {};
|
|
5925
|
+
try {
|
|
5926
|
+
const parsed = JSON.parse(el.data);
|
|
5927
|
+
if (sql.field?.length) {
|
|
5928
|
+
sql.field.forEach((f) => {
|
|
5929
|
+
obj[f] = parsed[f] ?? "";
|
|
5930
|
+
});
|
|
6104
5931
|
} else {
|
|
6105
|
-
|
|
6106
|
-
if (match) break;
|
|
5932
|
+
Object.assign(obj, parsed);
|
|
6107
5933
|
}
|
|
5934
|
+
} catch {
|
|
6108
5935
|
}
|
|
6109
|
-
return
|
|
5936
|
+
return obj;
|
|
6110
5937
|
});
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
}
|
|
6121
|
-
Object.assign(obj, parsed);
|
|
6122
|
-
}
|
|
6123
|
-
} catch {
|
|
5938
|
+
if (sql.order?.length) {
|
|
5939
|
+
const orderField = sql.order[0];
|
|
5940
|
+
const orderFieldName = typeof orderField === "string" ? orderField : orderField[0];
|
|
5941
|
+
const direction = typeof orderField === "string" ? "ASC" : String(orderField[1]).toUpperCase();
|
|
5942
|
+
items.sort((a, b) => {
|
|
5943
|
+
const valA = String(a[orderFieldName] ?? "");
|
|
5944
|
+
const valB = String(b[orderFieldName] ?? "");
|
|
5945
|
+
const cmp = valA.localeCompare(valB);
|
|
5946
|
+
return direction === "DESC" ? -cmp : cmp;
|
|
5947
|
+
});
|
|
6124
5948
|
}
|
|
6125
|
-
|
|
6126
|
-
}
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
const valA = String(a[orderFieldName] ?? "");
|
|
6133
|
-
const valB = String(b[orderFieldName] ?? "");
|
|
6134
|
-
const cmp = valA.localeCompare(valB);
|
|
6135
|
-
return direction === "DESC" ? -cmp : cmp;
|
|
6136
|
-
});
|
|
5949
|
+
result = items;
|
|
5950
|
+
} else {
|
|
5951
|
+
const fields = sql.field;
|
|
5952
|
+
result = listRes.map((el) => ({
|
|
5953
|
+
[fields[0]]: el.file,
|
|
5954
|
+
...fields[1] ? { [fields[1]]: el.data } : {}
|
|
5955
|
+
}));
|
|
6137
5956
|
}
|
|
6138
|
-
result = items;
|
|
6139
|
-
} else {
|
|
6140
|
-
const fields = sql.field;
|
|
6141
|
-
result = listRes.map((el) => ({
|
|
6142
|
-
[fields[0]]: el.file,
|
|
6143
|
-
...fields[1] ? { [fields[1]]: el.data } : {}
|
|
6144
|
-
}));
|
|
6145
5957
|
}
|
|
5958
|
+
} else {
|
|
5959
|
+
result = sql?.list ? { open: [], delete: [] } : [];
|
|
6146
5960
|
}
|
|
6147
|
-
} else {
|
|
6148
|
-
result = sql?.list ? { open: [], delete: [] } : [];
|
|
6149
5961
|
}
|
|
6150
5962
|
}
|
|
5963
|
+
if (result !== void 0) {
|
|
5964
|
+
resolve6(result);
|
|
5965
|
+
} else {
|
|
5966
|
+
reject("");
|
|
5967
|
+
}
|
|
5968
|
+
} catch (e) {
|
|
5969
|
+
reject(e);
|
|
6151
5970
|
}
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
} else {
|
|
6155
|
-
reject("");
|
|
6156
|
-
}
|
|
6157
|
-
} catch (e) {
|
|
6158
|
-
reject(e);
|
|
6159
|
-
}
|
|
6160
|
-
});
|
|
5971
|
+
});
|
|
5972
|
+
}
|
|
6161
5973
|
}
|
|
6162
|
-
|
|
6163
|
-
|
|
5974
|
+
return DbFile;
|
|
5975
|
+
}
|
|
5976
|
+
__name(dbDriver, "dbDriver");
|
|
5977
|
+
var Index_default2 = dbDriver;
|
|
6164
5978
|
|
|
6165
5979
|
// src/sysserver/db/db.ts
|
|
6166
|
-
var
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
function
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
5980
|
+
var BUILTIN_FILE = "file";
|
|
5981
|
+
function buildFileDriver() {
|
|
5982
|
+
return Index_default2({ IdbType: {}, Sql: Sql_default, DbCommon: common_exports });
|
|
5983
|
+
}
|
|
5984
|
+
__name(buildFileDriver, "buildFileDriver");
|
|
5985
|
+
function resolveDriver(type, dbDrivers) {
|
|
5986
|
+
if (type === BUILTIN_FILE) {
|
|
5987
|
+
return buildFileDriver();
|
|
5988
|
+
}
|
|
5989
|
+
const factory = dbDrivers[type];
|
|
5990
|
+
if (typeof factory !== "function") {
|
|
5991
|
+
return null;
|
|
6175
5992
|
}
|
|
6176
|
-
return {};
|
|
5993
|
+
return factory({ IdbType: {}, Sql: Sql_default, DbCommon: common_exports });
|
|
6177
5994
|
}
|
|
6178
|
-
__name(
|
|
5995
|
+
__name(resolveDriver, "resolveDriver");
|
|
5996
|
+
var initDb = /* @__PURE__ */ __name((type = BUILTIN_FILE, config = {}, dbDrivers = {}) => {
|
|
5997
|
+
let Driver = resolveDriver(type, dbDrivers);
|
|
5998
|
+
if (!Driver) {
|
|
5999
|
+
if (type !== BUILTIN_FILE) {
|
|
6000
|
+
console.warn(
|
|
6001
|
+
`[initDb] \u672A\u6CE8\u518C\u9A71\u52A8 "${type}"\uFF08\u8BF7\u5728 sysserver($asai, { db: { ${type}: driver } }) \u6302\u8F7D\uFF09\uFF0C\u5DF2\u56DE\u9000 file \u5B58\u50A8`
|
|
6002
|
+
);
|
|
6003
|
+
}
|
|
6004
|
+
Driver = buildFileDriver();
|
|
6005
|
+
}
|
|
6006
|
+
return new Driver(config);
|
|
6007
|
+
}, "initDb");
|
|
6179
6008
|
var db_default = initDb;
|
|
6180
6009
|
|
|
6181
6010
|
// src/sysserver/index.ts
|
|
6182
|
-
|
|
6011
|
+
function sysserver($asai, opt = {}) {
|
|
6183
6012
|
if (!$asai.$lib) $asai.$lib = {};
|
|
6013
|
+
$asai.$lib = { ...lib_default, ...$asai.$lib, ...opt || {} };
|
|
6184
6014
|
$asai.dataserver = db_default;
|
|
6185
|
-
$asai.$lib = { ...lib_default, ...$asai.$lib };
|
|
6186
|
-
if (!$asai.asaifs) {
|
|
6187
|
-
$asai.asaifs = $asai.$lib.AsFs;
|
|
6188
|
-
}
|
|
6189
6015
|
$asai.$lib.ErrorCode?.loadErrorCodes?.($asai);
|
|
6190
6016
|
if (!$asai.$lib.api) $asai.$lib.api = {};
|
|
6191
6017
|
$asai.$lib.api = { ...api_default, ...$asai.$lib.api };
|
|
6192
6018
|
if (!$asai.$lib.ws) $asai.$lib.ws = {};
|
|
6193
6019
|
$asai.$lib.ws = { ...ws_default, ...$asai.$lib.ws };
|
|
6194
|
-
}
|
|
6020
|
+
}
|
|
6021
|
+
__name(sysserver, "sysserver");
|
|
6195
6022
|
|
|
6196
6023
|
// src/config/validate.ts
|
|
6197
6024
|
function normalizeHostConfig(cfg) {
|
|
@@ -6306,6 +6133,12 @@ function normalizeUserStoreConfig(cfg) {
|
|
|
6306
6133
|
} else if (store.type === "sqlite") {
|
|
6307
6134
|
if (!store.opt.database) store.opt.database = "./webdata/webdb/users/users.db";
|
|
6308
6135
|
if (store.opt.create == null) store.opt.create = 1;
|
|
6136
|
+
} else if (store.type === "mysql") {
|
|
6137
|
+
if (!store.opt.host) store.opt.host = "127.0.0.1";
|
|
6138
|
+
if (!store.opt.user) store.opt.user = "root";
|
|
6139
|
+
if (store.opt.password == null) store.opt.password = "";
|
|
6140
|
+
if (!store.opt.database) store.opt.database = "asai";
|
|
6141
|
+
if (store.opt.create == null) store.opt.create = 1;
|
|
6309
6142
|
}
|
|
6310
6143
|
}
|
|
6311
6144
|
__name(normalizeUserStoreConfig, "normalizeUserStoreConfig");
|
|
@@ -6348,6 +6181,30 @@ function normalizeDataStoresConfig(cfg) {
|
|
|
6348
6181
|
if (!stores.user && cfg.userStore) {
|
|
6349
6182
|
stores.user = { type: cfg.userStore.type || "file", opt: { ...cfg.userStore.opt || {} } };
|
|
6350
6183
|
}
|
|
6184
|
+
if (!stores.user) {
|
|
6185
|
+
stores.user = {
|
|
6186
|
+
type: "file",
|
|
6187
|
+
opt: { dir: "webdata/webdb/users/", ext: ".json", create: 1 }
|
|
6188
|
+
};
|
|
6189
|
+
}
|
|
6190
|
+
if (stores.user && typeof stores.user === "object") {
|
|
6191
|
+
const userStore = stores.user;
|
|
6192
|
+
const userType = userStore.type || "file";
|
|
6193
|
+
if (!userStore.type) userStore.type = "file";
|
|
6194
|
+
if (!userStore.opt || typeof userStore.opt !== "object") userStore.opt = {};
|
|
6195
|
+
if (userType === "file") {
|
|
6196
|
+
if (!userStore.opt.dir) userStore.opt.dir = "webdata/webdb/users/";
|
|
6197
|
+
if (!userStore.opt.ext) userStore.opt.ext = ".json";
|
|
6198
|
+
} else if (userType === "sqlite") {
|
|
6199
|
+
if (!userStore.opt.database) userStore.opt.database = "./webdata/webdb/users/users.db";
|
|
6200
|
+
} else if (userType === "mysql") {
|
|
6201
|
+
if (!userStore.opt.host) userStore.opt.host = "127.0.0.1";
|
|
6202
|
+
if (!userStore.opt.user) userStore.opt.user = "root";
|
|
6203
|
+
if (userStore.opt.password == null) userStore.opt.password = "";
|
|
6204
|
+
if (!userStore.opt.database) userStore.opt.database = "asai";
|
|
6205
|
+
}
|
|
6206
|
+
if (userStore.opt.create == null) userStore.opt.create = 1;
|
|
6207
|
+
}
|
|
6351
6208
|
if (!stores.log && cfg.logger?.store) {
|
|
6352
6209
|
const s = cfg.logger.store;
|
|
6353
6210
|
stores.log = {
|
|
@@ -6674,10 +6531,10 @@ function hostDefault(mod) {
|
|
|
6674
6531
|
}
|
|
6675
6532
|
__name(hostDefault, "hostDefault");
|
|
6676
6533
|
var hostMain = hostDefault(src_default);
|
|
6677
|
-
var
|
|
6534
|
+
var initAsaiHostNodejs2 = {
|
|
6678
6535
|
name: PLUGIN_NAME,
|
|
6679
6536
|
fn: /* @__PURE__ */ __name(($asai, opt) => hostMain($asai, opt), "fn"),
|
|
6680
|
-
sysserver
|
|
6537
|
+
sysserver,
|
|
6681
6538
|
AsaiCommon: Index_default,
|
|
6682
6539
|
AsaiUtils: utils_exports,
|
|
6683
6540
|
normalizeHostConfig,
|
|
@@ -6714,8 +6571,8 @@ var initAsaiHost2 = {
|
|
|
6714
6571
|
createTransportDataHandler,
|
|
6715
6572
|
wireTransportConnectionLogs
|
|
6716
6573
|
};
|
|
6717
|
-
var PLUGIN_VERSION = true ? "0.0.
|
|
6718
|
-
var index_default =
|
|
6574
|
+
var PLUGIN_VERSION = true ? "0.0.3" : "0.0.1";
|
|
6575
|
+
var index_default = initAsaiHostNodejs2;
|
|
6719
6576
|
export {
|
|
6720
6577
|
Index_default as AsaiCommon,
|
|
6721
6578
|
utils_exports as AsaiUtils,
|
|
@@ -6730,7 +6587,7 @@ export {
|
|
|
6730
6587
|
getTransportBlock,
|
|
6731
6588
|
getTransportDefault,
|
|
6732
6589
|
hasTransportDefault,
|
|
6733
|
-
|
|
6590
|
+
initAsaiHostNodejs2 as initAsaiHostNodejs,
|
|
6734
6591
|
initHostserverApi,
|
|
6735
6592
|
initHostserverWs,
|
|
6736
6593
|
initTransportBridge,
|
|
@@ -6751,7 +6608,7 @@ export {
|
|
|
6751
6608
|
stripForbiddenSecretsFromHostConfig,
|
|
6752
6609
|
syncApiRouteKeys,
|
|
6753
6610
|
syncWsRouteKeys,
|
|
6754
|
-
|
|
6611
|
+
sysserver,
|
|
6755
6612
|
validateAsaiSecrets,
|
|
6756
6613
|
validateHostConfig,
|
|
6757
6614
|
validateHttpAuth,
|