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 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 !== setting.exeKey) {
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: (process.pid === jsir.pid ? "*" : " ") + jsir.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
- await eia(`cd "${setting.workspaceMap[setting.defaultSpace]}";${cmd}`, _args, true)
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[0]) {
1447
- if (setting.selfRoom.nodes.indexOf(args[0]) !== -1) {
1448
- await eia("ssh", [args[0]])
1449
- } else {
1450
- console.warn("invalid node")
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
- result = await evalText(text, uniqueName)
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
- try {
41
- return await eval(`(async ()=>{${$text};
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
- }, 3000)
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
- respBody = await reqNode(node, "get", "/");
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, 9, 3000);
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 (room) {
317
- let activeJsir = Object.values(room.jsirs)
318
- .filter(i => !isDisableConnect(node, i.port))
319
- .sort((a,b) => a.busy - b.busy);
320
- if (activeJsir.length > 0) {
321
- port = activeJsir[0].port;
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
- const req = http.request(opt, (res) => {
336
- let data = '';
337
- // 数据块接收
338
- res.on('data', (chunk) => {
339
- data += chunk;
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
- res.on('end', () => {
343
- resolve(data)
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
- req.on('error', (e) => {
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 = 52108;
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) > 3000) {
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\n');
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\n');
62
+ res.end('Forbidden: Missing Sign');
60
63
  return;
61
64
  }
62
-
63
65
  // 如果签名通过,继续处理请求
64
- 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
+ }
65
85
  } else {
66
86
  res.writeHead(404, { 'Content-Type': 'text/plain' });
67
- res.end('Not Found\n');
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(`AddRoute: ${method.toUpperCase()} ${url} ${handler ? handler.constructor.name:''}`);
135
+ debug(`setRoute: ${method.toUpperCase()} ${url} ${handler ? handler.name:''}`);
110
136
  return setting.server;
111
137
  }
112
138
 
113
- function delRoute(method, url) {
114
- const routeKey = `${method.toLowerCase()} ${url}`;
115
- delete routes[routeKey];
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
- // 创建响应 JSON 数据
133
- let responseData = {};
134
- let result = await fn(requestData, responseData);
135
- if (vl(result)) {
136
- responseData = result;
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
- res.writeHead(200, { 'Content-Type': 'application/json' });
141
- // 返回 JSON 响应
142
- res.end(JSON.stringify(responseData));
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 (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
  }
@@ -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
- 文件锁, 默认一直等待,直到加锁成功执行fn
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 = 9000) {
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(getVl(e.message, e))
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsir",
3
- "version": "2.4.0",
3
+ "version": "2.4.2",
4
4
  "description": "JavaScript Script Management Tool",
5
5
  "main": "index.js",
6
6
  "scripts": {