jsir 2.3.3 → 2.3.5

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,7 @@ const {
11
11
  createConsole, setTips, delTips,
12
12
  getEditor, errorStr, getConfigDir,
13
13
  getFullPath, parseUniqueName, toUniqueName, isJsirFileName, toJsirFileName,
14
- getAlias, wrapperJsirText, eia, getKeyTips, getValTips
14
+ getAlias, wrapperJsirText, eia, getKeyTips, getValTips, getRoomsDir
15
15
  } = $lib;
16
16
  const _args = process.argv.slice(2).map(trim);
17
17
  const evalCode = require('../deps/evalCode')
@@ -562,13 +562,13 @@ async function wrapperInput(str) {
562
562
  }
563
563
 
564
564
  function wrapperAlias(str) {
565
- let aliasMap = {};
565
+ let aliasMap = null;
566
566
  try {
567
567
  aliasMap = getConfig("alias");
568
568
  } catch (e) {
569
569
  console.$error("getAlias failed ", e);
570
570
  }
571
- if (Object.keys(aliasMap).length <= 0) {
571
+ if (!aliasMap || Object.keys(aliasMap).length <= 0) {
572
572
  return str;
573
573
  }
574
574
  let fstr = str.split(/\s+/)[0];
@@ -1342,8 +1342,6 @@ const keywordDef = {
1342
1342
  _noAppendNextLine = false
1343
1343
  resetCmdMap()
1344
1344
  console.log(warnStr(`(${setting.name} ${packageJson.version}) You can start with .help, use * to expand context.`))
1345
- room.onRoom()
1346
- nextLine()
1347
1345
  },
1348
1346
  short: 'p'
1349
1347
  },
@@ -1404,6 +1402,44 @@ const keywordDef = {
1404
1402
  }
1405
1403
  },
1406
1404
  short: 'D'
1405
+ },
1406
+ room: {
1407
+ comment: 'manage room',
1408
+ exeFn: async (args) => {
1409
+ let results = [];
1410
+ await room.syncSetting();
1411
+ for (let i = 0; i < setting.rooms.length; i++) {
1412
+ let room = setting.rooms[i]
1413
+ results.push({
1414
+ mid: i,
1415
+ name: (room.local ? "*":" ") + room.name,
1416
+ node: room.selfNode,
1417
+ active: room.active,
1418
+ })
1419
+ for (let key of Object.keys(room.jsirs)) {
1420
+ let jsir = room.jsirs[key]
1421
+ results.push({
1422
+ mid: i,
1423
+ name: (room.local ? "*":"") + room.name,
1424
+ node: room.selfNode,
1425
+ active: room.active,
1426
+ pid: (process.pid === jsir.pid ? "*":" ") + jsir.pid,
1427
+ space: jsir.space,
1428
+ running: jsir.active,
1429
+ port: jsir.port,
1430
+ back: jsir.back,
1431
+ busy: jsir.busy,
1432
+ tips: Object.keys(jsir.tips).map(i => i + ': ' + jsir.tips[i]).join('\n')
1433
+ });
1434
+ }
1435
+ }
1436
+ if (results.length > 0) {
1437
+ console.nable(results)
1438
+ } else {
1439
+ console.warn("no items")
1440
+ }
1441
+ },
1442
+ short: 'm'
1407
1443
  }
1408
1444
  }
1409
1445
 
@@ -2097,6 +2133,7 @@ process.on('beforeExit', function () {
2097
2133
  delTips();
2098
2134
  } else {
2099
2135
  nextLine();
2136
+ room.onRoom()
2100
2137
  }
2101
2138
  });
2102
2139
 
package/deps/room.js CHANGED
@@ -1,10 +1,18 @@
1
1
  const os = require('os');
2
- const {fileJson, fileLock, vl, createConsole, getKeyTips, getValTips} = require('./util');
2
+ const {fileJson, fileLock, vl, createConsole, getKeyTips, getValTips,
3
+ getRoomsDir, e, regEach} = require('./util');
4
+ const server = require('../deps/server')
3
5
  const roomDataFile = "jsirRoom.json"
6
+ const roomsDirLockKey = "RW_" + getRoomsDir();
7
+ const syncRoomsLockKey = "SyncRooms_" + getRoomsDir();
8
+ const enrichRoomInfoLockKey = "ENRICH_" + roomDataFile
4
9
  const console = createConsole();
5
- const ping = require("ping");
6
10
  const net = require("net");
7
11
  const setting = require('../deps/setting')
12
+ const fp = require('fs').promises
13
+ const http = require('http');
14
+ let tailscalePath = os.platform() === 'darwin' ?
15
+ '/Applications/Tailscale.app/Contents/MacOS/Tailscale':'tailscale';
8
16
 
9
17
  function isPidAlive(pid) {
10
18
  try {
@@ -20,8 +28,13 @@ function isPidAlive(pid) {
20
28
  }
21
29
 
22
30
  function onRoom() {
23
- return
24
31
  if (!setting.roomTid[0]) {
32
+ try {
33
+ server.setRoute("post", "/", (req, res) => setting.selfRoom)
34
+ server.setRoute("get", "/", (req, res) => setting.selfRoom)
35
+ } catch (e) {
36
+ console.$error("initRoute failed", e)
37
+ }
25
38
  _onRoom();
26
39
  }
27
40
  }
@@ -29,56 +42,135 @@ function onRoom() {
29
42
  function _onRoom() {
30
43
  setting.roomTid[0] = setTimeout(async () => {
31
44
  try {
32
- await _initRoom();
45
+ await initRoom();
33
46
  } catch (e) {
34
47
  console.$error('initRoom', e)
35
48
  }
36
49
  if (setting.roomTid[0]) {
37
50
  _onRoom();
38
51
  }
39
- }, 1000)
52
+ }, 3000)
40
53
  }
41
54
 
42
55
  function offRoom() {
43
- return;
44
56
  if (setting.roomTid[0]) {
45
57
  clearTimeout(setting.roomTid[0])
46
58
  setting.roomTid[0] = null
59
+ if (setting.server) {
60
+ setting.server.close(() => {
61
+ console.$log('Existing server shut down.');
62
+ });
63
+ }
47
64
  }
48
65
  }
49
66
 
50
- async function getTailScaleNodes() {
51
- console.$log("getNodes")
67
+ function getSelfIP(nodes) {
52
68
  let ips = getLocalIPs().ipv4;
53
- ips = ips.filter(i => i.startsWith("100.")); // tailScale ip前缀
69
+ ips = ips.filter(i => nodes.indexOf(i) !== -1);
70
+ return ips[0]
71
+ }
72
+
73
+ async function getTailscaleNodes() {
74
+ console.$log("getNodes")
75
+ let resp = await e(`${tailscalePath} status`);
54
76
  let nodes = []
55
- for (let ip of ips) {
56
- let netIps = await scanLocalNetwork(ip);
57
- netIps = netIps.filter(i => i !== ip)
58
- nodes.push(...netIps)
77
+ regEach(resp, /^(\d+\.\d+\.\d+\.\d+)\s+/mg, arr => {
78
+ nodes.push(arr[1])
79
+ });
80
+ return [...new Set(nodes)];
81
+ }
82
+
83
+ async function enrichRoomInfo() {
84
+ let nodes = await getTailscaleNodes();
85
+ let ip = getSelfIP(nodes)
86
+ await fileJson(roomDataFile, async room => {
87
+ // 设置roomName
88
+ let name = os.hostname();
89
+ if (!vl(room.name) || room.name !== name) {
90
+ room.name = name;
91
+ console.$log("set roomName", name)
92
+ }
93
+ room.selfNode = ip;
94
+ room.nodes = nodes;
95
+ room.lastUpdateTime = Date.now()
96
+ console.$log("init room", room.name)
97
+ })
98
+ }
99
+
100
+ async function syncRooms() {
101
+ console.$log('syncRooms')
102
+ let roomsDir = getRoomsDir();
103
+ let room = await fileJson(roomDataFile)
104
+
105
+ let syncRooms = []
106
+ for (let node of room.nodes) {
107
+ if (node === room.selfNode) {
108
+ continue
109
+ }
110
+ try {
111
+ let respBody = await reqNode(node, "get", "/");
112
+ syncRooms.push(JSON.parse(respBody))
113
+ } catch (e) {
114
+ console.$log(`sync ${node} failed`, e);
115
+ }
59
116
  }
60
- return await sshEnables(nodes);
61
- }
62
-
63
- async function initRoomInfo() {
64
- await fileLock(roomDataFile + "getNodes", async () => {
65
- let nodes = await getTailScaleNodes();
66
- await fileJson(roomDataFile, async room => {
67
- // 设置roomName
68
- let name = os.hostname();
69
- if (!vl(room.name) || room.name !== name) {
70
- room.name = name;
71
- console.$log("set roomName", name)
117
+ await fileLock(roomsDirLockKey, async () => {
118
+ for (let syncRoom of syncRooms) {
119
+ if (syncRoom.selfNode) {
120
+ await fp.writeFile(syncRoom.selfNode, JSON.stringify(syncRoom, null, 2))
72
121
  }
122
+ }
123
+ })
73
124
 
74
- room.nodes = nodes;
75
- room.lastUpdateTime = Date.now()
76
- console.$log("init room", room.name)
77
- })
78
- }, false)
125
+ // 清理 roomsDir
126
+ let files = await fp.readdir(roomsDir)
127
+ let nodes = room.nodes;
128
+ for (let file of files) {
129
+ if (nodes.indexOf(file) === -1) {
130
+ try {
131
+ await fp.unlink(roomsDir + '/' + file)
132
+ } catch (e) {
133
+ console.$error(e);
134
+ }
135
+ }
136
+ }
137
+ }
138
+
139
+ async function syncSetting() {
140
+ let room = await fileJson(roomDataFile)
141
+ await fileLock(roomsDirLockKey, async () => await _syncSetting(room));
79
142
  }
80
143
 
81
- async function _initRoom() {
144
+ async function _syncSetting(room) {
145
+ setting.selfRoom = room;
146
+ setting.selfRoom.local = true;
147
+ let roomDir = getRoomsDir();
148
+ let rooms = []
149
+ let files = await fp.readdir(roomDir);
150
+ for (let node of setting.selfRoom.nodes) {
151
+ if (files.indexOf(node) !== -1) {
152
+ let resp = String(await fp.readFile(roomDir + '/' + node));
153
+ let room = JSON.parse(resp);
154
+ if (room.selfNode !== setting.selfRoom.selfNode) {
155
+ rooms.push(room)
156
+ }
157
+ } else {
158
+ rooms.push({
159
+ selfNode: node,
160
+ jsirs: []
161
+ })
162
+ }
163
+ }
164
+ setting.rooms = [setting.selfRoom, ...rooms]
165
+ .map(room => {
166
+ room.active = Date.now() - (room.lastUpdateTime || 0) <= 18000;
167
+ Object.entries(room.jsirs).forEach(([key, jsir]) =>
168
+ jsir.active = Date.now() - (jsir.lastUpdateTime || 0) <= 6000)
169
+ return room;
170
+ });
171
+ }
172
+
173
+ async function initRoom() {
82
174
  let roomUpdateTouchTime = false;
83
175
  await fileJson(roomDataFile, async room => {
84
176
  await initRoomJsir(room)
@@ -88,8 +180,11 @@ async function _initRoom() {
88
180
  })
89
181
 
90
182
  if (roomUpdateTouchTime) {
91
- initRoomInfo();
183
+ await fileLock(enrichRoomInfoLockKey, enrichRoomInfo, false)
184
+ await fileLock(syncRoomsLockKey, syncRooms, false);
92
185
  }
186
+
187
+ await syncSetting();
93
188
  }
94
189
 
95
190
  async function getEventLoopDelay() {
@@ -103,14 +198,10 @@ async function getEventLoopDelay() {
103
198
  }
104
199
 
105
200
  async function initRoomJsir(room) {
106
- room.jsir = room.jsir || {};
107
-
108
- for (let pid of [...Object.keys(room.jsir)]) {
109
- if (!isPidAlive(pid)) {
110
- delete room.jsir[pid]
111
- }
112
- }
113
- room.jsir[process.pid] = {
201
+ room.jsirs = Object.fromEntries(
202
+ Object.entries(room.jsirs || {}).filter(([pid]) => isPidAlive(pid))
203
+ );
204
+ room.jsirs[process.pid] = {
114
205
  pid: process.pid,
115
206
  space: setting.defaultSpace,
116
207
  tips: getKeyTips().reduce((obj, key, index) => {
@@ -119,7 +210,8 @@ async function initRoomJsir(room) {
119
210
  }, {}),
120
211
  busy: await getEventLoopDelay(),
121
212
  back: isRunningInBackground(),
122
- lastUpdateTime: Date.now()
213
+ lastUpdateTime: Date.now(),
214
+ port: setting.server.address().port
123
215
  }
124
216
  console.$log("init jsir", process.pid)
125
217
  }
@@ -200,33 +292,48 @@ function checkPortOpen(ip, port) {
200
292
  });
201
293
  }
202
294
 
203
- /**
204
- * 扫描局域网中的可访问IP
205
- * @param {string} localIP - 本机内网IP地址
206
- * @returns {Promise<string[]>} - 可访问的IP地址列表
207
- */
208
- async function scanLocalNetwork(localIP) {
209
- const subnet = localIP.substring(0, localIP.lastIndexOf('.') + 1); // 例如 "192.168.1."
210
- const reachableIPs = [];
211
-
212
- // 扫描 1 到 254 的 IP
213
- const promises = [];
214
- for (let i = 1; i <= 254; i++) {
215
- const ip = `${subnet}${i}`;
216
- promises.push(
217
- ping.promise.probe(ip, { timeout: 1 }).then((res) => {
218
- if (res.alive) {
219
- reachableIPs.push(ip);
220
- }
221
- })
222
- );
295
+ async function reqNode(node, method, url) {
296
+ let port = 52108;
297
+ let room = setting.rooms.filter(i => i.selfNode === node)[0]
298
+ if (room) {
299
+ let activeJsir = Object.values(room.jsirs)
300
+ .filter(i => i.active).sort((a,b) => a.busy - b.busy);
301
+ if (activeJsir.length > 0) {
302
+ port = activeJsir[0].port;
303
+ }
223
304
  }
305
+ let opt = {
306
+ hostname: node,
307
+ port: port, // HTTP 默认端口
308
+ path: url,
309
+ method: method.toUpperCase(),
310
+ };
311
+ console.$log('reqRoom', JSON.stringify(opt))
312
+ return await new Promise((resolve, reject) => {
313
+ const req = http.request(opt, (res) => {
314
+ let data = '';
315
+ // 数据块接收
316
+ res.on('data', (chunk) => {
317
+ data += chunk;
318
+ });
319
+ // 响应结束
320
+ res.on('end', () => {
321
+ resolve(data)
322
+ });
323
+ });
324
+
325
+ // 错误处理
326
+ req.on('error', (e) => {
327
+ reject(e)
328
+ });
224
329
 
225
- await Promise.all(promises);
226
- return reachableIPs;
330
+ // 结束请求
331
+ req.end();
332
+ })
227
333
  }
228
334
 
229
335
  module.exports = {
230
336
  onRoom,
231
- offRoom
337
+ offRoom,
338
+ syncSetting
232
339
  }
package/deps/server.js ADDED
@@ -0,0 +1,110 @@
1
+ const http = require('http');
2
+ const {createConsole, vl} = require('./util');
3
+ const console = createConsole();
4
+ const setting = require('../deps/setting')
5
+
6
+ // 尝试监听的端口
7
+ const preferredPort = 52108;
8
+
9
+ // 路由存储
10
+ const routes = {};
11
+
12
+ // 创建一个 HTTP 服务
13
+ function createServer(port) {
14
+ const server = http.createServer(async (req, res) => {
15
+ const method = req.method.toLowerCase();
16
+ const url = req.url;
17
+
18
+ // 查找对应的路由处理函数
19
+ const routeKey = `${method} ${url}`;
20
+ if (routes[routeKey]) {
21
+ await routes[routeKey](req, res);
22
+ } else {
23
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
24
+ res.end('Not Found\n');
25
+ }
26
+ });
27
+
28
+ server.listen(port, () => {
29
+ const address = server.address();
30
+ console.$log(`Server listening on port ${address.port}`);
31
+ });
32
+
33
+ server.on('error', (err) => {
34
+ console.$error(`Error occurred: ${err.message}`);
35
+ });
36
+
37
+ return server;
38
+ }
39
+
40
+ // 尝试启动服务的逻辑
41
+ function startServer() {
42
+ if (!setting.server) {
43
+ try {
44
+ setting.server = createServer(preferredPort);
45
+ } catch (e) {
46
+ console.$error("startServer failed", e)
47
+ setting.server = createServer(0);
48
+ }
49
+ }
50
+ }
51
+
52
+ // 设置路由的方法
53
+ function setRoute(method, url, fn, handler = jsonHandle) {
54
+ startServer();
55
+
56
+ const routeKey = `${method.toLowerCase()} ${url}`;
57
+ if (handler) {
58
+ routes[routeKey] = async (req, res) => {
59
+ await handler(req, res, fn)
60
+ };
61
+ } else {
62
+ routes[routeKey] = fn;
63
+ }
64
+ console.$log(`AddRoute: ${method.toUpperCase()} ${url} ${handler ? handler.constructor.name:''}`);
65
+ return setting.server;
66
+ }
67
+
68
+ function delRoute(method, url) {
69
+ const routeKey = `${method.toLowerCase()} ${url}`;
70
+ delete routes[routeKey];
71
+ console.$log(`DelRoute: ${method.toUpperCase()} ${url}`);
72
+ }
73
+
74
+ function jsonHandle(req, res, fn) {
75
+ let body = '';
76
+
77
+ // 监听请求数据
78
+ req.on('data', chunk => {
79
+ body += chunk;
80
+ });
81
+
82
+ req.on('end', async () => {
83
+ try {
84
+ // 解析 JSON 请求体
85
+ const requestData = body ? JSON.parse(body):{};
86
+
87
+ // 创建响应 JSON 数据
88
+ let responseData = {};
89
+ let result = await fn(requestData, responseData);
90
+ if (vl(result)) {
91
+ responseData = result;
92
+ }
93
+
94
+ // 设置响应头
95
+ res.writeHead(200, { 'Content-Type': 'application/json' });
96
+ // 返回 JSON 响应
97
+ res.end(JSON.stringify(responseData));
98
+ } catch (error) {
99
+ // 错误处理:如果 JSON 解析失败
100
+ res.writeHead(400, { 'Content-Type': 'application/json' });
101
+ res.end(JSON.stringify({ error: 'Invalid JSON format' }));
102
+ }
103
+ });
104
+ }
105
+
106
+ module.exports = {
107
+ setRoute,
108
+ delRoute,
109
+ jsonHandle
110
+ };
package/deps/setting.js CHANGED
@@ -23,5 +23,8 @@ module.exports = {
23
23
  defaultSpace: 'local',
24
24
  workspaceMap: {},
25
25
  enableNextLine: true,
26
- roomTid: []
26
+ roomTid: [],
27
+ selfRoom: {},
28
+ rooms: [],
29
+ server: null
27
30
  }
package/deps/util.js CHANGED
@@ -23,6 +23,8 @@ let lockDir;
23
23
  let logDir;
24
24
  let tempDir;
25
25
  let configDir;
26
+ let roomsDir;
27
+ let dataDir;
26
28
 
27
29
  class SyncQueue {
28
30
  constructor() {
@@ -708,7 +710,7 @@ function getInitName(fileName) {
708
710
  }
709
711
 
710
712
  function dataFile(fileName, fn, fmt, defaultObj = {}, returnStr = false) {
711
- let dataDir = getLibDataDir() + "/data"
713
+ let dataDir = getDataDir()
712
714
  fileName = trim(fileName)
713
715
  let path
714
716
  if (fileName.startsWith("/")) {
@@ -758,11 +760,11 @@ async function fileExist(path) {
758
760
  }
759
761
  }
760
762
 
761
- async function fileJson(key, fn, fmt = true, safeMs = 49000) {
763
+ async function fileJson(key, fn = null, fmt = true, safeMs = 49000) {
762
764
  `
763
765
  多进程安全文件读写
764
766
  `
765
- let dataDir = getLibDataDir() + "/data"
767
+ let dataDir = getDataDir()
766
768
  let fileName = trim(key)
767
769
  let path = dataDir + "/" + fileName;
768
770
  let homeDir = setting.workspaceMap[setting.defaultSpace]
@@ -901,6 +903,24 @@ function getConfigDir() {
901
903
  return configDir;
902
904
  }
903
905
 
906
+ function getRoomsDir() {
907
+ if (roomsDir) {
908
+ return roomsDir
909
+ }
910
+ roomsDir = getLibDataDir() + '/rooms';
911
+ mkdir(roomsDir);
912
+ return roomsDir;
913
+ }
914
+
915
+ function getDataDir() {
916
+ if (dataDir) {
917
+ return dataDir
918
+ }
919
+ dataDir = getLibDataDir() + '/data';
920
+ mkdir(dataDir);
921
+ return dataDir;
922
+ }
923
+
904
924
  function getAlias(aliasMap, key) {
905
925
  key = trim(key)
906
926
  if (!key) {
@@ -2062,5 +2082,7 @@ module.exports = {
2062
2082
  terminalRun,
2063
2083
  eia,
2064
2084
  getKeyTips,
2065
- getValTips
2085
+ getValTips,
2086
+ getRoomsDir,
2087
+ getDataDir
2066
2088
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsir",
3
- "version": "2.3.3",
3
+ "version": "2.3.5",
4
4
  "description": "JavaScript Script Management Tool",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -22,7 +22,6 @@
22
22
  "chokidar": "^3.5.2",
23
23
  "console.table": "^0.10.0",
24
24
  "dayjs": "^1.10.4",
25
- "pad": "^3.2.0",
26
- "ping": "^0.4.4"
25
+ "pad": "^3.2.0"
27
26
  }
28
27
  }