iosignal 3.2.0 → 4.0.0

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.
Files changed (76) hide show
  1. package/.vscode/settings.json +3 -0
  2. package/README.en.md +56 -0
  3. package/README.md +39 -782
  4. package/dist/io.d.ts +10 -3
  5. package/dist/io.js +4 -4
  6. package/dist/io.js.map +1 -1
  7. package/dist/io.min.js +4 -4
  8. package/dist/io.min.js.map +1 -1
  9. package/dist/iosignal.js +266 -217
  10. package/dist/types/client/IOCore.d.ts +10 -3
  11. package/package.json +3 -4
  12. package/src/api/RedisAPI.js +19 -31
  13. package/src/api/api_reply.js +14 -6
  14. package/src/api/api_sudo.js +2 -8
  15. package/src/auth/Auth_Redis.js +8 -3
  16. package/src/auth/BohoAuth.js +12 -12
  17. package/src/client/CongPacket.js +2 -0
  18. package/src/client/IOCore.js +98 -52
  19. package/src/server/Manager.js +12 -12
  20. package/src/server/Metric.js +1 -2
  21. package/src/server/RemoteCore.js +27 -34
  22. package/src/server/Server.js +69 -60
  23. package/src/server/serverOption.js +3 -2
  24. package/README.ko.md +0 -566
  25. package/docs/iosignal_architecture.png +0 -0
  26. package/examples/react-chat-js/dist/assets/index-Cy4qaVcJ.js +0 -56
  27. package/examples/react-chat-js/dist/assets/index-NETrjYhz.css +0 -1
  28. package/examples/react-chat-js/dist/index.html +0 -14
  29. package/examples/react-chat-js/index.html +0 -13
  30. package/examples/react-chat-js/package-lock.json +0 -1723
  31. package/examples/react-chat-js/package.json +0 -20
  32. package/examples/react-chat-js/src/App.css +0 -61
  33. package/examples/react-chat-js/src/App.jsx +0 -125
  34. package/examples/react-chat-js/src/index.css +0 -13
  35. package/examples/react-chat-js/src/main.jsx +0 -13
  36. package/examples/react-chat-js/src/shared_io.js +0 -12
  37. package/examples/react-chat-js/vite.config.js +0 -7
  38. package/examples/server/index.js +0 -12
  39. package/examples/server/package-lock.json +0 -47
  40. package/examples/server/package.json +0 -12
  41. package/examples/svelte-chat-js/.devtools +0 -3
  42. package/examples/svelte-chat-js/README.md +0 -38
  43. package/examples/svelte-chat-js/package-lock.json +0 -1505
  44. package/examples/svelte-chat-js/package.json +0 -22
  45. package/examples/svelte-chat-js/src/app.css +0 -105
  46. package/examples/svelte-chat-js/src/app.d.ts +0 -13
  47. package/examples/svelte-chat-js/src/app.html +0 -12
  48. package/examples/svelte-chat-js/src/lib/images/github.svg +0 -1
  49. package/examples/svelte-chat-js/src/lib/images/svelte-logo.svg +0 -1
  50. package/examples/svelte-chat-js/src/lib/images/svelte-welcome.png +0 -0
  51. package/examples/svelte-chat-js/src/lib/images/svelte-welcome.webp +0 -0
  52. package/examples/svelte-chat-js/src/lib/shared_io.js +0 -12
  53. package/examples/svelte-chat-js/src/routes/+layout.svelte +0 -9
  54. package/examples/svelte-chat-js/src/routes/+page.js +0 -3
  55. package/examples/svelte-chat-js/src/routes/+page.svelte +0 -162
  56. package/examples/svelte-chat-js/static/favicon.svg +0 -1
  57. package/examples/svelte-chat-js/static/global.css +0 -19
  58. package/examples/svelte-chat-js/static/robots.txt +0 -3
  59. package/examples/svelte-chat-js/svelte.config.js +0 -15
  60. package/examples/svelte-chat-js/vite.config.js +0 -6
  61. package/test-browser/favicon.ico +0 -0
  62. package/test-browser/index.html +0 -95
  63. package/test-browser/server.js +0 -12
  64. package/test-nodejs/client-playToggle.js +0 -19
  65. package/test-nodejs/client.js +0 -17
  66. package/test-nodejs/client_api_reply.js +0 -15
  67. package/test-nodejs/server-Auth_Env.js +0 -19
  68. package/test-nodejs/server-Auth_File.js +0 -12
  69. package/test-nodejs/server-api_reply.js +0 -6
  70. package/test-nodejs/server-api_sudo.js +0 -7
  71. package/test-nodejs/server.js +0 -20
  72. package/test-nodejs/signalPack.js +0 -34
  73. package/test-nodejs/signaling.js +0 -61
  74. package/test-nodejs/simple-server.js +0 -13
  75. /package/{auth_file.js → auth_file_sample.js} +0 -0
  76. /package/{auth_file.json → auth_file_sample.json} +0 -0
@@ -208,6 +208,8 @@ export class IOCore extends EventEmitter<string | symbol, any> {
208
208
  * @param {Buffer} buffer - The incoming data buffer.
209
209
  */
210
210
  onData(buffer: Buffer): void;
211
+ did: any;
212
+ uid: any;
211
213
  /**
212
214
  * Sends an IAM (I Am) message to the server.
213
215
  * @param {string} [title] - Optional title for the IAM message.
@@ -299,9 +301,9 @@ export class IOCore extends EventEmitter<string | symbol, any> {
299
301
  */
300
302
  get(storeName: string): Promise<any>;
301
303
  /**
302
- * Sends a request to a target and topic.
303
- * @param {string} target - The target of the request.
304
- * @param {string} topic - The topic of the request.
304
+ * Sends a request to a target and topic.(remote api call)
305
+ * @param {string} target - The target(api name) of the request.
306
+ * @param {string} topic - The topic(api function name) of the request.
305
307
  * @param {...any} args - Optional arguments for the request.
306
308
  * @returns {Promise<any>}
307
309
  */
@@ -393,6 +395,11 @@ export class IOCore extends EventEmitter<string | symbol, any> {
393
395
  * Changes the connection state and emits events.
394
396
  * @param {string} state - The new state name (e.g., 'ready', 'closed').
395
397
  * @param {string} [emitEventAndMessage] - Optional message to emit with the state change event.
398
+ *
399
+ * 주의.
400
+ * 1. 상태가 변경 될 때만 'change' 이벤트 호출된다.
401
+ * 2. emitEventAndMessage 옵션 값이 지정되야 해당 이벤트 이름이 호출된다.
402
+ * 보통 이벤트 이름과 동일하게 적거나 이벤트 상황 안내문을 넣는다.
396
403
  */
397
404
  stateChange(state: string, emitEventAndMessage?: string): void;
398
405
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iosignal",
3
- "version": "3.2.0",
3
+ "version": "4.0.0",
4
4
  "description": "iosignal",
5
5
  "type": "module",
6
6
  "exports": {
@@ -18,11 +18,10 @@
18
18
  "unpkg": "./dist/io.min.js",
19
19
  "scripts": {
20
20
  "build": "rollup -c && npm run build:types",
21
- "build:types": "tsc --project tsconfig.json",
22
- "start": "node ./test-browser/server.js"
21
+ "build:types": "tsc --project tsconfig.json"
23
22
  },
24
23
  "engines": {
25
- "node": ">=18.0.0"
24
+ "node": ">=22.0.0"
26
25
  },
27
26
  "author": {
28
27
  "name": "Taeo Lee",
@@ -1,14 +1,21 @@
1
+ /**
2
+ * RedisAPI <IOSignal API>
3
+ *
4
+ * iosignal 클라이언트의 redis 질의 요청을 받으면,
5
+ * redisClient 로 연결된 redis-server 에게 질의 후 결과를 되돌려 준다.
6
+ *
7
+ * client ex:
8
+ * await io.req('redis','hget','user:id' )
9
+ *
10
+ */
1
11
  import { STATUS } from './api_constant.js'
2
12
 
3
13
  const MIN_LEVEL = 200;
4
14
  const COMMANDS = [
5
- 'GET', 'SET',
6
- 'HGETALL', 'HGET',
7
- 'HSET', 'SADD',
8
- 'SISMEMBER', 'SMEMBERS',
9
- 'EXISTS', 'SREM',
10
- 'DEL', 'KEYS',
11
- 'SAVE'
15
+ 'GET', 'SET', 'HGETALL', 'HGET', 'HSET', 'SADD', 'SISMEMBER',
16
+ 'SMEMBERS', 'EXISTS', 'SREM', 'DEL', 'KEYS', 'SAVE',
17
+ 'get', 'set', 'hGetAll', 'hGet', 'hSet', 'sAdd', 'sIsMember',
18
+ 'sMembers', 'exists', 'sRem', 'del', 'keys', 'save'
12
19
  ]
13
20
 
14
21
  export class RedisAPI {
@@ -21,42 +28,23 @@ export class RedisAPI {
21
28
  this.commands = COMMANDS
22
29
  }
23
30
 
24
-
25
31
  checkPermission(remote, req) {
26
- console.log('checkPermission', remote.level, 'vs', this.minLevel)
27
- if (remote.level >= this.minLevel) {
28
- return true
29
- } else {
30
- return false
31
- }
32
+ return (remote.level >= this.minLevel) ? true : false;
32
33
  }
33
34
 
34
-
35
35
  async request(remote, req) {
36
-
37
36
  let result;
38
- let status = STATUS.OK;
39
37
  try {
40
38
  let cmd = req.topic
41
- cmd = cmd.toUpperCase()
42
- if (COMMANDS.includes(cmd)) {
43
- console.log('RedisAPI call:', cmd, req.args)
44
-
45
- if (req.args.length > 0) {
46
- result = await this.redisClient[cmd](...req.args)
47
- } else {
48
- result = await this.redisClient[cmd]()
49
- }
50
-
51
-
39
+ if (req.args?.length > 0) {
40
+ result = await this.redisClient[cmd](...req.args)
52
41
  } else {
53
- status = STATUS.ERROR;
42
+ result = await this.redisClient[cmd]()
54
43
  }
55
- remote.response(req.mid, status, result)
44
+ remote.response(req.mid, STATUS.OK, result)
56
45
  } catch (e) {
57
46
  remote.response(req.mid, STATUS.ERROR, e.message)
58
47
  }
59
-
60
48
  }
61
49
 
62
50
  }
@@ -1,17 +1,19 @@
1
1
 
2
+ /**
3
+ * simple IOSignal API example.
4
+ */
2
5
 
3
6
  import { STATUS } from './api_constant.js'
7
+ export const commands = ['echo', 'date', 'unixtime'];
4
8
 
5
9
  const MIN_LEVEL = 0;
6
-
7
10
  export function checkPermission(remote) {
8
- if (remote.level >= MIN_LEVEL) {
9
- return true
10
- } else {
11
- return false
12
- }
11
+ return (remote.level >= MIN_LEVEL) ? true : false;
13
12
  }
14
13
 
14
+ /**
15
+ * client api call example: await io.req('reply','echo','hello')
16
+ */
15
17
  export async function echo(remote, req) {
16
18
  if (!req.args)
17
19
  remote.response(req.mid, STATUS.ERROR, 'no message to echo')
@@ -19,11 +21,17 @@ export async function echo(remote, req) {
19
21
  remote.response(req.mid, STATUS.OK, req.args)
20
22
  }
21
23
 
24
+ /**
25
+ * client api call example: await io.req('reply','date')
26
+ */
22
27
  export async function date(remote, req) {
23
28
  let r = new Date().toUTCString()
24
29
  remote.response(req.mid, STATUS.OK, r)
25
30
  }
26
31
 
32
+ /**
33
+ * client api call example: await io.req('reply','unixtime')
34
+ */
27
35
  export async function unixtime(remote, req) {
28
36
  let r = Math.floor(Date.now() / 1000)
29
37
  remote.response(req.mid, STATUS.OK, r)
@@ -1,7 +1,5 @@
1
1
  import { STATUS } from './api_constant.js'
2
2
 
3
- const MIN_LEVEL = 255;
4
-
5
3
  export const commands = [
6
4
  'cid', 'remotes', 'clients',
7
5
  'channels', 'subscribers',
@@ -11,13 +9,9 @@ export const commands = [
11
9
  'adddevice', 'getdevice', 'deldevice'
12
10
  ]
13
11
 
14
-
12
+ const MIN_LEVEL = 255;
15
13
  export function checkPermission(remote) {
16
- if (remote.level >= MIN_LEVEL) {
17
- return true
18
- } else {
19
- return false
20
- }
14
+ return (remote.level >= MIN_LEVEL) ? true : false;
21
15
  }
22
16
 
23
17
  export async function request(remote, req) {
@@ -13,11 +13,15 @@ export class Auth_Redis extends BohoAuth {
13
13
 
14
14
  // get device key from DB. (for Boho auth.)
15
15
  async getAuth(id) {
16
- let result = await this.redis.hGetAll(DEVICE_PREFIX + id)
17
- if (result.key) return result
16
+ try {
17
+ let result = await this.redis.hGetAll(DEVICE_PREFIX + id)
18
+ if (result.key) return result
19
+ } catch (error) {
20
+ console.log('getAuth', error)
21
+ }
18
22
  }
19
23
 
20
-
24
+ //api_sudo call
21
25
  async getAuthIdList() {
22
26
  let result = await this.redis.keys(DEVICE_PREFIX + '*')
23
27
  result = result.map(v => {
@@ -32,6 +36,7 @@ export class Auth_Redis extends BohoAuth {
32
36
  return this.redis.hSet(DEVICE_PREFIX + id, { 'key': Base64hashKey, 'cid': cid, 'level': level })
33
37
  }
34
38
 
39
+ //api_sudo call
35
40
  async delAuth(id) {
36
41
  return this.redis.del(DEVICE_PREFIX + id)
37
42
  }
@@ -20,10 +20,9 @@ export class BohoAuth {
20
20
  let peerInfo = `FAIL #${peer.ssid} reason:${reason} `
21
21
  this.authLogger.log(peerInfo)
22
22
  }
23
-
24
- console.log('## AUTH_FAIL reason:', reason)
25
- // peer.send( Buffer.from( [Boho.BohoMsg.AUTH_FAIL] ))
23
+ // console.log('##### AUTH_FAIL reason: ', reason)
26
24
  peer.setState(CLIENT_STATE.AUTH_FAIL)
25
+ // add some delay time.
27
26
  setTimeout(e => {
28
27
  peer.send(Buffer.from([Boho.BohoMsg.AUTH_FAIL]))
29
28
  }, serverOption.auth.delay_auth_fail)
@@ -33,30 +32,31 @@ export class BohoAuth {
33
32
  try {
34
33
  //1. unpack
35
34
  let infoPack = MBP.unpack(auth_hmac, Boho.Meta.AUTH_HMAC)
35
+
36
36
  if (!infoPack) {
37
37
  this.send_auth_fail(peer, 'unpack auth_pack fail');
38
38
  return
39
39
  }
40
-
40
+
41
41
  let id = ""
42
42
  if (infoPack.id8.includes(0)) {
43
43
  id = decoder.decode(infoPack.id8.subarray(0, infoPack.id8.indexOf(0)));
44
44
  } else {
45
45
  id = decoder.decode(infoPack.id8);
46
46
  }
47
-
47
+
48
48
  //2. get key of id from DB
49
49
  let authInfo = await this.getAuth(id)
50
50
 
51
- // console.log('##### authInfo',authInfo )
52
- if (serverOption.debug.showAuthInfo) {
53
- }
54
-
55
51
  if (!authInfo) {
56
52
  this.send_auth_fail(peer, 'NO ID:' + id);
57
53
  return
58
54
  }
59
55
 
56
+ if (serverOption.debug.showAuthInfo) {
57
+ console.log('[debug]showAuthInfo',authInfo )
58
+ }
59
+
60
60
  // console.log('db authInfo.key: ', authInfo.key)
61
61
  peer.boho.copy_id8(infoPack.id8)
62
62
  // type of key
@@ -107,6 +107,7 @@ export class BohoAuth {
107
107
 
108
108
  //6. delete current (temp rand)cid if exist.
109
109
  if (peer.cid) {
110
+ console.log('duplicate cid')
110
111
  peer.manager.cid2remote.delete(peer.cid)
111
112
  }
112
113
 
@@ -114,6 +115,7 @@ export class BohoAuth {
114
115
  peer.did = id
115
116
  peer.cid = authInfo.cid
116
117
  peer.nick = authInfo.cid // temporary: nick as cid
118
+ if( authInfo.uid ) peer.uid = authInfo.uid
117
119
 
118
120
  //8. setting quota level.
119
121
  let quotaLevel = serverOption.defaultQuotaIndex;
@@ -152,13 +154,11 @@ export class BohoAuth {
152
154
  }
153
155
  return authInfo
154
156
  } catch (error) {
155
- this.send_auth_fail(peer, 'caught: unknown error' + error);
157
+ this.send_auth_fail(peer, '[BohoAuth]caught: unknown error:' + error);
156
158
  }
157
159
 
158
160
  }
159
161
 
160
162
 
161
-
162
-
163
163
  }
164
164
 
@@ -11,6 +11,8 @@ export const CongType = {
11
11
  // support LittleEndian system.
12
12
  export function pack(payload) {
13
13
 
14
+ if( payload.byteLength == undefined ) payload = Buffer.from(payload)
15
+
14
16
  if (payload.byteLength < 256) { //one byte len
15
17
  return MBP.pack(
16
18
  MBP.MB('#type', '8', CongType.TYPE_LEN1),
@@ -207,7 +207,7 @@ export class IOCore extends EventEmitter {
207
207
  close() {
208
208
  if (this._closed) return;
209
209
  this._closed = true;
210
-
210
+ // console.log('####### IOCOre.js close() called')
211
211
  // If auto-reconnect is disabled, we must stop the keep-alive timer.
212
212
  if (this.autoReconnect === false) {
213
213
  clearInterval(this.connectionCheckerIntervalID);
@@ -430,13 +430,25 @@ export class IOCore extends EventEmitter {
430
430
  case IOMsg.PONG:
431
431
  break;
432
432
 
433
+ case IOMsg.ECHO:
434
+ try {
435
+ let str = decoder.decode(buffer.subarray(1))
436
+
437
+ this.emit('echo', str)
438
+ } catch (error) {
439
+ this.emit('error', new Error('ECHO data error'))
440
+ }
441
+ break;
442
+
433
443
  case IOMsg.IAM_RES:
434
444
  try {
435
445
  let str = decoder.decode(buffer.subarray(1))
436
446
  let jsonInfo = JSON.parse(str)
437
- if (jsonInfo.ip) {
438
- this.ip = jsonInfo.ip;
439
- }
447
+ if (jsonInfo.ip) { this.ip = jsonInfo.ip; }
448
+ if (jsonInfo.nick) { this.nick = jsonInfo.nick; }
449
+ if (jsonInfo.did) { this.did = jsonInfo.did; }
450
+ if (jsonInfo.uid) { this.uid = jsonInfo.uid; }
451
+ this.emit('iam_res', str )
440
452
  } catch (error) {
441
453
  this.emit('error', new Error('IAM_RES data error'))
442
454
  }
@@ -447,7 +459,7 @@ export class IOCore extends EventEmitter {
447
459
  this.cid = cidStr;
448
460
 
449
461
  // **IMPORTANT** change state before subscribe.
450
- this.stateChange('ready', 'cid_ready')
462
+ this.stateChange('ready', 'cid_ready')
451
463
  this.subscribe_memory_channels()
452
464
  break;
453
465
 
@@ -455,13 +467,14 @@ export class IOCore extends EventEmitter {
455
467
  let quotaLevel = buffer[1]
456
468
  this.level = quotaLevel;
457
469
  this.quota = quotaTable[quotaLevel];
458
- console.log('## QUOTA:', quotaLevel, JSON.stringify(this.quota))
470
+ console.log('[QUOTA_LEVEL]', JSON.stringify(this.quota))
459
471
  break;
460
472
 
461
473
  case IOMsg.SERVER_CLEAR_AUTH:
462
474
  this.useAuth = false;
463
475
  this.boho.clearAuth();
464
476
  this.stop();
477
+ console.log('[SERVER_CLEAR_AUTH] io stop.')
465
478
  break;
466
479
 
467
480
  case IOMsg.SERVER_REDIRECT:
@@ -538,8 +551,11 @@ export class IOCore extends EventEmitter {
538
551
  switch (payloadType) {
539
552
 
540
553
  case PAYLOAD_TYPE.EMPTY:
541
- if (tag.indexOf('@') === 0) this.emit('@', null, tag)
542
- else this.emit(tag, null, tag)
554
+ if (tag.indexOf('@') === 0) this.emit('@', tag, null)
555
+ else {
556
+ this.emit(tag, tag, null)
557
+ this.emit('message', tag, null )
558
+ }
543
559
  break;
544
560
 
545
561
  case PAYLOAD_TYPE.TEXT:
@@ -550,33 +566,48 @@ export class IOCore extends EventEmitter {
550
566
  payloadStringWithoutNull = payloadBuffer.subarray(0, payloadBuffer.byteLength - 1)
551
567
  }
552
568
  let oneString = decoder.decode(payloadStringWithoutNull)
553
- if (tag.indexOf('@') === 0) this.emit('@', oneString, tag)
554
- if (tag !== '@') this.emit(tag, oneString, tag)
569
+ if (tag.indexOf('@') === 0) this.emit('@', tag, oneString)
570
+ if (tag !== '@'){
571
+ this.emit(tag, tag, oneString)
572
+ this.emit('message', tag, oneString)
573
+ }
555
574
  break;
556
575
 
557
576
  case PAYLOAD_TYPE.BINARY:
558
- if (tag.indexOf('@') === 0) this.emit('@', payloadBuffer, tag)
559
- if (tag !== '@') this.emit(tag, payloadBuffer, tag)
577
+ if (tag.indexOf('@') === 0) this.emit('@', tag, payloadBuffer)
578
+ if (tag !== '@'){
579
+ this.emit(tag, tag, payloadBuffer)
580
+ this.emit('message', tag, payloadBuffer)
581
+ }
560
582
  break;
561
583
 
562
584
  case PAYLOAD_TYPE.OBJECT:
563
585
  let oneObjectBuffer = decoder.decode(payloadBuffer)
564
586
  let oneJSONObject = JSON.parse(oneObjectBuffer)
565
- if (tag.indexOf('@') === 0) this.emit('@', oneJSONObject, tag)
566
- if (tag !== '@') this.emit(tag, oneJSONObject, tag)
587
+ if (tag.indexOf('@') === 0) this.emit('@', tag, oneJSONObject)
588
+ if (tag !== '@'){
589
+ this.emit(tag, tag, oneJSONObject)
590
+ this.emit('message', tag, oneJSONObject)
591
+ }
567
592
  break;
568
593
 
569
594
  case PAYLOAD_TYPE.MJSON:
570
595
  let mjsonBuffer = decoder.decode(payloadBuffer)
571
596
  let mjson = JSON.parse(mjsonBuffer)
572
- if (tag.indexOf('@') === 0) this.emit('@', ...mjson, tag)
573
- if (tag !== '@') this.emit(tag, ...mjson, tag)
597
+ if (tag.indexOf('@') === 0) this.emit('@', tag, ...mjson)
598
+ if (tag !== '@'){
599
+ this.emit(tag, tag, ...mjson)
600
+ this.emit('message', tag, ...mjson)
601
+ }
574
602
  break;
575
603
 
576
604
  case PAYLOAD_TYPE.MBA:
577
605
  let mbaObject = MBP.unpack(payloadBuffer)
578
- if (tag.indexOf('@') === 0) this.emit('@', ...mbaObject.args, tag)
579
- if (tag !== '@') this.emit(tag, ...mbaObject.args, tag)
606
+ if (tag.indexOf('@') === 0) this.emit('@', tag, ...mbaObject.args)
607
+ if (tag !== '@'){
608
+ this.emit(tag, tag, ...mbaObject.args)
609
+ this.emit('message', tag, ...mbaObject.args )
610
+ }
580
611
  break;
581
612
 
582
613
  default:
@@ -668,7 +699,7 @@ export class IOCore extends EventEmitter {
668
699
  */
669
700
  echo(args) {
670
701
  if (args) {
671
- console.log('echo args:', args)
702
+ // console.log('send echo args:', args)
672
703
  this.send_enc_mode(MBP.pack(
673
704
  MBP.MB('#MsgType', '8', IOMsg.ECHO),
674
705
  MBP.MB('#msg', args)
@@ -757,13 +788,13 @@ export class IOCore extends EventEmitter {
757
788
  */
758
789
  setMsgPromise(mid) {
759
790
  return new Promise((resolve, reject) => {
760
- this.promiseMap.set(mid, [resolve, reject])
761
- setTimeout(e => {
791
+ const timeoutId = setTimeout(e => {
762
792
  if (this.promiseMap.has(mid)) {
763
793
  reject('timeout');
764
794
  this.promiseMap.delete(mid)
765
795
  }
766
796
  }, this.promiseTimeOut);
797
+ this.promiseMap.set(mid, [resolve, reject, timeoutId]);
767
798
  })
768
799
  }
769
800
 
@@ -777,7 +808,8 @@ export class IOCore extends EventEmitter {
777
808
  if (!res) return
778
809
 
779
810
  if (this.promiseMap.has(res.mid)) {
780
- let [resolve, reject] = this.promiseMap.get(res.mid)
811
+ let [resolve, reject, timeoutId] = this.promiseMap.get(res.mid)
812
+ clearTimeout(timeoutId)
781
813
  this.promiseMap.delete(res.mid)
782
814
 
783
815
  if (res.status < 128) {
@@ -886,9 +918,9 @@ export class IOCore extends EventEmitter {
886
918
 
887
919
 
888
920
  /**
889
- * Sends a request to a target and topic.
890
- * @param {string} target - The target of the request.
891
- * @param {string} topic - The topic of the request.
921
+ * Sends a request to a target and topic.(remote api call)
922
+ * @param {string} target - The target(api name) of the request.
923
+ * @param {string} topic - The topic(api function name) of the request.
892
924
  * @param {...any} args - Optional arguments for the request.
893
925
  * @returns {Promise<any>}
894
926
  */
@@ -924,21 +956,24 @@ export class IOCore extends EventEmitter {
924
956
  */
925
957
  subscribe(tag) {
926
958
  if (typeof tag !== 'string') throw TypeError('tag should be string.')
959
+ if (tag.length > SIZE_LIMIT.TAG_LEN1) throw TypeError('please check tag string length limit:' + SIZE_LIMIT.TAG_LEN1)
927
960
  if (this.state !== STATES.READY) return
928
961
 
929
- let tagList = tag.split(',')
930
- tagList.forEach(tag => {
931
- this.channels.add(tag)
932
- })
933
-
934
- let tagEncoded = encoder.encode(tag)
935
- if (tagEncoded.byteLength > SIZE_LIMIT.TAG_LEN1) throw TypeError('please use tag string bytelength below:' + SIZE_LIMIT.TAG_LEN1)
936
-
937
- this.send_enc_mode(
938
- Buffer.concat([
939
- MBP.NB('8', IOMsg.SUBSCRIBE),
940
- MBP.NB('8', tagEncoded.byteLength),
941
- tagEncoded]))
962
+ // subscribe 사용시, 사용 'ready' 이벤트시 메뉴얼 등록되므로 여기에 등록하면 2중 호출된다.
963
+ // 즉, channels 등록은 listen 같은 자동화 구독시만 사용.
964
+ // let tagList = tag.split(',')
965
+ // tagList.forEach(tag => {
966
+ // this.channels.add(tag)
967
+ // })
968
+
969
+ try {
970
+ let tagEncoded = encoder.encode(tag)
971
+ this.send_enc_mode(
972
+ Buffer.concat([
973
+ MBP.NB('8', IOMsg.SUBSCRIBE),
974
+ MBP.NB('8', tagEncoded.byteLength),
975
+ tagEncoded]))
976
+ } catch (error) { }
942
977
  }
943
978
 
944
979
  /**
@@ -949,20 +984,22 @@ export class IOCore extends EventEmitter {
949
984
  */
950
985
  subscribe_promise(tag) {
951
986
  if (typeof tag !== 'string') throw TypeError('tag should be string.')
987
+ if (tag.length > SIZE_LIMIT.TAG_LEN2) throw TypeError('please check tag string length limit:' + SIZE_LIMIT.TAG_LEN2)
988
+
952
989
  if (this.state !== STATES.READY) {
953
990
  return Promise.reject('subscribe_promise:: connection is not ready')
954
991
  }
955
992
 
956
- let tagEncoded = encoder.encode(tag)
957
- if (tagEncoded.byteLength > SIZE_LIMIT.TAG_LEN2) throw TypeError('please use tag string bytelength: ' + SIZE_LIMIT.TAG_LEN2)
958
-
959
- this.send_enc_mode(
960
- Buffer.concat([
961
- MBP.NB('8', IOMsg.SUBSCRIBE_REQ),
962
- MBP.NB('16', ++this.mid),
963
- MBP.NB('16', tagEncoded.byteLength),
964
- tagEncoded]))
965
- return this.setMsgPromise(this.mid)
993
+ try {
994
+ let tagEncoded = encoder.encode(tag)
995
+ this.send_enc_mode(
996
+ Buffer.concat([
997
+ MBP.NB('8', IOMsg.SUBSCRIBE_REQ),
998
+ MBP.NB('16', ++this.mid),
999
+ MBP.NB('16', tagEncoded.byteLength),
1000
+ tagEncoded]))
1001
+ return this.setMsgPromise(this.mid)
1002
+ } catch (error) { }
966
1003
  }
967
1004
 
968
1005
  /**
@@ -1159,14 +1196,23 @@ export class IOCore extends EventEmitter {
1159
1196
  * Changes the connection state and emits events.
1160
1197
  * @param {string} state - The new state name (e.g., 'ready', 'closed').
1161
1198
  * @param {string} [emitEventAndMessage] - Optional message to emit with the state change event.
1199
+ *
1200
+ * 주의.
1201
+ * 1. 상태가 변경 될 때만 'change' 이벤트 호출된다.
1202
+ * 2. emitEventAndMessage 옵션 값이 지정되야 해당 이벤트 이름이 호출된다.
1203
+ * 보통 이벤트 이름과 동일하게 적거나 이벤트 상황 안내문을 넣는다.
1162
1204
  */
1163
1205
  stateChange(state, emitEventAndMessage) {
1164
- // STATES constant name : string upperCase
1165
- // eventName, .stateName : string lowerCase
1166
- // .state : number
1206
+ // STATES constant name <string> upperCase
1207
+ // eventName and .stateName <string> lowerCase
1208
+ // .state <number>
1209
+ // console.log('### stateChange reason:', emitEventAndMessage )
1167
1210
  let eventName = state.toLowerCase()
1168
1211
  this.state = STATES[state.toUpperCase()] // state: number
1169
- if (emitEventAndMessage) this.emit(eventName, emitEventAndMessage)
1212
+
1213
+ if (emitEventAndMessage){
1214
+ this.emit(eventName, emitEventAndMessage)
1215
+ }
1170
1216
 
1171
1217
  if (this.stateName !== eventName) {
1172
1218
  this.stateName = eventName
@@ -181,8 +181,8 @@ export class Manager {
181
181
  serverSignalTo(tag, ...args) {
182
182
  let cid = tag.split('@')[0]
183
183
  let topic = tag.split('@')[1]
184
- let sigPack = getSignalPack('@' + topic, ...args)
185
184
  if (cid && this.cid2remote.has(cid)) {
185
+ let sigPack = getSignalPack('@' + topic, ...args)
186
186
  let targetRemote = this.cid2remote.get(cid)
187
187
  console.log('target', targetRemote.state, targetRemote.cid)
188
188
  targetRemote.send_enc_mode(sigPack)
@@ -193,11 +193,11 @@ export class Manager {
193
193
  }
194
194
 
195
195
  sender(tag, remote, message) {
196
- if (serverOption.memberOnly && !remote.boho.isAuthorized) {
197
- // console.log("## MemberOnly, reject unAuthorized remote." )
196
+ if (serverOption.membersOnly && !remote.boho.isAuthorized) {
197
+ console.log("[membersOnly] unAuthorized remote.",tag )
198
198
  remote.send(Buffer.from([IOMsg.SERVER_CLEAR_AUTH]))
199
199
  remote.close()
200
- return ['err', 'not autrized']
200
+ return ['err', 'unAuthorized']
201
201
  }
202
202
 
203
203
  if (tag.indexOf('$') == 0) return ['err', 'prefix $ is reserved for userSet tag.']
@@ -284,17 +284,18 @@ export class Manager {
284
284
  // console.log('## null remote' )
285
285
  break;
286
286
  }
287
- if (c !== remote) {
287
+ // change: from v4.x allow receiveSelfMessages.
288
+ // if (c !== remote) {
288
289
  c.send_enc_mode(message);
289
290
  sentCounter++;
290
- }
291
+ // }
291
292
  }
292
293
 
293
- if (serverOption.useQuota.publishCounter) {
294
- // console.log(`pub >> [${tag}] sent: ${sentCounter} [use quota limit: ${remote.quota.publishCounter }] total: ${remotes.size} subscribers. ` )
295
- } else {
296
- // console.log(`pub >> [${tag}] sent: ${sentCounter} [no quota limit] total: ${remotes.size} subscribers. ` )
297
- }
294
+ // if (serverOption.useQuota.publishCounter) {
295
+ // // console.log(`pub >> [${tag}] sent: ${sentCounter} [use quota limit: ${remote.quota.publishCounter }] total: ${remotes.size} subscribers. ` )
296
+ // } else {
297
+ // // console.log(`pub >> [${tag}] sent: ${sentCounter} [no quota limit] total: ${remotes.size} subscribers. ` )
298
+ // }
298
299
  return ['ok', sentCounter]
299
300
  }
300
301
  }
@@ -444,6 +445,5 @@ export class Manager {
444
445
  this.retain_messages.clear();
445
446
  this.channel_map.clear();
446
447
 
447
- console.log('Manager closed');
448
448
  }
449
449
  }
@@ -118,8 +118,7 @@ export class Metric {
118
118
  ip: remote.ip,
119
119
  uptime: Math.trunc((Date.now() - remote.socket.openTime) / 1000),
120
120
  nick: remote.nick,
121
- ssid: remote.ssid,
122
- echo: remote.lastEchoMessage
121
+ ssid: remote.ssid
123
122
  }
124
123
  } else if (mode == 3) {
125
124
  return {