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 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 !== setting.exeKey) {
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: (process.pid === jsir.pid ? "*" : " ") + jsir.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[0]) {
1447
- if (setting.selfRoom.nodes.indexOf(args[0]) !== -1) {
1448
- await eia("ssh", [args[0]])
1449
- } else {
1450
- console.warn("invalid node")
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
- result = await evalText(text, uniqueName)
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
- console.$error(`check isPidAlive ${pid} failed`, err)
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
- console.$error("initRoute failed", e)
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
- console.$error('initRoom', e)
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
- console.$log('Existing server shut down.');
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
- console.$log("set roomName", name)
115
+ debug("set roomName", name)
93
116
  }
94
117
  room.selfNode = ip;
95
118
  room.nodes = nodes;
96
119
  room.lastUpdateTime = Date.now()
97
- console.$log("init room", room.name)
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
- console.$log(`sync ${node} success`);
142
+ debug(`sync ${node} success`);
120
143
  } catch (e) {
121
- console.$log(`sync ${node} failed:`, respBody, e);
144
+ debug(`sync ${node} failed:`, respBody, e);
122
145
  }
123
146
  })
124
147
  }
125
- await batchAsync(fns, 9, 3000);
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
- console.$error(e);
165
+ debug(e);
143
166
  }
144
167
  }
145
168
  }
146
- console.$log('syncRooms done')
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 (room) {
311
- let activeJsir = Object.values(room.jsirs)
312
- .filter(i => !isDisableConnect(node, i.port))
313
- .sort((a,b) => a.busy - b.busy);
314
- if (activeJsir.length > 0) {
315
- port = activeJsir[0].port;
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
- console.$log('reqRoom', JSON.stringify(opt))
348
+ debug('reqRoom', JSON.stringify(opt))
328
349
  return await new Promise((resolve, reject) => {
329
- const req = http.request(opt, (res) => {
330
- let data = '';
331
- // 数据块接收
332
- res.on('data', (chunk) => {
333
- data += chunk;
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
- res.on('end', () => {
337
- resolve(data)
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
- req.on('error', (e) => {
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) > 3000) {
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\n');
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\n');
62
+ res.end('Forbidden: Missing Sign');
54
63
  return;
55
64
  }
56
-
57
65
  // 如果签名通过,继续处理请求
58
- await routes[routeKey](req, res);
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\n');
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
- console.$log(`Server listening on port ${address.port}`);
94
+ debug(`Server listening on port ${address.port}`);
69
95
  resolve(server)
70
96
  });
71
97
  server.on('error', (err) => {
72
- console.$log(`Error occurred: ${err.message}`);
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
- console.$log("startServer failed, try other port")
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
- console.$log(`AddRoute: ${method.toUpperCase()} ${url} ${handler ? handler.constructor.name:''}`);
135
+ debug(`setRoute: ${method.toUpperCase()} ${url} ${handler ? handler.name:''}`);
104
136
  return setting.server;
105
137
  }
106
138
 
107
- function delRoute(method, url) {
108
- const routeKey = `${method.toLowerCase()} ${url}`;
109
- delete routes[routeKey];
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
- req.on('data', chunk => {
118
- body += chunk;
119
- });
120
-
121
- req.on('end', async () => {
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
- res.writeHead(200, { 'Content-Type': 'application/json' });
135
- // 返回 JSON 响应
136
- res.end(JSON.stringify(responseData));
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
@@ -27,5 +27,9 @@ module.exports = {
27
27
  selfRoom: {},
28
28
  rooms: [],
29
29
  server: null,
30
- disableConnect: {}
30
+ disableConnect: {},
31
+ routes: {},
32
+ services: {},
33
+ serviceFns: {},
34
+ enterOutputs: null
31
35
  }
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 (isRunningInBackground() || (uniqueName && quite)) {
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 result = await Promise.race([Promise.all(proms), sleep(mills)])
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 => setTimeout(resolve, milliseconds));
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 = 9000) {
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(getVl(e.message, e))
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsir",
3
- "version": "2.3.9",
3
+ "version": "2.4.1",
4
4
  "description": "JavaScript Script Management Tool",
5
5
  "main": "index.js",
6
6
  "scripts": {