jsir 2.3.9 → 2.4.1
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/cmd/oaa.js +122 -16
- package/deps/room.js +148 -84
- package/deps/server.js +59 -49
- package/deps/setting.js +5 -1
- package/deps/util.js +55 -12
- package/package.json +1 -1
package/cmd/oaa.js
CHANGED
|
@@ -11,7 +11,8 @@ const {
|
|
|
11
11
|
createConsole, setTips, delTips,
|
|
12
12
|
getEditor, errorStr, getConfigDir,
|
|
13
13
|
getFullPath, parseUniqueName, toUniqueName, isJsirFileName, toJsirFileName,
|
|
14
|
-
getAlias, wrapperJsirText, eia, getKeyTips, getValTips, getJsirTypeKey
|
|
14
|
+
getAlias, wrapperJsirText, eia, getKeyTips, getValTips, getJsirTypeKey,
|
|
15
|
+
createDetachedProcess, interceptStdStreams
|
|
15
16
|
} = $lib;
|
|
16
17
|
const _args = process.argv.slice(2).map(trim);
|
|
17
18
|
const evalCode = require('../deps/evalCode')
|
|
@@ -29,6 +30,8 @@ const _fileWatcherMap = {}
|
|
|
29
30
|
const _types = setting.fileType
|
|
30
31
|
const console = createConsole();
|
|
31
32
|
const room = require('../deps/room');
|
|
33
|
+
const server = require('../deps/server')
|
|
34
|
+
const {reqNode} = require("../deps/room");
|
|
32
35
|
|
|
33
36
|
let lastFilterArg = '';
|
|
34
37
|
let _cmdMapFile = setting.name + 'CmdMap.json'
|
|
@@ -157,6 +160,7 @@ function checkWorkspaces() {
|
|
|
157
160
|
}
|
|
158
161
|
|
|
159
162
|
async function start() {
|
|
163
|
+
interceptStdStreams()
|
|
160
164
|
checkWorkspaces()
|
|
161
165
|
initWorkspace()
|
|
162
166
|
|
|
@@ -391,7 +395,7 @@ function _nextLine(callback, promptStr, hidden, resolve, end, isText) {
|
|
|
391
395
|
_haveWrapperInput = true;
|
|
392
396
|
closeRl()
|
|
393
397
|
inputStr = inputStr.replace(/\s+$/, '');
|
|
394
|
-
if (!isText && /^\d+$/.test(inputStr) && /^\s+/.test(textLine)) {
|
|
398
|
+
if (!isText && /^\d+$/.test(inputStr) && /^\s+/.test(textLine) && (!callback || callback === wrapperInput)) {
|
|
395
399
|
inputStr = '.e ' + inputStr;
|
|
396
400
|
}
|
|
397
401
|
let pro = (callback || wrapperInput)(inputStr);
|
|
@@ -580,11 +584,11 @@ function wrapperAlias(str) {
|
|
|
580
584
|
async function _wrapperInput(str) {
|
|
581
585
|
_haveWrapperInput = true;
|
|
582
586
|
global.$newInput = true;
|
|
583
|
-
|
|
584
587
|
str = trim(str)
|
|
585
588
|
if (!str) {
|
|
586
589
|
return;
|
|
587
590
|
}
|
|
591
|
+
global.$newError = false;
|
|
588
592
|
str = wrapperAlias(str);
|
|
589
593
|
|
|
590
594
|
if (/^[`'"]/.test(str)) {
|
|
@@ -624,10 +628,16 @@ async function _wrapperInput(str) {
|
|
|
624
628
|
let firstName = getJsirTypeKey(fileName)
|
|
625
629
|
if (firstName === setting.fileKey) {
|
|
626
630
|
await workFile(uniqueName)
|
|
627
|
-
} else if (firstName
|
|
628
|
-
console.log(String(fs.readFileSync(path)));
|
|
629
|
-
} else {
|
|
631
|
+
} else if (firstName === setting.exeKey) {
|
|
630
632
|
await runCmd(str)
|
|
633
|
+
} else if (firstName === setting.initKey) {
|
|
634
|
+
if (strs[1] === '-') {
|
|
635
|
+
await offServer(uniqueName)
|
|
636
|
+
} else {
|
|
637
|
+
await joinServer(uniqueName)
|
|
638
|
+
}
|
|
639
|
+
} else {
|
|
640
|
+
console.log(String(fs.readFileSync(path)));
|
|
631
641
|
}
|
|
632
642
|
} else {
|
|
633
643
|
await runCmd(str)
|
|
@@ -635,6 +645,44 @@ async function _wrapperInput(str) {
|
|
|
635
645
|
}
|
|
636
646
|
}
|
|
637
647
|
|
|
648
|
+
async function joinServer(uniqueName) {
|
|
649
|
+
let exportLib = await _requireSource(setting.defaultSpace, uniqueName, true);
|
|
650
|
+
let asyncFnSize = 0
|
|
651
|
+
for (let key of Object.keys(exportLib)) {
|
|
652
|
+
let val = exportLib[key];
|
|
653
|
+
if (getType(val) !== 'AsyncFunction') {
|
|
654
|
+
throw `service function must be async`;
|
|
655
|
+
}
|
|
656
|
+
asyncFnSize ++
|
|
657
|
+
}
|
|
658
|
+
if (asyncFnSize <= 0) {
|
|
659
|
+
console.warn('no items');
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
let pair = parseUniqueName(uniqueName)
|
|
663
|
+
for (let key of Object.keys(exportLib)) {
|
|
664
|
+
let val = exportLib[key];
|
|
665
|
+
server.setRoute('post', `/${pair[0]}/${trimJsirFileName(pair[1])}/${key}`,async (req, res) => {
|
|
666
|
+
res.result = await val(...req);
|
|
667
|
+
}, server.jsonHandle)
|
|
668
|
+
}
|
|
669
|
+
console.msg(uniqueName, 'has become a service.');
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
async function offServer(uniqueName) {
|
|
673
|
+
let pair = parseUniqueName(uniqueName)
|
|
674
|
+
let service = `${pair[0]}/${trimJsirFileName(pair[1])}`;
|
|
675
|
+
|
|
676
|
+
for (let key of Object.keys(setting.routes)) {
|
|
677
|
+
let ss = key.split("/").map(trim).filter(i => i);
|
|
678
|
+
let keyService = ss[1] + '/' + ss[2]
|
|
679
|
+
if (keyService === service) {
|
|
680
|
+
delete setting.routes[key]
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
console.msg(uniqueName, 'service has been offline.');
|
|
684
|
+
}
|
|
685
|
+
|
|
638
686
|
async function dealStartGlobalMode(rows, items, text) {
|
|
639
687
|
console.info('global search mode');
|
|
640
688
|
rows.push(...await dealStarCase(await evalText('return $context'), items, '$context'))
|
|
@@ -978,18 +1026,20 @@ async function showRooms() {
|
|
|
978
1026
|
})
|
|
979
1027
|
for (let key of Object.keys(room.jsirs)) {
|
|
980
1028
|
let jsir = room.jsirs[key]
|
|
1029
|
+
let pidStr = (process.pid === jsir.pid ? "*" : " ") + jsir.pid;
|
|
981
1030
|
results.push({
|
|
982
1031
|
mid: i,
|
|
983
1032
|
name: (isLocal ? "*" : " ") + room.name,
|
|
984
1033
|
node: room.selfNode,
|
|
985
1034
|
active: room.active,
|
|
986
|
-
pid:
|
|
1035
|
+
pid: jsir.newError ? errorStr(pidStr):pidStr,
|
|
987
1036
|
space: jsir.space,
|
|
988
1037
|
running: jsir.active,
|
|
989
1038
|
port: jsir.port,
|
|
990
1039
|
back: jsir.back,
|
|
991
1040
|
busy: jsir.busy,
|
|
992
|
-
tips: Object.keys(jsir.tips).map(i => i + ': ' + jsir.tips[i]).join('\n')
|
|
1041
|
+
tips: Object.keys(jsir.tips).map(i => i + ': ' + jsir.tips[i]).join('\n'),
|
|
1042
|
+
services: Object.keys(jsir.services || []).map(msgStr).join('\n')
|
|
993
1043
|
});
|
|
994
1044
|
}
|
|
995
1045
|
}
|
|
@@ -1443,17 +1493,48 @@ const keywordDef = {
|
|
|
1443
1493
|
comment: 'manage room ([node])',
|
|
1444
1494
|
exeFn: async (args) => {
|
|
1445
1495
|
await room.syncSetting();
|
|
1446
|
-
if (args
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1496
|
+
if (args.length > 0) {
|
|
1497
|
+
let have = false;
|
|
1498
|
+
let tasks = []
|
|
1499
|
+
for (let room of setting.rooms) {
|
|
1500
|
+
for (let pid of Object.keys(room.jsirs || {})) {
|
|
1501
|
+
if (args.indexOf(room.selfNode) === -1 && args.indexOf(pid) === -1) {
|
|
1502
|
+
continue;
|
|
1503
|
+
}
|
|
1504
|
+
if (setting.selfRoom.selfNode === room.selfNode && String(process.pid) === pid) {
|
|
1505
|
+
continue;
|
|
1506
|
+
}
|
|
1507
|
+
have = true;
|
|
1508
|
+
tasks.push(async (input) => {
|
|
1509
|
+
let jsir = room.jsirs[pid];
|
|
1510
|
+
console.log(infoStr(`[${pid}]`))
|
|
1511
|
+
let resp = await reqNode(room.selfNode, "post", "/enter", jsir.port, JSON.stringify({input}))
|
|
1512
|
+
console.log(JSON.parse(resp).output)
|
|
1513
|
+
})
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
let input = have ? await nextLine(i => i):''
|
|
1517
|
+
if (vl(input)) {
|
|
1518
|
+
for (let task of tasks) {
|
|
1519
|
+
try {
|
|
1520
|
+
await task(input)
|
|
1521
|
+
} catch (e) {
|
|
1522
|
+
console.error(e)
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1451
1525
|
}
|
|
1452
1526
|
} else {
|
|
1453
1527
|
await showRooms();
|
|
1454
1528
|
}
|
|
1455
1529
|
},
|
|
1456
1530
|
short: 'm'
|
|
1531
|
+
},
|
|
1532
|
+
nsir: {
|
|
1533
|
+
comment: 'new jsir',
|
|
1534
|
+
exeFn: async (args) => {
|
|
1535
|
+
await createDetachedProcess('jsir', ['.p'])
|
|
1536
|
+
},
|
|
1537
|
+
short: 'N'
|
|
1457
1538
|
}
|
|
1458
1539
|
}
|
|
1459
1540
|
|
|
@@ -2050,7 +2131,7 @@ function filterRequire(currSpace, cmdMatchStr) {
|
|
|
2050
2131
|
return cmds[0];
|
|
2051
2132
|
}
|
|
2052
2133
|
|
|
2053
|
-
async function _requireSource(currSpace, cmdMatchStr) {
|
|
2134
|
+
async function _requireSource(currSpace, cmdMatchStr, force = false) {
|
|
2054
2135
|
let result
|
|
2055
2136
|
let uniqueName = filterRequire(currSpace, cmdMatchStr);
|
|
2056
2137
|
let path = getFullPath(uniqueName)
|
|
@@ -2058,7 +2139,32 @@ async function _requireSource(currSpace, cmdMatchStr) {
|
|
|
2058
2139
|
let pair = parseUniqueName(uniqueName)
|
|
2059
2140
|
let typeKey = getJsirTypeKey(pair[1]);
|
|
2060
2141
|
if (typeKey === setting.initKey) {
|
|
2061
|
-
|
|
2142
|
+
let pair = parseUniqueName(uniqueName)
|
|
2143
|
+
let serviceKey = `${pair[0]}/${trimJsirFileName(pair[1])}`
|
|
2144
|
+
if (setting.defaultSpace !== 'local' && !force && setting.services[serviceKey]) {
|
|
2145
|
+
let serviceObj = {};
|
|
2146
|
+
for (let fn of setting.services[serviceKey]) {
|
|
2147
|
+
serviceObj[fn] = async (...args) => {
|
|
2148
|
+
let key = `${serviceKey}/${fn}`;
|
|
2149
|
+
let targets = setting.serviceFns[key] || []
|
|
2150
|
+
targets = targets
|
|
2151
|
+
.filter(i => i.active)
|
|
2152
|
+
.filter(i => !room.isDisableConnect(i.node, i.port));
|
|
2153
|
+
let target = null;
|
|
2154
|
+
if (targets.length > 0) {
|
|
2155
|
+
target = room.busyPick(targets);
|
|
2156
|
+
} else {
|
|
2157
|
+
throw `service function ${key} not found`;
|
|
2158
|
+
}
|
|
2159
|
+
let resp = await room.reqNode(target.node, 'post',
|
|
2160
|
+
`/${pair[0]}/${encodeURIComponent(trimJsirFileName(pair[1]))}/${fn}`, target.port, JSON.stringify(args))
|
|
2161
|
+
return JSON.parse(resp).result;
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
return serviceObj
|
|
2165
|
+
} else {
|
|
2166
|
+
result = await evalText(text, uniqueName)
|
|
2167
|
+
}
|
|
2062
2168
|
} else if (typeKey === setting.exeKey) {
|
|
2063
2169
|
result = async (argsStr) => {
|
|
2064
2170
|
return await runCmd(trim(argsStr), uniqueName, text)
|
|
@@ -2148,7 +2254,7 @@ process.on('beforeExit', function () {
|
|
|
2148
2254
|
delTips();
|
|
2149
2255
|
} else {
|
|
2150
2256
|
nextLine();
|
|
2151
|
-
room.onRoom()
|
|
2257
|
+
room.onRoom(wrapperInput)
|
|
2152
2258
|
}
|
|
2153
2259
|
});
|
|
2154
2260
|
|
package/deps/room.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const os = require('os');
|
|
2
2
|
const {fileJson, fileLock, vl, createConsole, getKeyTips, getValTips,
|
|
3
3
|
getRoomsDir, e, regEach, isRunningInBackground,
|
|
4
|
-
batchAsync
|
|
4
|
+
batchAsync, debugStr, trim, getOr, errorTag, warnStr, getConfig
|
|
5
5
|
} = require('./util');
|
|
6
6
|
const {setRoute, createSign} = require('../deps/server')
|
|
7
7
|
const roomDataFile = "jsirRoom.json"
|
|
@@ -16,6 +16,12 @@ const http = require('http');
|
|
|
16
16
|
let tailscalePath = os.platform() === 'darwin' ?
|
|
17
17
|
'/Applications/Tailscale.app/Contents/MacOS/Tailscale':'tailscale';
|
|
18
18
|
|
|
19
|
+
function debug(...args) {
|
|
20
|
+
if (global.$DEBUG) {
|
|
21
|
+
console.$log(debugStr('[debug]'), ...args);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
19
25
|
function isPidAlive(pid) {
|
|
20
26
|
try {
|
|
21
27
|
process.kill(Number(pid), 0); // 信号 0 不会实际发送,但会检查进程是否存在
|
|
@@ -24,18 +30,35 @@ function isPidAlive(pid) {
|
|
|
24
30
|
if (err.code === 'ESRCH') {
|
|
25
31
|
return false; // 进程不存在
|
|
26
32
|
}
|
|
27
|
-
|
|
33
|
+
debug(`check isPidAlive ${pid} failed`, err)
|
|
28
34
|
return false;
|
|
29
35
|
}
|
|
30
36
|
}
|
|
31
37
|
|
|
32
|
-
function onRoom() {
|
|
38
|
+
function onRoom(wrapperInput) {
|
|
33
39
|
if (!setting.roomTid[0]) {
|
|
34
40
|
try {
|
|
35
41
|
setRoute("post", "/", (req, res) => setting.selfRoom)
|
|
36
42
|
setRoute("get", "/", (req, res) => setting.selfRoom)
|
|
43
|
+
setRoute("post", "/enter", async (req, res) => {
|
|
44
|
+
if (!getConfig("enableRemote", true)) {
|
|
45
|
+
res.output = warnStr("Disable Remote!")
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (vl(req.input)) {
|
|
49
|
+
if (!isRunningInBackground() && ['.nsir', '.N'].indexOf(req.input.trim()) === -1) {
|
|
50
|
+
res.output = warnStr("Disable Remote!")
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
setting.enterOutputs = []
|
|
54
|
+
await wrapperInput(req.input)
|
|
55
|
+
let outputs = setting.enterOutputs || [];
|
|
56
|
+
setting.enterOutputs = null;
|
|
57
|
+
res.output = outputs.join('\n');
|
|
58
|
+
}
|
|
59
|
+
})
|
|
37
60
|
} catch (e) {
|
|
38
|
-
|
|
61
|
+
debug("initRoute failed", e)
|
|
39
62
|
}
|
|
40
63
|
_onRoom();
|
|
41
64
|
}
|
|
@@ -46,7 +69,7 @@ function _onRoom() {
|
|
|
46
69
|
try {
|
|
47
70
|
await initRoom();
|
|
48
71
|
} catch (e) {
|
|
49
|
-
|
|
72
|
+
debug('initRoom', e)
|
|
50
73
|
}
|
|
51
74
|
if (setting.roomTid[0]) {
|
|
52
75
|
_onRoom();
|
|
@@ -60,7 +83,7 @@ function offRoom() {
|
|
|
60
83
|
setting.roomTid[0] = null
|
|
61
84
|
if (setting.server) {
|
|
62
85
|
setting.server.close(() => {
|
|
63
|
-
|
|
86
|
+
debug('Existing server shut down.');
|
|
64
87
|
});
|
|
65
88
|
}
|
|
66
89
|
}
|
|
@@ -89,12 +112,12 @@ async function enrichRoomInfo() {
|
|
|
89
112
|
let name = os.hostname();
|
|
90
113
|
if (!vl(room.name) || room.name !== name) {
|
|
91
114
|
room.name = name;
|
|
92
|
-
|
|
115
|
+
debug("set roomName", name)
|
|
93
116
|
}
|
|
94
117
|
room.selfNode = ip;
|
|
95
118
|
room.nodes = nodes;
|
|
96
119
|
room.lastUpdateTime = Date.now()
|
|
97
|
-
|
|
120
|
+
debug("init room", room.name)
|
|
98
121
|
|
|
99
122
|
// 提前设置一下
|
|
100
123
|
setting.selfRoom = Object.assign(setting.selfRoom || {}, room);
|
|
@@ -116,13 +139,13 @@ async function syncRooms() {
|
|
|
116
139
|
try {
|
|
117
140
|
respBody = await reqNode(node, "get", "/");
|
|
118
141
|
syncRooms.push(JSON.parse(respBody))
|
|
119
|
-
|
|
142
|
+
debug(`sync ${node} success`);
|
|
120
143
|
} catch (e) {
|
|
121
|
-
|
|
144
|
+
debug(`sync ${node} failed:`, respBody, e);
|
|
122
145
|
}
|
|
123
146
|
})
|
|
124
147
|
}
|
|
125
|
-
await batchAsync(fns,
|
|
148
|
+
await batchAsync(fns, 33);
|
|
126
149
|
await fileLock(roomsDirLockKey, async () => {
|
|
127
150
|
for (let syncRoom of [...syncRooms]) {
|
|
128
151
|
if (syncRoom.selfNode) {
|
|
@@ -139,11 +162,11 @@ async function syncRooms() {
|
|
|
139
162
|
try {
|
|
140
163
|
await fp.unlink(roomsDir + '/' + file)
|
|
141
164
|
} catch (e) {
|
|
142
|
-
|
|
165
|
+
debug(e);
|
|
143
166
|
}
|
|
144
167
|
}
|
|
145
168
|
}
|
|
146
|
-
|
|
169
|
+
debug('syncRooms done')
|
|
147
170
|
}
|
|
148
171
|
|
|
149
172
|
async function syncSetting() {
|
|
@@ -180,6 +203,30 @@ async function _syncSetting(room) {
|
|
|
180
203
|
jsir.active = Date.now() - (jsir.lastUpdateTime || 0) <= 9000)
|
|
181
204
|
return room;
|
|
182
205
|
});
|
|
206
|
+
|
|
207
|
+
setting.services = {}
|
|
208
|
+
setting.serviceFns = {}
|
|
209
|
+
for (const room of setting.rooms) {
|
|
210
|
+
for (const pid of Object.keys(room.jsirs || [])) {
|
|
211
|
+
let jsir = room.jsirs[pid];
|
|
212
|
+
for (const service of Object.keys(jsir.services || [])) {
|
|
213
|
+
let serviceFns = jsir.services[service] || [];
|
|
214
|
+
|
|
215
|
+
let existFns = setting.services[service] || [];
|
|
216
|
+
existFns.push(...serviceFns)
|
|
217
|
+
setting.services[service] = [...new Set(existFns)]
|
|
218
|
+
|
|
219
|
+
for (const fn of serviceFns) {
|
|
220
|
+
getOr(setting.serviceFns, service + '/' + fn, []).push({
|
|
221
|
+
node: room.selfNode,
|
|
222
|
+
port: jsir.port,
|
|
223
|
+
busy: jsir.busy,
|
|
224
|
+
active: jsir.active
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
183
230
|
}
|
|
184
231
|
|
|
185
232
|
async function initRoom() {
|
|
@@ -199,6 +246,10 @@ async function initRoom() {
|
|
|
199
246
|
await syncSetting();
|
|
200
247
|
}
|
|
201
248
|
|
|
249
|
+
/*
|
|
250
|
+
最小为0
|
|
251
|
+
100 为1 毫秒
|
|
252
|
+
*/
|
|
202
253
|
async function getEventLoopDelay() {
|
|
203
254
|
const start = process.hrtime.bigint();
|
|
204
255
|
const delay = 1; // 用于测量的短暂延迟(单位:毫秒)
|
|
@@ -213,6 +264,15 @@ async function initRoomJsir(room) {
|
|
|
213
264
|
room.jsirs = Object.fromEntries(
|
|
214
265
|
Object.entries(room.jsirs || {}).filter(([pid]) => isPidAlive(pid))
|
|
215
266
|
);
|
|
267
|
+
|
|
268
|
+
let services = {}
|
|
269
|
+
for (let key of Object.keys(setting.routes)) {
|
|
270
|
+
let ss = key.split("/").map(trim).filter(i => i);
|
|
271
|
+
if (ss.length > 3) {
|
|
272
|
+
getOr(services, ss[1] + '/' + ss[2], []).push(ss[3])
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
let lastJsir = room.jsirs[process.pid]
|
|
216
276
|
room.jsirs[process.pid] = {
|
|
217
277
|
pid: process.pid,
|
|
218
278
|
space: setting.defaultSpace,
|
|
@@ -220,10 +280,12 @@ async function initRoomJsir(room) {
|
|
|
220
280
|
obj[key] = getValTips()[index];
|
|
221
281
|
return obj;
|
|
222
282
|
}, {}),
|
|
223
|
-
busy: await getEventLoopDelay(),
|
|
283
|
+
busy: await getEventLoopDelay() || (lastJsir ? lastJsir.busy:0),
|
|
224
284
|
back: isRunningInBackground(),
|
|
225
285
|
lastUpdateTime: Date.now(),
|
|
226
|
-
port: setting.server ? setting.server.address().port:null
|
|
286
|
+
port: setting.server ? setting.server.address().port:null,
|
|
287
|
+
services: services,
|
|
288
|
+
newError: global.$newError
|
|
227
289
|
}
|
|
228
290
|
}
|
|
229
291
|
|
|
@@ -257,62 +319,21 @@ function getLocalIPs() {
|
|
|
257
319
|
return result;
|
|
258
320
|
}
|
|
259
321
|
|
|
260
|
-
async function sshEnables(ips) {
|
|
261
|
-
// 检查开放 22 端口的 IP
|
|
262
|
-
const sshEnabledIPs = [];
|
|
263
|
-
const portCheckPromises = ips.map((ip) =>
|
|
264
|
-
checkPortOpen(ip, 22).then((isOpen) => {
|
|
265
|
-
if (isOpen) {
|
|
266
|
-
sshEnabledIPs.push(ip);
|
|
267
|
-
}
|
|
268
|
-
})
|
|
269
|
-
);
|
|
270
|
-
|
|
271
|
-
await Promise.all(portCheckPromises);
|
|
272
|
-
return sshEnabledIPs;
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* 检查指定 IP 的指定端口是否开放
|
|
277
|
-
* @param {string} ip - 目标 IP 地址
|
|
278
|
-
* @param {number} port - 目标端口
|
|
279
|
-
* @returns {Promise<boolean>} - 是否开放
|
|
280
|
-
*/
|
|
281
|
-
function checkPortOpen(ip, port) {
|
|
282
|
-
return new Promise((resolve) => {
|
|
283
|
-
const socket = new net.Socket();
|
|
284
|
-
socket.setTimeout(1000); // 设置超时时间
|
|
285
|
-
|
|
286
|
-
socket
|
|
287
|
-
.connect(port, ip, () => {
|
|
288
|
-
socket.destroy(); // 成功连接后关闭 socket
|
|
289
|
-
resolve(true);
|
|
290
|
-
})
|
|
291
|
-
.on("error", () => {
|
|
292
|
-
socket.destroy(); // 遇到错误时关闭 socket
|
|
293
|
-
resolve(false);
|
|
294
|
-
})
|
|
295
|
-
.on("timeout", () => {
|
|
296
|
-
socket.destroy(); // 超时时关闭 socket
|
|
297
|
-
resolve(false);
|
|
298
|
-
});
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
|
|
302
322
|
function isDisableConnect(node, port) {
|
|
303
323
|
let disableTime = setting.disableConnect[node + ":" + port] || 0;
|
|
304
324
|
return disableTime > Date.now()
|
|
305
325
|
}
|
|
306
326
|
|
|
307
|
-
async function reqNode(node, method, url) {
|
|
308
|
-
let port = 52108;
|
|
327
|
+
async function reqNode(node, method, url, port, body) {
|
|
309
328
|
let room = setting.rooms.filter(i => i.selfNode === node)[0]
|
|
310
|
-
if (
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
329
|
+
if (!port) {
|
|
330
|
+
port = 52108;
|
|
331
|
+
if (room) {
|
|
332
|
+
let activeJsir = Object.values(room.jsirs)
|
|
333
|
+
.filter(i => !isDisableConnect(node, i.port));
|
|
334
|
+
if (activeJsir.length > 0) {
|
|
335
|
+
port = busyPick(activeJsir).port;
|
|
336
|
+
}
|
|
316
337
|
}
|
|
317
338
|
}
|
|
318
339
|
let opt = {
|
|
@@ -324,35 +345,78 @@ async function reqNode(node, method, url) {
|
|
|
324
345
|
'sign': createSign()
|
|
325
346
|
}
|
|
326
347
|
};
|
|
327
|
-
|
|
348
|
+
debug('reqRoom', JSON.stringify(opt))
|
|
328
349
|
return await new Promise((resolve, reject) => {
|
|
329
|
-
|
|
330
|
-
let
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
350
|
+
try {
|
|
351
|
+
let timeoutMs = url === '/' ? 3000:49000;
|
|
352
|
+
let timeout = null
|
|
353
|
+
|
|
354
|
+
const req = http.request(opt, (res) => {
|
|
355
|
+
let data = '';
|
|
356
|
+
// 数据块接收
|
|
357
|
+
res.on('data', (chunk) => {
|
|
358
|
+
data += chunk;
|
|
359
|
+
});
|
|
360
|
+
// 响应结束
|
|
361
|
+
res.on('end', () => {
|
|
362
|
+
clearTimeout(timeout)
|
|
363
|
+
// 检查响应状态码
|
|
364
|
+
if (res.statusCode !== 200) {
|
|
365
|
+
reject(errorTag(new Error(data), decodeURIComponent(url)));
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
resolve(data)
|
|
369
|
+
});
|
|
334
370
|
});
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
371
|
+
|
|
372
|
+
// 设置超时逻辑
|
|
373
|
+
timeout = setTimeout(() => {
|
|
374
|
+
req.destroy(); // 中断请求
|
|
375
|
+
}, timeoutMs); // 10秒超时
|
|
376
|
+
|
|
377
|
+
// 错误处理
|
|
378
|
+
req.on('error', (e) => {
|
|
379
|
+
clearTimeout(timeout); // 清除超时定时器
|
|
380
|
+
if (room) {
|
|
381
|
+
setting.disableConnect[node + ":" + port] = Date.now() + 6000
|
|
382
|
+
}
|
|
383
|
+
reject(e)
|
|
338
384
|
});
|
|
339
|
-
});
|
|
340
385
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
if (room) {
|
|
344
|
-
setting.disableConnect[node + ":" + port] = Date.now() + 9000
|
|
386
|
+
if (body) {
|
|
387
|
+
req.write(body)
|
|
345
388
|
}
|
|
389
|
+
// 结束请求
|
|
390
|
+
req.end();
|
|
391
|
+
} catch (e) {
|
|
346
392
|
reject(e)
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// 结束请求
|
|
350
|
-
req.end();
|
|
393
|
+
}
|
|
351
394
|
})
|
|
352
395
|
}
|
|
353
396
|
|
|
397
|
+
function busyPick(busyItems) {
|
|
398
|
+
if (busyItems.length === 1) {
|
|
399
|
+
return busyItems[0]
|
|
400
|
+
}
|
|
401
|
+
// 反转权重:计算 1 / busy 值作为权重
|
|
402
|
+
const weights = busyItems.map(item => 1 / (item.busy || 1));
|
|
403
|
+
const totalWeight = weights.reduce((sum, weight) => sum + weight, 0);
|
|
404
|
+
const random = Math.random() * totalWeight;
|
|
405
|
+
|
|
406
|
+
let cumulativeWeight = 0;
|
|
407
|
+
for (let i = 0; i < busyItems.length; i++) {
|
|
408
|
+
cumulativeWeight += weights[i];
|
|
409
|
+
if (random < cumulativeWeight) {
|
|
410
|
+
return busyItems[i]; // 选中反转权重命中的对象
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
354
415
|
module.exports = {
|
|
355
416
|
onRoom,
|
|
356
417
|
offRoom,
|
|
357
|
-
syncSetting
|
|
418
|
+
syncSetting,
|
|
419
|
+
reqNode,
|
|
420
|
+
isDisableConnect,
|
|
421
|
+
busyPick
|
|
358
422
|
}
|
package/deps/server.js
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
const http = require('http');
|
|
2
|
-
const {createConsole, vl, aesDecipher, md5, aesCipher} = require('./util');
|
|
2
|
+
const {createConsole, vl, aesDecipher, md5, aesCipher, debugStr, isError} = require('./util');
|
|
3
3
|
const console = createConsole();
|
|
4
4
|
const setting = require('../deps/setting')
|
|
5
5
|
// 尝试监听的端口
|
|
6
6
|
const preferredPort = 52108;
|
|
7
7
|
// 路由存储
|
|
8
|
-
const routes =
|
|
8
|
+
const routes = setting.routes;
|
|
9
9
|
let invokeStart = false;
|
|
10
10
|
|
|
11
|
+
function debug(...args) {
|
|
12
|
+
if (global.$DEBUG) {
|
|
13
|
+
console.$log(debugStr('[debug]'), ...args);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
11
17
|
function verifySign(sign) {
|
|
12
18
|
try {
|
|
13
19
|
let key = md5([...setting.selfRoom.nodes].sort().join(":")).substring(0, 16);
|
|
@@ -30,10 +36,14 @@ function createSign() {
|
|
|
30
36
|
async function createServer(port) {
|
|
31
37
|
const server = http.createServer(async (req, res) => {
|
|
32
38
|
const method = req.method.toLowerCase();
|
|
33
|
-
const url = req.url;
|
|
39
|
+
const url = decodeURIComponent(req.url);
|
|
34
40
|
|
|
41
|
+
// 获取客户端的 IP 地址
|
|
42
|
+
const clientIp = req.socket.remoteAddress || req.connection.remoteAddress;
|
|
35
43
|
// 查找对应的路由处理函数
|
|
36
44
|
const routeKey = `${method} ${url}`;
|
|
45
|
+
debug(`Received ${routeKey} from ${clientIp}`);
|
|
46
|
+
|
|
37
47
|
if (routes[routeKey]) {
|
|
38
48
|
// 取header里的sign字段
|
|
39
49
|
const sign = req.headers['sign'];
|
|
@@ -41,35 +51,51 @@ async function createServer(port) {
|
|
|
41
51
|
const decodedTime = verifySign(sign);
|
|
42
52
|
const currentTime = Date.now();
|
|
43
53
|
|
|
44
|
-
if (decodedTime === null || Math.abs(currentTime - decodedTime) >
|
|
45
|
-
// 解码失败或者时间差大于9秒,返回403
|
|
54
|
+
if (decodedTime === null || Math.abs(currentTime - decodedTime) > 6000) {
|
|
46
55
|
res.writeHead(403, { 'Content-Type': 'text/plain' });
|
|
47
|
-
res.end('Forbidden: Invalid Sign
|
|
56
|
+
res.end('Forbidden: Invalid Sign');
|
|
48
57
|
return;
|
|
49
58
|
}
|
|
50
59
|
} else {
|
|
51
60
|
// 如果没有sign字段,返回403
|
|
52
61
|
res.writeHead(403, { 'Content-Type': 'text/plain' });
|
|
53
|
-
res.end('Forbidden: Missing Sign
|
|
62
|
+
res.end('Forbidden: Missing Sign');
|
|
54
63
|
return;
|
|
55
64
|
}
|
|
56
|
-
|
|
57
65
|
// 如果签名通过,继续处理请求
|
|
58
|
-
|
|
66
|
+
try {
|
|
67
|
+
let body = '';
|
|
68
|
+
await new Promise((resolve, reject) => {
|
|
69
|
+
// 监听请求数据
|
|
70
|
+
req.on('data', chunk => {
|
|
71
|
+
body += chunk;
|
|
72
|
+
});
|
|
73
|
+
req.on('error', (err) => {
|
|
74
|
+
reject(err)
|
|
75
|
+
});
|
|
76
|
+
req.on('end', async () => {
|
|
77
|
+
resolve()
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
await routes[routeKey](body, res);
|
|
81
|
+
} catch (e) {
|
|
82
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
83
|
+
res.end(String(isError(e) ? e.message:e));
|
|
84
|
+
}
|
|
59
85
|
} else {
|
|
60
86
|
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
61
|
-
res.end('Not Found
|
|
87
|
+
res.end('Not Found');
|
|
62
88
|
}
|
|
63
89
|
});
|
|
64
90
|
|
|
65
91
|
return await new Promise((resolve, reject) => {
|
|
66
92
|
server.listen(port, () => {
|
|
67
93
|
const address = server.address();
|
|
68
|
-
|
|
94
|
+
debug(`Server listening on port ${address.port}`);
|
|
69
95
|
resolve(server)
|
|
70
96
|
});
|
|
71
97
|
server.on('error', (err) => {
|
|
72
|
-
|
|
98
|
+
debug(`Error occurred: ${err.message}`);
|
|
73
99
|
reject(err)
|
|
74
100
|
});
|
|
75
101
|
})
|
|
@@ -82,13 +108,19 @@ async function startServer() {
|
|
|
82
108
|
try {
|
|
83
109
|
setting.server = await createServer(preferredPort);
|
|
84
110
|
} catch (e) {
|
|
85
|
-
|
|
111
|
+
debug("startServer failed, try other port")
|
|
86
112
|
setting.server = await createServer(0);
|
|
87
113
|
}
|
|
88
114
|
}
|
|
89
115
|
}
|
|
90
116
|
|
|
91
117
|
// 设置路由的方法
|
|
118
|
+
/*
|
|
119
|
+
exam:
|
|
120
|
+
method, url, fn, handler(req, res, fn, err)
|
|
121
|
+
method, url, fn(req, res, err), null
|
|
122
|
+
err: {code, msg}
|
|
123
|
+
*/
|
|
92
124
|
function setRoute(method, url, fn, handler = jsonHandle) {
|
|
93
125
|
startServer();
|
|
94
126
|
|
|
@@ -100,51 +132,29 @@ function setRoute(method, url, fn, handler = jsonHandle) {
|
|
|
100
132
|
} else {
|
|
101
133
|
routes[routeKey] = fn;
|
|
102
134
|
}
|
|
103
|
-
|
|
135
|
+
debug(`setRoute: ${method.toUpperCase()} ${url} ${handler ? handler.name:''}`);
|
|
104
136
|
return setting.server;
|
|
105
137
|
}
|
|
106
138
|
|
|
107
|
-
function
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
console.$log(`DelRoute: ${method.toUpperCase()} ${url}`);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function jsonHandle(req, res, fn) {
|
|
114
|
-
let body = '';
|
|
139
|
+
async function jsonHandle(req, res, fn) {
|
|
140
|
+
// 解析 JSON 请求体
|
|
141
|
+
const requestData = req ? JSON.parse(req):{};
|
|
115
142
|
|
|
116
|
-
//
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
try {
|
|
123
|
-
// 解析 JSON 请求体
|
|
124
|
-
const requestData = body ? JSON.parse(body):{};
|
|
125
|
-
|
|
126
|
-
// 创建响应 JSON 数据
|
|
127
|
-
let responseData = {};
|
|
128
|
-
let result = await fn(requestData, responseData);
|
|
129
|
-
if (vl(result)) {
|
|
130
|
-
responseData = result;
|
|
131
|
-
}
|
|
143
|
+
// 创建响应 JSON 数据
|
|
144
|
+
let responseData = {};
|
|
145
|
+
let result = await fn(requestData, responseData);
|
|
146
|
+
if (vl(result)) {
|
|
147
|
+
responseData = result;
|
|
148
|
+
}
|
|
132
149
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
} catch (error) {
|
|
138
|
-
// 错误处理:如果 JSON 解析失败
|
|
139
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
140
|
-
res.end(JSON.stringify({ error: 'Invalid JSON format' }));
|
|
141
|
-
}
|
|
142
|
-
});
|
|
150
|
+
// 设置响应头
|
|
151
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
152
|
+
// 返回 JSON 响应
|
|
153
|
+
res.end(JSON.stringify(responseData));
|
|
143
154
|
}
|
|
144
155
|
|
|
145
156
|
module.exports = {
|
|
146
157
|
setRoute,
|
|
147
|
-
delRoute,
|
|
148
158
|
jsonHandle,
|
|
149
159
|
createSign
|
|
150
160
|
};
|
package/deps/setting.js
CHANGED
package/deps/util.js
CHANGED
|
@@ -256,7 +256,7 @@ function createConsole(uniqueName) {
|
|
|
256
256
|
if ('debug' === key && !global.$DEBUG) {
|
|
257
257
|
return;
|
|
258
258
|
}
|
|
259
|
-
if (
|
|
259
|
+
if (uniqueName && (quite || isRunningInBackground())) {
|
|
260
260
|
if ((key === 'table' || key === 'nable') && typeof args[0] === "object") {
|
|
261
261
|
args = ['', ...args]
|
|
262
262
|
}
|
|
@@ -610,7 +610,9 @@ function randomInt(min,max){
|
|
|
610
610
|
}
|
|
611
611
|
|
|
612
612
|
async function timeLimit(proms, mills) {
|
|
613
|
-
let
|
|
613
|
+
let tIds = []
|
|
614
|
+
let result = await Promise.race([Promise.all(proms), sleep(mills, tIds)])
|
|
615
|
+
clearTimeout(tIds[0])
|
|
614
616
|
if (!result) {
|
|
615
617
|
throw new Error(`timeLimit: exceed ${mills} ms`)
|
|
616
618
|
}
|
|
@@ -700,7 +702,7 @@ function createLimitLogger(fileName, {
|
|
|
700
702
|
if (error) {
|
|
701
703
|
global.$newError = true;
|
|
702
704
|
}
|
|
703
|
-
text = `${pad(3, String(process.pid%1000), ' ')}> ${text}`
|
|
705
|
+
text = `${pad(3, String(process.pid%1000), process.pid >= 1000 ? '0':' ')}> ${text}`
|
|
704
706
|
if (time) {
|
|
705
707
|
text = `${timeStr('YYYY-MM-DD HH:mm:ss.SSS')} ${text}`
|
|
706
708
|
}
|
|
@@ -1246,6 +1248,9 @@ function setTips(key, value, onRm) {
|
|
|
1246
1248
|
function delTips(...keys) {
|
|
1247
1249
|
for (let key of Object.keys(setting.tips)) {
|
|
1248
1250
|
if (keys.length === 0) {
|
|
1251
|
+
if (key === 'DEBUG') {
|
|
1252
|
+
continue
|
|
1253
|
+
}
|
|
1249
1254
|
delete setting.tips[key]
|
|
1250
1255
|
tipsOnRmCallback(key)
|
|
1251
1256
|
} else if (keys.indexOf(key) !== -1) {
|
|
@@ -1255,6 +1260,9 @@ function delTips(...keys) {
|
|
|
1255
1260
|
}
|
|
1256
1261
|
if (keys.length === 0) {
|
|
1257
1262
|
for (let key of Object.keys(_tipsOnRm)) {
|
|
1263
|
+
if (key === 'DEBUG') {
|
|
1264
|
+
continue
|
|
1265
|
+
}
|
|
1258
1266
|
tipsOnRmCallback(key)
|
|
1259
1267
|
}
|
|
1260
1268
|
}
|
|
@@ -1550,8 +1558,10 @@ async function setCbText(str) {
|
|
|
1550
1558
|
fs.unlinkSync(copyFile)
|
|
1551
1559
|
}
|
|
1552
1560
|
|
|
1553
|
-
async function sleep(milliseconds) {
|
|
1554
|
-
return new Promise(resolve =>
|
|
1561
|
+
async function sleep(milliseconds, tIds = []) {
|
|
1562
|
+
return new Promise(resolve => {
|
|
1563
|
+
tIds.push(setTimeout(resolve, milliseconds))
|
|
1564
|
+
});
|
|
1555
1565
|
}
|
|
1556
1566
|
|
|
1557
1567
|
function splitArray(items, size) {
|
|
@@ -1844,7 +1854,7 @@ function md5(message) {
|
|
|
1844
1854
|
return crypto.createHash('md5').update(message).digest('hex');
|
|
1845
1855
|
}
|
|
1846
1856
|
|
|
1847
|
-
async function batchAsync(fns = [], asyncNum = 1, limitMs =
|
|
1857
|
+
async function batchAsync(fns = [], asyncNum = 1, limitMs = 49000) {
|
|
1848
1858
|
if (fns.length <= 0 || asyncNum < 1) {
|
|
1849
1859
|
return []
|
|
1850
1860
|
}
|
|
@@ -1910,12 +1920,8 @@ function errorTag(e, tag) {
|
|
|
1910
1920
|
if (!tag) {
|
|
1911
1921
|
return e;
|
|
1912
1922
|
}
|
|
1913
|
-
let newError = new Error(
|
|
1923
|
+
let newError = isError(e) ? e:new Error(e);
|
|
1914
1924
|
let stack = newError.stack
|
|
1915
|
-
if (isError(e)) {
|
|
1916
|
-
newError.name = e.name;
|
|
1917
|
-
stack = e.stack;
|
|
1918
|
-
}
|
|
1919
1925
|
newError.stack = stack + `\n at ${tag}`
|
|
1920
1926
|
return newError
|
|
1921
1927
|
}
|
|
@@ -2015,6 +2021,41 @@ function getJsirTypeKey(fileName) {
|
|
|
2015
2021
|
return fileName.split(/[^a-zA-Z]+/)[0] || ''
|
|
2016
2022
|
}
|
|
2017
2023
|
|
|
2024
|
+
function createDetachedProcess(cmd, args = []) {
|
|
2025
|
+
// 创建子进程
|
|
2026
|
+
const subprocess = spawn(cmd, [...args], {
|
|
2027
|
+
detached: true, // 独立运行
|
|
2028
|
+
stdio: ['ignore']
|
|
2029
|
+
});
|
|
2030
|
+
// 让主进程不再等待子进程
|
|
2031
|
+
subprocess.unref();
|
|
2032
|
+
console.log(`New ${cmd} process started with PID:`, subprocess.pid);
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
function interceptStdStreams() {
|
|
2036
|
+
// 保存原始的 stdout 和 stderr 写入方法
|
|
2037
|
+
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
2038
|
+
const originalStderrWrite = process.stderr.write.bind(process.stderr);
|
|
2039
|
+
|
|
2040
|
+
// 重写 process.stdout.write
|
|
2041
|
+
process.stdout.write = (chunk, ...args) => {
|
|
2042
|
+
if(setting.enterOutputs) {
|
|
2043
|
+
setting.enterOutputs.push(chunk.toString());
|
|
2044
|
+
} else {
|
|
2045
|
+
originalStdoutWrite(chunk, ...args); // 保留原始行为
|
|
2046
|
+
}
|
|
2047
|
+
};
|
|
2048
|
+
|
|
2049
|
+
// 重写 process.stderr.write
|
|
2050
|
+
process.stderr.write = (chunk, ...args) => {
|
|
2051
|
+
if(setting.enterOutputs) {
|
|
2052
|
+
setting.enterOutputs.push(chunk.toString());
|
|
2053
|
+
} else {
|
|
2054
|
+
originalStderrWrite(chunk, ...args); // 保留原始行为
|
|
2055
|
+
}
|
|
2056
|
+
};
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2018
2059
|
module.exports = {
|
|
2019
2060
|
wrapperJsirText,
|
|
2020
2061
|
run,
|
|
@@ -2121,5 +2162,7 @@ module.exports = {
|
|
|
2121
2162
|
getRoomsDir,
|
|
2122
2163
|
getDataDir,
|
|
2123
2164
|
getJsirTypeKey,
|
|
2124
|
-
isRunningInBackground
|
|
2165
|
+
isRunningInBackground,
|
|
2166
|
+
createDetachedProcess,
|
|
2167
|
+
interceptStdStreams
|
|
2125
2168
|
}
|