jsir 2.4.0 → 2.4.2
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 +128 -17
- package/deps/evalCode.js +2 -6
- package/deps/room.js +138 -75
- package/deps/server.js +51 -47
- package/deps/setting.js +14 -4
- package/deps/util.js +43 -10
- 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,9 @@ 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");
|
|
35
|
+
const {isRunningInBackground} = require("../deps/util");
|
|
32
36
|
|
|
33
37
|
let lastFilterArg = '';
|
|
34
38
|
let _cmdMapFile = setting.name + 'CmdMap.json'
|
|
@@ -157,6 +161,7 @@ function checkWorkspaces() {
|
|
|
157
161
|
}
|
|
158
162
|
|
|
159
163
|
async function start() {
|
|
164
|
+
interceptStdStreams()
|
|
160
165
|
checkWorkspaces()
|
|
161
166
|
initWorkspace()
|
|
162
167
|
|
|
@@ -391,7 +396,7 @@ function _nextLine(callback, promptStr, hidden, resolve, end, isText) {
|
|
|
391
396
|
_haveWrapperInput = true;
|
|
392
397
|
closeRl()
|
|
393
398
|
inputStr = inputStr.replace(/\s+$/, '');
|
|
394
|
-
if (!isText && /^\d+$/.test(inputStr) && /^\s+/.test(textLine)) {
|
|
399
|
+
if (!isText && /^\d+$/.test(inputStr) && /^\s+/.test(textLine) && (!callback || callback === wrapperInput)) {
|
|
395
400
|
inputStr = '.e ' + inputStr;
|
|
396
401
|
}
|
|
397
402
|
let pro = (callback || wrapperInput)(inputStr);
|
|
@@ -580,11 +585,11 @@ function wrapperAlias(str) {
|
|
|
580
585
|
async function _wrapperInput(str) {
|
|
581
586
|
_haveWrapperInput = true;
|
|
582
587
|
global.$newInput = true;
|
|
583
|
-
|
|
584
588
|
str = trim(str)
|
|
585
589
|
if (!str) {
|
|
586
590
|
return;
|
|
587
591
|
}
|
|
592
|
+
global.$newError = false;
|
|
588
593
|
str = wrapperAlias(str);
|
|
589
594
|
|
|
590
595
|
if (/^[`'"]/.test(str)) {
|
|
@@ -624,10 +629,16 @@ async function _wrapperInput(str) {
|
|
|
624
629
|
let firstName = getJsirTypeKey(fileName)
|
|
625
630
|
if (firstName === setting.fileKey) {
|
|
626
631
|
await workFile(uniqueName)
|
|
627
|
-
} else if (firstName
|
|
628
|
-
console.log(String(fs.readFileSync(path)));
|
|
629
|
-
} else {
|
|
632
|
+
} else if (firstName === setting.exeKey) {
|
|
630
633
|
await runCmd(str)
|
|
634
|
+
} else if (firstName === setting.initKey) {
|
|
635
|
+
if (strs[1] === '-') {
|
|
636
|
+
await offServer(uniqueName)
|
|
637
|
+
} else {
|
|
638
|
+
await joinServer(uniqueName)
|
|
639
|
+
}
|
|
640
|
+
} else {
|
|
641
|
+
console.log(String(fs.readFileSync(path)));
|
|
631
642
|
}
|
|
632
643
|
} else {
|
|
633
644
|
await runCmd(str)
|
|
@@ -635,6 +646,44 @@ async function _wrapperInput(str) {
|
|
|
635
646
|
}
|
|
636
647
|
}
|
|
637
648
|
|
|
649
|
+
async function joinServer(uniqueName) {
|
|
650
|
+
let exportLib = await _requireSource(setting.defaultSpace, uniqueName, true);
|
|
651
|
+
let asyncFnSize = 0
|
|
652
|
+
for (let key of Object.keys(exportLib)) {
|
|
653
|
+
let val = exportLib[key];
|
|
654
|
+
if (getType(val) !== 'AsyncFunction') {
|
|
655
|
+
throw `service function must be async`;
|
|
656
|
+
}
|
|
657
|
+
asyncFnSize ++
|
|
658
|
+
}
|
|
659
|
+
if (asyncFnSize <= 0) {
|
|
660
|
+
console.warn('no items');
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
let pair = parseUniqueName(uniqueName)
|
|
664
|
+
for (let key of Object.keys(exportLib)) {
|
|
665
|
+
let val = exportLib[key];
|
|
666
|
+
server.setRoute('post', `/${pair[0]}/${trimJsirFileName(pair[1])}/${key}`,async (req, res) => {
|
|
667
|
+
res.result = await val(...req);
|
|
668
|
+
}, server.jsonHandle)
|
|
669
|
+
}
|
|
670
|
+
console.msg(uniqueName, 'has become a service.');
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
async function offServer(uniqueName) {
|
|
674
|
+
let pair = parseUniqueName(uniqueName)
|
|
675
|
+
let service = `${pair[0]}/${trimJsirFileName(pair[1])}`;
|
|
676
|
+
|
|
677
|
+
for (let key of Object.keys(setting.routes)) {
|
|
678
|
+
let ss = key.split("/").map(trim).filter(i => i);
|
|
679
|
+
let keyService = ss[1] + '/' + ss[2]
|
|
680
|
+
if (keyService === service) {
|
|
681
|
+
delete setting.routes[key]
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
console.msg(uniqueName, 'service has been offline.');
|
|
685
|
+
}
|
|
686
|
+
|
|
638
687
|
async function dealStartGlobalMode(rows, items, text) {
|
|
639
688
|
console.info('global search mode');
|
|
640
689
|
rows.push(...await dealStarCase(await evalText('return $context'), items, '$context'))
|
|
@@ -978,18 +1027,20 @@ async function showRooms() {
|
|
|
978
1027
|
})
|
|
979
1028
|
for (let key of Object.keys(room.jsirs)) {
|
|
980
1029
|
let jsir = room.jsirs[key]
|
|
1030
|
+
let pidStr = (process.pid === jsir.pid ? "*" : " ") + jsir.pid;
|
|
981
1031
|
results.push({
|
|
982
1032
|
mid: i,
|
|
983
1033
|
name: (isLocal ? "*" : " ") + room.name,
|
|
984
1034
|
node: room.selfNode,
|
|
985
1035
|
active: room.active,
|
|
986
|
-
pid:
|
|
1036
|
+
pid: jsir.newError ? errorStr(pidStr):pidStr,
|
|
987
1037
|
space: jsir.space,
|
|
988
1038
|
running: jsir.active,
|
|
989
1039
|
port: jsir.port,
|
|
990
1040
|
back: jsir.back,
|
|
991
1041
|
busy: jsir.busy,
|
|
992
|
-
tips: Object.keys(jsir.tips).map(i => i + ': ' + jsir.tips[i]).join('\n')
|
|
1042
|
+
tips: Object.keys(jsir.tips).map(i => i + ': ' + jsir.tips[i]).join('\n'),
|
|
1043
|
+
services: Object.keys(jsir.services || []).map(msgStr).join('\n')
|
|
993
1044
|
});
|
|
994
1045
|
}
|
|
995
1046
|
}
|
|
@@ -1392,7 +1443,11 @@ const keywordDef = {
|
|
|
1392
1443
|
if (args.length > 1) {
|
|
1393
1444
|
_args = args.slice(1)
|
|
1394
1445
|
}
|
|
1395
|
-
|
|
1446
|
+
if (isRunningInBackground()) {
|
|
1447
|
+
console.log(await e(`cd "${setting.workspaceMap[setting.defaultSpace]}";${args.join(' ')}`))
|
|
1448
|
+
} else {
|
|
1449
|
+
await eia(`cd "${setting.workspaceMap[setting.defaultSpace]}";${cmd}`, _args, true)
|
|
1450
|
+
}
|
|
1396
1451
|
},
|
|
1397
1452
|
short: 'E'
|
|
1398
1453
|
},
|
|
@@ -1443,17 +1498,48 @@ const keywordDef = {
|
|
|
1443
1498
|
comment: 'manage room ([node])',
|
|
1444
1499
|
exeFn: async (args) => {
|
|
1445
1500
|
await room.syncSetting();
|
|
1446
|
-
if (args
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1501
|
+
if (args.length > 0) {
|
|
1502
|
+
let have = false;
|
|
1503
|
+
let tasks = []
|
|
1504
|
+
for (let room of setting.rooms) {
|
|
1505
|
+
for (let pid of Object.keys(room.jsirs || {})) {
|
|
1506
|
+
if (args.indexOf(room.selfNode) === -1 && args.indexOf(pid) === -1) {
|
|
1507
|
+
continue;
|
|
1508
|
+
}
|
|
1509
|
+
if (setting.selfRoom.selfNode === room.selfNode && String(process.pid) === pid) {
|
|
1510
|
+
continue;
|
|
1511
|
+
}
|
|
1512
|
+
have = true;
|
|
1513
|
+
tasks.push(async (input) => {
|
|
1514
|
+
let jsir = room.jsirs[pid];
|
|
1515
|
+
console.log(infoStr(`[${pid}]`))
|
|
1516
|
+
let resp = await reqNode(room.selfNode, "post", "/enter", jsir.port, JSON.stringify({input}))
|
|
1517
|
+
console.log(JSON.parse(resp).output)
|
|
1518
|
+
})
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
let input = have ? await nextLine(i => i):''
|
|
1522
|
+
if (vl(input)) {
|
|
1523
|
+
for (let task of tasks) {
|
|
1524
|
+
try {
|
|
1525
|
+
await task(input)
|
|
1526
|
+
} catch (e) {
|
|
1527
|
+
console.error(e)
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1451
1530
|
}
|
|
1452
1531
|
} else {
|
|
1453
1532
|
await showRooms();
|
|
1454
1533
|
}
|
|
1455
1534
|
},
|
|
1456
1535
|
short: 'm'
|
|
1536
|
+
},
|
|
1537
|
+
nsir: {
|
|
1538
|
+
comment: 'new jsir',
|
|
1539
|
+
exeFn: async (args) => {
|
|
1540
|
+
await createDetachedProcess('jsir', ['.p'])
|
|
1541
|
+
},
|
|
1542
|
+
short: 'N'
|
|
1457
1543
|
}
|
|
1458
1544
|
}
|
|
1459
1545
|
|
|
@@ -2050,7 +2136,7 @@ function filterRequire(currSpace, cmdMatchStr) {
|
|
|
2050
2136
|
return cmds[0];
|
|
2051
2137
|
}
|
|
2052
2138
|
|
|
2053
|
-
async function _requireSource(currSpace, cmdMatchStr) {
|
|
2139
|
+
async function _requireSource(currSpace, cmdMatchStr, force = false) {
|
|
2054
2140
|
let result
|
|
2055
2141
|
let uniqueName = filterRequire(currSpace, cmdMatchStr);
|
|
2056
2142
|
let path = getFullPath(uniqueName)
|
|
@@ -2058,7 +2144,32 @@ async function _requireSource(currSpace, cmdMatchStr) {
|
|
|
2058
2144
|
let pair = parseUniqueName(uniqueName)
|
|
2059
2145
|
let typeKey = getJsirTypeKey(pair[1]);
|
|
2060
2146
|
if (typeKey === setting.initKey) {
|
|
2061
|
-
|
|
2147
|
+
let pair = parseUniqueName(uniqueName)
|
|
2148
|
+
let serviceKey = `${pair[0]}/${trimJsirFileName(pair[1])}`
|
|
2149
|
+
if (setting.defaultSpace !== 'local' && !force && setting.services[serviceKey]) {
|
|
2150
|
+
let serviceObj = {};
|
|
2151
|
+
for (let fn of setting.services[serviceKey]) {
|
|
2152
|
+
serviceObj[fn] = async (...args) => {
|
|
2153
|
+
let key = `${serviceKey}/${fn}`;
|
|
2154
|
+
let targets = setting.serviceFns[key] || []
|
|
2155
|
+
targets = targets
|
|
2156
|
+
.filter(i => i.active)
|
|
2157
|
+
.filter(i => !room.isDisableConnect(i.node, i.port));
|
|
2158
|
+
let target = null;
|
|
2159
|
+
if (targets.length > 0) {
|
|
2160
|
+
target = room.busyPick(targets);
|
|
2161
|
+
} else {
|
|
2162
|
+
throw `service function ${key} not found`;
|
|
2163
|
+
}
|
|
2164
|
+
let resp = await room.reqNode(target.node, 'post',
|
|
2165
|
+
`/${pair[0]}/${encodeURIComponent(trimJsirFileName(pair[1]))}/${fn}`, target.port, JSON.stringify(args))
|
|
2166
|
+
return JSON.parse(resp).result;
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
return serviceObj
|
|
2170
|
+
} else {
|
|
2171
|
+
result = await evalText(text, uniqueName)
|
|
2172
|
+
}
|
|
2062
2173
|
} else if (typeKey === setting.exeKey) {
|
|
2063
2174
|
result = async (argsStr) => {
|
|
2064
2175
|
return await runCmd(trim(argsStr), uniqueName, text)
|
|
@@ -2148,7 +2259,7 @@ process.on('beforeExit', function () {
|
|
|
2148
2259
|
delTips();
|
|
2149
2260
|
} else {
|
|
2150
2261
|
nextLine();
|
|
2151
|
-
room.onRoom()
|
|
2262
|
+
room.onRoom(wrapperInput)
|
|
2152
2263
|
}
|
|
2153
2264
|
});
|
|
2154
2265
|
|
package/deps/evalCode.js
CHANGED
|
@@ -37,10 +37,6 @@ module.exports = async ($text = '', $cmdName = '', $args = [],
|
|
|
37
37
|
$homeDir, $lib, $cmdMap
|
|
38
38
|
}
|
|
39
39
|
let console = $lib.createConsole($cmdName);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
})()`)
|
|
43
|
-
} catch (e) {
|
|
44
|
-
console.error(e)
|
|
45
|
-
}
|
|
40
|
+
return await eval(`(async ()=>{${$text};
|
|
41
|
+
})()`)
|
|
46
42
|
}
|
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, debugStr
|
|
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"
|
|
@@ -35,11 +35,28 @@ function isPidAlive(pid) {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
function onRoom() {
|
|
38
|
+
function onRoom(wrapperInput) {
|
|
39
39
|
if (!setting.roomTid[0]) {
|
|
40
40
|
try {
|
|
41
41
|
setRoute("post", "/", (req, res) => setting.selfRoom)
|
|
42
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
|
+
})
|
|
43
60
|
} catch (e) {
|
|
44
61
|
debug("initRoute failed", e)
|
|
45
62
|
}
|
|
@@ -57,7 +74,7 @@ function _onRoom() {
|
|
|
57
74
|
if (setting.roomTid[0]) {
|
|
58
75
|
_onRoom();
|
|
59
76
|
}
|
|
60
|
-
},
|
|
77
|
+
}, setting.roomTimer)
|
|
61
78
|
}
|
|
62
79
|
|
|
63
80
|
function offRoom() {
|
|
@@ -120,7 +137,12 @@ async function syncRooms() {
|
|
|
120
137
|
fns.push(async () => {
|
|
121
138
|
let respBody = ''
|
|
122
139
|
try {
|
|
123
|
-
|
|
140
|
+
try {
|
|
141
|
+
respBody = await reqNode(node, "get", "/");
|
|
142
|
+
} catch (e) {
|
|
143
|
+
debug(`sync ${node} failed:`, e);
|
|
144
|
+
respBody = await reqNode(node, "get", "/", setting.defaultPort);
|
|
145
|
+
}
|
|
124
146
|
syncRooms.push(JSON.parse(respBody))
|
|
125
147
|
debug(`sync ${node} success`);
|
|
126
148
|
} catch (e) {
|
|
@@ -128,7 +150,7 @@ async function syncRooms() {
|
|
|
128
150
|
}
|
|
129
151
|
})
|
|
130
152
|
}
|
|
131
|
-
await batchAsync(fns,
|
|
153
|
+
await batchAsync(fns, 33);
|
|
132
154
|
await fileLock(roomsDirLockKey, async () => {
|
|
133
155
|
for (let syncRoom of [...syncRooms]) {
|
|
134
156
|
if (syncRoom.selfNode) {
|
|
@@ -186,6 +208,30 @@ async function _syncSetting(room) {
|
|
|
186
208
|
jsir.active = Date.now() - (jsir.lastUpdateTime || 0) <= 9000)
|
|
187
209
|
return room;
|
|
188
210
|
});
|
|
211
|
+
|
|
212
|
+
setting.services = {}
|
|
213
|
+
setting.serviceFns = {}
|
|
214
|
+
for (const room of setting.rooms) {
|
|
215
|
+
for (const pid of Object.keys(room.jsirs || [])) {
|
|
216
|
+
let jsir = room.jsirs[pid];
|
|
217
|
+
for (const service of Object.keys(jsir.services || [])) {
|
|
218
|
+
let serviceFns = jsir.services[service] || [];
|
|
219
|
+
|
|
220
|
+
let existFns = setting.services[service] || [];
|
|
221
|
+
existFns.push(...serviceFns)
|
|
222
|
+
setting.services[service] = [...new Set(existFns)]
|
|
223
|
+
|
|
224
|
+
for (const fn of serviceFns) {
|
|
225
|
+
getOr(setting.serviceFns, service + '/' + fn, []).push({
|
|
226
|
+
node: room.selfNode,
|
|
227
|
+
port: jsir.port,
|
|
228
|
+
busy: jsir.busy,
|
|
229
|
+
active: jsir.active
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
189
235
|
}
|
|
190
236
|
|
|
191
237
|
async function initRoom() {
|
|
@@ -205,6 +251,10 @@ async function initRoom() {
|
|
|
205
251
|
await syncSetting();
|
|
206
252
|
}
|
|
207
253
|
|
|
254
|
+
/*
|
|
255
|
+
最小为0
|
|
256
|
+
100 为1 毫秒
|
|
257
|
+
*/
|
|
208
258
|
async function getEventLoopDelay() {
|
|
209
259
|
const start = process.hrtime.bigint();
|
|
210
260
|
const delay = 1; // 用于测量的短暂延迟(单位:毫秒)
|
|
@@ -219,6 +269,15 @@ async function initRoomJsir(room) {
|
|
|
219
269
|
room.jsirs = Object.fromEntries(
|
|
220
270
|
Object.entries(room.jsirs || {}).filter(([pid]) => isPidAlive(pid))
|
|
221
271
|
);
|
|
272
|
+
|
|
273
|
+
let services = {}
|
|
274
|
+
for (let key of Object.keys(setting.routes)) {
|
|
275
|
+
let ss = key.split("/").map(trim).filter(i => i);
|
|
276
|
+
if (ss.length > 3) {
|
|
277
|
+
getOr(services, ss[1] + '/' + ss[2], []).push(ss[3])
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
let lastJsir = room.jsirs[process.pid]
|
|
222
281
|
room.jsirs[process.pid] = {
|
|
223
282
|
pid: process.pid,
|
|
224
283
|
space: setting.defaultSpace,
|
|
@@ -226,10 +285,12 @@ async function initRoomJsir(room) {
|
|
|
226
285
|
obj[key] = getValTips()[index];
|
|
227
286
|
return obj;
|
|
228
287
|
}, {}),
|
|
229
|
-
busy: await getEventLoopDelay(),
|
|
288
|
+
busy: await getEventLoopDelay() || (lastJsir ? lastJsir.busy:0),
|
|
230
289
|
back: isRunningInBackground(),
|
|
231
290
|
lastUpdateTime: Date.now(),
|
|
232
|
-
port: setting.server ? setting.server.address().port:null
|
|
291
|
+
port: setting.server ? setting.server.address().port:null,
|
|
292
|
+
services: services,
|
|
293
|
+
newError: global.$newError
|
|
233
294
|
}
|
|
234
295
|
}
|
|
235
296
|
|
|
@@ -263,62 +324,21 @@ function getLocalIPs() {
|
|
|
263
324
|
return result;
|
|
264
325
|
}
|
|
265
326
|
|
|
266
|
-
async function sshEnables(ips) {
|
|
267
|
-
// 检查开放 22 端口的 IP
|
|
268
|
-
const sshEnabledIPs = [];
|
|
269
|
-
const portCheckPromises = ips.map((ip) =>
|
|
270
|
-
checkPortOpen(ip, 22).then((isOpen) => {
|
|
271
|
-
if (isOpen) {
|
|
272
|
-
sshEnabledIPs.push(ip);
|
|
273
|
-
}
|
|
274
|
-
})
|
|
275
|
-
);
|
|
276
|
-
|
|
277
|
-
await Promise.all(portCheckPromises);
|
|
278
|
-
return sshEnabledIPs;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* 检查指定 IP 的指定端口是否开放
|
|
283
|
-
* @param {string} ip - 目标 IP 地址
|
|
284
|
-
* @param {number} port - 目标端口
|
|
285
|
-
* @returns {Promise<boolean>} - 是否开放
|
|
286
|
-
*/
|
|
287
|
-
function checkPortOpen(ip, port) {
|
|
288
|
-
return new Promise((resolve) => {
|
|
289
|
-
const socket = new net.Socket();
|
|
290
|
-
socket.setTimeout(1000); // 设置超时时间
|
|
291
|
-
|
|
292
|
-
socket
|
|
293
|
-
.connect(port, ip, () => {
|
|
294
|
-
socket.destroy(); // 成功连接后关闭 socket
|
|
295
|
-
resolve(true);
|
|
296
|
-
})
|
|
297
|
-
.on("error", () => {
|
|
298
|
-
socket.destroy(); // 遇到错误时关闭 socket
|
|
299
|
-
resolve(false);
|
|
300
|
-
})
|
|
301
|
-
.on("timeout", () => {
|
|
302
|
-
socket.destroy(); // 超时时关闭 socket
|
|
303
|
-
resolve(false);
|
|
304
|
-
});
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
|
|
308
327
|
function isDisableConnect(node, port) {
|
|
309
328
|
let disableTime = setting.disableConnect[node + ":" + port] || 0;
|
|
310
329
|
return disableTime > Date.now()
|
|
311
330
|
}
|
|
312
331
|
|
|
313
|
-
async function reqNode(node, method, url) {
|
|
314
|
-
let port = 52108;
|
|
332
|
+
async function reqNode(node, method, url, port, body) {
|
|
315
333
|
let room = setting.rooms.filter(i => i.selfNode === node)[0]
|
|
316
|
-
if (
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
334
|
+
if (!port) {
|
|
335
|
+
port = setting.defaultPort;
|
|
336
|
+
if (room) {
|
|
337
|
+
let activeJsir = Object.values(room.jsirs)
|
|
338
|
+
.filter(i => !isDisableConnect(node, i.port));
|
|
339
|
+
if (activeJsir.length > 0) {
|
|
340
|
+
port = busyPick(activeJsir).port;
|
|
341
|
+
}
|
|
322
342
|
}
|
|
323
343
|
}
|
|
324
344
|
let opt = {
|
|
@@ -332,33 +352,76 @@ async function reqNode(node, method, url) {
|
|
|
332
352
|
};
|
|
333
353
|
debug('reqRoom', JSON.stringify(opt))
|
|
334
354
|
return await new Promise((resolve, reject) => {
|
|
335
|
-
|
|
336
|
-
let
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
355
|
+
try {
|
|
356
|
+
let timeoutMs = setting.reqNodeTimeouts[url] || setting.reqNodeDefaultTimeout;
|
|
357
|
+
let timeout = null
|
|
358
|
+
|
|
359
|
+
const req = http.request(opt, (res) => {
|
|
360
|
+
let data = '';
|
|
361
|
+
// 数据块接收
|
|
362
|
+
res.on('data', (chunk) => {
|
|
363
|
+
data += chunk;
|
|
364
|
+
});
|
|
365
|
+
// 响应结束
|
|
366
|
+
res.on('end', () => {
|
|
367
|
+
clearTimeout(timeout)
|
|
368
|
+
// 检查响应状态码
|
|
369
|
+
if (res.statusCode !== 200) {
|
|
370
|
+
reject(errorTag(new Error(data), decodeURIComponent(url)));
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
resolve(data)
|
|
374
|
+
});
|
|
340
375
|
});
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
376
|
+
|
|
377
|
+
// 设置超时逻辑
|
|
378
|
+
timeout = setTimeout(() => {
|
|
379
|
+
req.destroy(); // 中断请求
|
|
380
|
+
}, timeoutMs); // 10秒超时
|
|
381
|
+
|
|
382
|
+
// 错误处理
|
|
383
|
+
req.on('error', (e) => {
|
|
384
|
+
clearTimeout(timeout); // 清除超时定时器
|
|
385
|
+
if (room) {
|
|
386
|
+
setting.disableConnect[node + ":" + port] = Date.now() + setting.disableConnectLimit
|
|
387
|
+
}
|
|
388
|
+
reject(e)
|
|
344
389
|
});
|
|
345
|
-
});
|
|
346
390
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
if (room) {
|
|
350
|
-
setting.disableConnect[node + ":" + port] = Date.now() + 9000
|
|
391
|
+
if (body) {
|
|
392
|
+
req.write(body)
|
|
351
393
|
}
|
|
394
|
+
// 结束请求
|
|
395
|
+
req.end();
|
|
396
|
+
} catch (e) {
|
|
352
397
|
reject(e)
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// 结束请求
|
|
356
|
-
req.end();
|
|
398
|
+
}
|
|
357
399
|
})
|
|
358
400
|
}
|
|
359
401
|
|
|
402
|
+
function busyPick(busyItems) {
|
|
403
|
+
if (busyItems.length === 1) {
|
|
404
|
+
return busyItems[0]
|
|
405
|
+
}
|
|
406
|
+
// 反转权重:计算 1 / busy 值作为权重
|
|
407
|
+
const weights = busyItems.map(item => 1 / (item.busy || 1));
|
|
408
|
+
const totalWeight = weights.reduce((sum, weight) => sum + weight, 0);
|
|
409
|
+
const random = Math.random() * totalWeight;
|
|
410
|
+
|
|
411
|
+
let cumulativeWeight = 0;
|
|
412
|
+
for (let i = 0; i < busyItems.length; i++) {
|
|
413
|
+
cumulativeWeight += weights[i];
|
|
414
|
+
if (random < cumulativeWeight) {
|
|
415
|
+
return busyItems[i]; // 选中反转权重命中的对象
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
360
420
|
module.exports = {
|
|
361
421
|
onRoom,
|
|
362
422
|
offRoom,
|
|
363
|
-
syncSetting
|
|
423
|
+
syncSetting,
|
|
424
|
+
reqNode,
|
|
425
|
+
isDisableConnect,
|
|
426
|
+
busyPick
|
|
364
427
|
}
|
package/deps/server.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
const http = require('http');
|
|
2
|
-
const {createConsole, vl, aesDecipher, md5, aesCipher, debugStr} = 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
|
-
const preferredPort =
|
|
6
|
+
const preferredPort = setting.defaultPort;
|
|
7
7
|
// 路由存储
|
|
8
|
-
const routes =
|
|
8
|
+
const routes = setting.routes;
|
|
9
9
|
let invokeStart = false;
|
|
10
10
|
|
|
11
11
|
function debug(...args) {
|
|
@@ -36,10 +36,14 @@ function createSign() {
|
|
|
36
36
|
async function createServer(port) {
|
|
37
37
|
const server = http.createServer(async (req, res) => {
|
|
38
38
|
const method = req.method.toLowerCase();
|
|
39
|
-
const url = req.url;
|
|
39
|
+
const url = decodeURIComponent(req.url);
|
|
40
40
|
|
|
41
|
+
// 获取客户端的 IP 地址
|
|
42
|
+
const clientIp = req.socket.remoteAddress || req.connection.remoteAddress;
|
|
41
43
|
// 查找对应的路由处理函数
|
|
42
44
|
const routeKey = `${method} ${url}`;
|
|
45
|
+
debug(`Received ${routeKey} from ${clientIp}`);
|
|
46
|
+
|
|
43
47
|
if (routes[routeKey]) {
|
|
44
48
|
// 取header里的sign字段
|
|
45
49
|
const sign = req.headers['sign'];
|
|
@@ -47,24 +51,40 @@ async function createServer(port) {
|
|
|
47
51
|
const decodedTime = verifySign(sign);
|
|
48
52
|
const currentTime = Date.now();
|
|
49
53
|
|
|
50
|
-
if (decodedTime === null || Math.abs(currentTime - decodedTime) >
|
|
51
|
-
// 解码失败或者时间差大于9秒,返回403
|
|
54
|
+
if (decodedTime === null || Math.abs(currentTime - decodedTime) > setting.serverSignExpire) {
|
|
52
55
|
res.writeHead(403, { 'Content-Type': 'text/plain' });
|
|
53
|
-
res.end('Forbidden: Invalid Sign
|
|
56
|
+
res.end('Forbidden: Invalid Sign');
|
|
54
57
|
return;
|
|
55
58
|
}
|
|
56
59
|
} else {
|
|
57
60
|
// 如果没有sign字段,返回403
|
|
58
61
|
res.writeHead(403, { 'Content-Type': 'text/plain' });
|
|
59
|
-
res.end('Forbidden: Missing Sign
|
|
62
|
+
res.end('Forbidden: Missing Sign');
|
|
60
63
|
return;
|
|
61
64
|
}
|
|
62
|
-
|
|
63
65
|
// 如果签名通过,继续处理请求
|
|
64
|
-
|
|
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
|
+
}
|
|
65
85
|
} else {
|
|
66
86
|
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
67
|
-
res.end('Not Found
|
|
87
|
+
res.end('Not Found');
|
|
68
88
|
}
|
|
69
89
|
});
|
|
70
90
|
|
|
@@ -95,6 +115,12 @@ async function startServer() {
|
|
|
95
115
|
}
|
|
96
116
|
|
|
97
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
|
+
*/
|
|
98
124
|
function setRoute(method, url, fn, handler = jsonHandle) {
|
|
99
125
|
startServer();
|
|
100
126
|
|
|
@@ -106,51 +132,29 @@ function setRoute(method, url, fn, handler = jsonHandle) {
|
|
|
106
132
|
} else {
|
|
107
133
|
routes[routeKey] = fn;
|
|
108
134
|
}
|
|
109
|
-
debug(`
|
|
135
|
+
debug(`setRoute: ${method.toUpperCase()} ${url} ${handler ? handler.name:''}`);
|
|
110
136
|
return setting.server;
|
|
111
137
|
}
|
|
112
138
|
|
|
113
|
-
function
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
debug(`DelRoute: ${method.toUpperCase()} ${url}`);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function jsonHandle(req, res, fn) {
|
|
120
|
-
let body = '';
|
|
121
|
-
|
|
122
|
-
// 监听请求数据
|
|
123
|
-
req.on('data', chunk => {
|
|
124
|
-
body += chunk;
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
req.on('end', async () => {
|
|
128
|
-
try {
|
|
129
|
-
// 解析 JSON 请求体
|
|
130
|
-
const requestData = body ? JSON.parse(body):{};
|
|
139
|
+
async function jsonHandle(req, res, fn) {
|
|
140
|
+
// 解析 JSON 请求体
|
|
141
|
+
const requestData = req ? JSON.parse(req):{};
|
|
131
142
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
143
|
+
// 创建响应 JSON 数据
|
|
144
|
+
let responseData = {};
|
|
145
|
+
let result = await fn(requestData, responseData);
|
|
146
|
+
if (vl(result)) {
|
|
147
|
+
responseData = result;
|
|
148
|
+
}
|
|
138
149
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
} catch (error) {
|
|
144
|
-
// 错误处理:如果 JSON 解析失败
|
|
145
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
146
|
-
res.end(JSON.stringify({ error: 'Invalid JSON format' }));
|
|
147
|
-
}
|
|
148
|
-
});
|
|
150
|
+
// 设置响应头
|
|
151
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
152
|
+
// 返回 JSON 响应
|
|
153
|
+
res.end(JSON.stringify(responseData));
|
|
149
154
|
}
|
|
150
155
|
|
|
151
156
|
module.exports = {
|
|
152
157
|
setRoute,
|
|
153
|
-
delRoute,
|
|
154
158
|
jsonHandle,
|
|
155
159
|
createSign
|
|
156
160
|
};
|
package/deps/setting.js
CHANGED
|
@@ -8,8 +8,6 @@ fileType[exeKey] = "exe";
|
|
|
8
8
|
fileType[initKey] = "init";
|
|
9
9
|
fileType[fileKey] = "file";
|
|
10
10
|
|
|
11
|
-
const defaultType = 'note';
|
|
12
|
-
|
|
13
11
|
module.exports = {
|
|
14
12
|
name: 'jsir',
|
|
15
13
|
libDataDir: '.jsirData',
|
|
@@ -17,7 +15,7 @@ module.exports = {
|
|
|
17
15
|
exeKey,
|
|
18
16
|
initKey,
|
|
19
17
|
fileKey,
|
|
20
|
-
defaultType,
|
|
18
|
+
defaultType: 'note',
|
|
21
19
|
packages: {},
|
|
22
20
|
tips: {},
|
|
23
21
|
defaultSpace: 'local',
|
|
@@ -27,5 +25,17 @@ module.exports = {
|
|
|
27
25
|
selfRoom: {},
|
|
28
26
|
rooms: [],
|
|
29
27
|
server: null,
|
|
30
|
-
disableConnect: {}
|
|
28
|
+
disableConnect: {},
|
|
29
|
+
disableConnectLimit: 6000,
|
|
30
|
+
routes: {},
|
|
31
|
+
services: {},
|
|
32
|
+
serviceFns: {},
|
|
33
|
+
enterOutputs: null,
|
|
34
|
+
defaultPort: 52108,
|
|
35
|
+
reqNodeDefaultTimeout: 9000,
|
|
36
|
+
reqNodeTimeouts: {
|
|
37
|
+
'/': 3000
|
|
38
|
+
},
|
|
39
|
+
roomTimer: 1000,
|
|
40
|
+
serverSignExpire: 6000
|
|
31
41
|
}
|
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
|
}
|
|
@@ -702,7 +702,7 @@ function createLimitLogger(fileName, {
|
|
|
702
702
|
if (error) {
|
|
703
703
|
global.$newError = true;
|
|
704
704
|
}
|
|
705
|
-
text = `${pad(3, String(process.pid%1000), ' ')}> ${text}`
|
|
705
|
+
text = `${pad(3, String(process.pid%1000), process.pid >= 1000 ? '0':' ')}> ${text}`
|
|
706
706
|
if (time) {
|
|
707
707
|
text = `${timeStr('YYYY-MM-DD HH:mm:ss.SSS')} ${text}`
|
|
708
708
|
}
|
|
@@ -1204,7 +1204,7 @@ async function _fileLock(key, fn, expireMs = 49000) {
|
|
|
1204
1204
|
|
|
1205
1205
|
async function fileLock(key, fn, wait = true, expireMs = 49000) {
|
|
1206
1206
|
`
|
|
1207
|
-
文件锁,
|
|
1207
|
+
文件锁, 默认一直等待expireMs,直到加锁成功执行fn
|
|
1208
1208
|
wait = false, 加锁失败则不执行fn
|
|
1209
1209
|
return void
|
|
1210
1210
|
`
|
|
@@ -1854,7 +1854,7 @@ function md5(message) {
|
|
|
1854
1854
|
return crypto.createHash('md5').update(message).digest('hex');
|
|
1855
1855
|
}
|
|
1856
1856
|
|
|
1857
|
-
async function batchAsync(fns = [], asyncNum = 1, limitMs =
|
|
1857
|
+
async function batchAsync(fns = [], asyncNum = 1, limitMs = 49000) {
|
|
1858
1858
|
if (fns.length <= 0 || asyncNum < 1) {
|
|
1859
1859
|
return []
|
|
1860
1860
|
}
|
|
@@ -1920,12 +1920,8 @@ function errorTag(e, tag) {
|
|
|
1920
1920
|
if (!tag) {
|
|
1921
1921
|
return e;
|
|
1922
1922
|
}
|
|
1923
|
-
let newError = new Error(
|
|
1923
|
+
let newError = isError(e) ? e:new Error(e);
|
|
1924
1924
|
let stack = newError.stack
|
|
1925
|
-
if (isError(e)) {
|
|
1926
|
-
newError.name = e.name;
|
|
1927
|
-
stack = e.stack;
|
|
1928
|
-
}
|
|
1929
1925
|
newError.stack = stack + `\n at ${tag}`
|
|
1930
1926
|
return newError
|
|
1931
1927
|
}
|
|
@@ -2025,6 +2021,41 @@ function getJsirTypeKey(fileName) {
|
|
|
2025
2021
|
return fileName.split(/[^a-zA-Z]+/)[0] || ''
|
|
2026
2022
|
}
|
|
2027
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
|
+
|
|
2028
2059
|
module.exports = {
|
|
2029
2060
|
wrapperJsirText,
|
|
2030
2061
|
run,
|
|
@@ -2131,5 +2162,7 @@ module.exports = {
|
|
|
2131
2162
|
getRoomsDir,
|
|
2132
2163
|
getDataDir,
|
|
2133
2164
|
getJsirTypeKey,
|
|
2134
|
-
isRunningInBackground
|
|
2165
|
+
isRunningInBackground,
|
|
2166
|
+
createDetachedProcess,
|
|
2167
|
+
interceptStdStreams
|
|
2135
2168
|
}
|