iosignal 3.1.0 → 3.3.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 (38) hide show
  1. package/README.ko.md +1 -1
  2. package/README.md +1 -225
  3. package/dist/io.d.ts +5 -3
  4. package/dist/io.js +4 -4
  5. package/dist/io.js.map +1 -1
  6. package/dist/io.min.js +4 -4
  7. package/dist/io.min.js.map +1 -1
  8. package/dist/iosignal.js +285 -182
  9. package/dist/types/client/IOCore.d.ts +5 -3
  10. package/examples/react-chat-js/dist/assets/index-e2KOTYW8.js +65 -0
  11. package/examples/react-chat-js/dist/index.html +1 -1
  12. package/examples/react-chat-js/package-lock.json +1 -1
  13. package/examples/server/index.js +62 -9
  14. package/examples/server/package-lock.json +833 -2
  15. package/examples/server/package.json +1 -0
  16. package/examples/server/public/index.html +26 -0
  17. package/examples/svelte-chat-js/package-lock.json +11 -95
  18. package/examples/svelte-chat-js/package.json +1 -0
  19. package/examples/svelte-chat-js/src/app.html +1 -0
  20. package/examples/svelte-chat-js/src/routes/+layout.svelte +0 -4
  21. package/examples/svelte-chat-js/src/routes/+page.svelte +5 -0
  22. package/examples/svelte-chat-js/svelte.config.js +10 -2
  23. package/index.js +2 -1
  24. package/package.json +1 -1
  25. package/src/api/RedisAPI.js +19 -31
  26. package/src/api/api_reply.js +14 -6
  27. package/src/api/api_sudo.js +2 -8
  28. package/src/auth/Auth_Redis.js +8 -3
  29. package/src/auth/BohoAuth.js +12 -12
  30. package/src/client/CongPacket.js +2 -0
  31. package/src/client/IOCore.js +28 -12
  32. package/src/server/Manager.js +18 -3
  33. package/src/server/Metric.js +1 -2
  34. package/src/server/Remote.js +5 -0
  35. package/src/server/RemoteCore.js +27 -34
  36. package/src/server/Server.js +139 -66
  37. package/src/server/serverOption.js +5 -4
  38. package/test-nodejs/client-playToggle.js +19 -0
@@ -17,6 +17,7 @@
17
17
  "vite": "^7.0.4"
18
18
  },
19
19
  "dependencies": {
20
+ "@sveltejs/adapter-static": "^3.0.10",
20
21
  "iosignal": "file:../.."
21
22
  }
22
23
  }
@@ -3,6 +3,7 @@
3
3
  <head>
4
4
  <meta charset="utf-8" />
5
5
  <link rel="icon" href="%sveltekit.assets%/favicon.svg" />
6
+ <link rel="stylesheet" href="%sveltekit.assets%/global.css" />
6
7
  <meta name="viewport" content="width=device-width, initial-scale=1" />
7
8
  %sveltekit.head%
8
9
  </head>
@@ -2,8 +2,4 @@
2
2
  let { children } = $props();
3
3
  </script>
4
4
 
5
- <svelte:head>
6
- <link rel="stylesheet" href="/global.css" />
7
- </svelte:head>
8
-
9
5
  {@render children()}
@@ -19,6 +19,11 @@
19
19
  };
20
20
 
21
21
  if (browser) {
22
+ let wsURL = new URL( document.URL )
23
+ wsURL.protocol = 'ws'
24
+ console.log( 'ws url:', wsURL.href)
25
+
26
+ // io = new IO(wsURL.href);
22
27
  io = new IO(url);
23
28
  if (dev)
24
29
  console.log("new io:", IO.version, IO.instanceCount, IO.webSocketCount);
@@ -1,4 +1,4 @@
1
- import adapter from '@sveltejs/adapter-auto';
1
+ import adapter from '@sveltejs/adapter-static';
2
2
  import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
3
3
 
4
4
  /** @type {import('@sveltejs/kit').Config} */
@@ -7,7 +7,15 @@ const config = {
7
7
  runes: true
8
8
  },
9
9
  kit: {
10
- adapter: adapter()
10
+ adapter: adapter({
11
+ // default options are shown. On some platforms
12
+ // these options are set automatically — see below
13
+ pages: 'build',
14
+ assets: 'build',
15
+ // fallback: 'index.html',
16
+ precompress: false,
17
+ strict: true
18
+ })
11
19
  },
12
20
  preprocess: vitePreprocess()
13
21
  };
package/index.js CHANGED
@@ -1,4 +1,5 @@
1
- export {version} from "./package.json";
1
+ import pkg from './package.json' with { type: 'json' };
2
+ export const version = pkg.version;
2
3
  export { IOCongSocket } from './src/client/IOCongSocket.js'
3
4
  export { IOWS as IO } from './src/client/IOWS.js'
4
5
  export { pack, CongRx } from './src/client/CongPacket.js'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iosignal",
3
- "version": "3.1.0",
3
+ "version": "3.3.0",
4
4
  "description": "iosignal",
5
5
  "type": "module",
6
6
  "exports": {
@@ -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,26 @@ 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
+ // TIP. cid from [CID_RES], level from [QUOTA_LEVEL]
452
+ this.emit('iam_res', str )
440
453
  } catch (error) {
441
454
  this.emit('error', new Error('IAM_RES data error'))
442
455
  }
@@ -455,13 +468,14 @@ export class IOCore extends EventEmitter {
455
468
  let quotaLevel = buffer[1]
456
469
  this.level = quotaLevel;
457
470
  this.quota = quotaTable[quotaLevel];
458
- console.log('## QUOTA:', quotaLevel, JSON.stringify(this.quota))
471
+ console.log('[QUOTA_LEVEL]', JSON.stringify(this.quota))
459
472
  break;
460
473
 
461
474
  case IOMsg.SERVER_CLEAR_AUTH:
462
475
  this.useAuth = false;
463
476
  this.boho.clearAuth();
464
477
  this.stop();
478
+ console.log('[SERVER_CLEAR_AUTH] io stop.')
465
479
  break;
466
480
 
467
481
  case IOMsg.SERVER_REDIRECT:
@@ -668,7 +682,7 @@ export class IOCore extends EventEmitter {
668
682
  */
669
683
  echo(args) {
670
684
  if (args) {
671
- console.log('echo args:', args)
685
+ // console.log('send echo args:', args)
672
686
  this.send_enc_mode(MBP.pack(
673
687
  MBP.MB('#MsgType', '8', IOMsg.ECHO),
674
688
  MBP.MB('#msg', args)
@@ -757,13 +771,13 @@ export class IOCore extends EventEmitter {
757
771
  */
758
772
  setMsgPromise(mid) {
759
773
  return new Promise((resolve, reject) => {
760
- this.promiseMap.set(mid, [resolve, reject])
761
- setTimeout(e => {
774
+ const timeoutId = setTimeout(e => {
762
775
  if (this.promiseMap.has(mid)) {
763
776
  reject('timeout');
764
777
  this.promiseMap.delete(mid)
765
778
  }
766
779
  }, this.promiseTimeOut);
780
+ this.promiseMap.set(mid, [resolve, reject, timeoutId]);
767
781
  })
768
782
  }
769
783
 
@@ -777,7 +791,8 @@ export class IOCore extends EventEmitter {
777
791
  if (!res) return
778
792
 
779
793
  if (this.promiseMap.has(res.mid)) {
780
- let [resolve, reject] = this.promiseMap.get(res.mid)
794
+ let [resolve, reject, timeoutId] = this.promiseMap.get(res.mid)
795
+ clearTimeout(timeoutId)
781
796
  this.promiseMap.delete(res.mid)
782
797
 
783
798
  if (res.status < 128) {
@@ -886,9 +901,9 @@ export class IOCore extends EventEmitter {
886
901
 
887
902
 
888
903
  /**
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.
904
+ * Sends a request to a target and topic.(remote api call)
905
+ * @param {string} target - The target(api name) of the request.
906
+ * @param {string} topic - The topic(api function name) of the request.
892
907
  * @param {...any} args - Optional arguments for the request.
893
908
  * @returns {Promise<any>}
894
909
  */
@@ -1164,6 +1179,7 @@ export class IOCore extends EventEmitter {
1164
1179
  // STATES constant name : string upperCase
1165
1180
  // eventName, .stateName : string lowerCase
1166
1181
  // .state : number
1182
+ // console.log('### stateChange reason:', emitEventAndMessage )
1167
1183
  let eventName = state.toLowerCase()
1168
1184
  this.state = STATES[state.toUpperCase()] // state: number
1169
1185
  if (emitEventAndMessage) this.emit(eventName, emitEventAndMessage)
@@ -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.']
@@ -429,5 +429,20 @@ export class Manager {
429
429
  }
430
430
  }
431
431
 
432
+ close() {
433
+ clearInterval(this.pingIntervalID);
434
+ if (this.monitIntervalID) {
435
+ clearInterval(this.monitIntervalID);
436
+ }
437
+
438
+ this.remotes.forEach(remote => {
439
+ remote.close();
440
+ });
432
441
 
442
+ this.remotes.clear();
443
+ this.cid2remote.clear();
444
+ this.retain_messages.clear();
445
+ this.channel_map.clear();
446
+
447
+ }
433
448
  }
@@ -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 {
@@ -40,6 +40,8 @@ export class Remote extends RemoteCore {
40
40
  socket.on("ping", this.receiveMonitor.bind(this));
41
41
  socket.on("error", (e) => { console.log('Websocket error', e, e.code) });
42
42
  socket.onclose = (e) => {
43
+ // Clean up all listeners on the socket to prevent leaks
44
+ socket.removeAllListeners();
43
45
  this.manager.removeRemote(this);
44
46
  };
45
47
  } else { // TCP else
@@ -59,6 +61,9 @@ export class Remote extends RemoteCore {
59
61
 
60
62
  socket.on('error', e => { console.log('TCP Socket error', e) })
61
63
  socket.on('close', e => {
64
+ // Clean up listeners on both the socket and the parser
65
+ socket.removeAllListeners();
66
+ this.congRx.removeAllListeners();
62
67
  this.manager.removeRemote(this);
63
68
  })
64
69