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
package/dist/iosignal.js CHANGED
@@ -13,7 +13,9 @@ import require$$2 from 'os';
13
13
  import require$$0$1 from 'buffer';
14
14
  import { memoryUsage } from 'process';
15
15
 
16
- var version = "3.1.0";
16
+ var version$1 = "3.3.0";
17
+ var pkg = {
18
+ version: version$1};
17
19
 
18
20
  var t$1,e$1={},r$1={};var n$1,i$1,o$1={};
19
21
  /*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */function f$1(){return n$1||(n$1=1,o$1.read=function(t,e,r,n,i){var o,f,u=8*i-n-1,s=(1<<u)-1,h=s>>1,a=-7,c=r?i-1:0,l=r?-1:1,p=t[e+c];for(c+=l,o=p&(1<<-a)-1,p>>=-a,a+=u;a>0;o=256*o+t[e+c],c+=l,a-=8);for(f=o&(1<<-a)-1,o>>=-a,a+=n;a>0;f=256*f+t[e+c],c+=l,a-=8);if(0===o)o=1-h;else {if(o===s)return f?NaN:1/0*(p?-1:1);f+=Math.pow(2,n),o-=h;}return (p?-1:1)*f*Math.pow(2,o-n)},o$1.write=function(t,e,r,n,i,o){var f,u,s,h=8*o-i-1,a=(1<<h)-1,c=a>>1,l=23===i?Math.pow(2,-24)-Math.pow(2,-77):0,p=n?0:o-1,y=n?1:-1,g=e<0||0===e&&1/e<0?1:0;for(e=Math.abs(e),isNaN(e)||e===1/0?(u=isNaN(e)?1:0,f=a):(f=Math.floor(Math.log(e)/Math.LN2),e*(s=Math.pow(2,-f))<1&&(f--,s*=2),(e+=f+c>=1?l/s:l*Math.pow(2,1-c))*s>=2&&(f++,s/=2),f+c>=a?(u=0,f=a):f+c>=1?(u=(e*s-1)*Math.pow(2,i),f+=c):(u=e*Math.pow(2,c-1)*Math.pow(2,i),f=0));i>=8;t[r+p]=255&u,p+=y,u/=256,i-=8);for(f=f<<i|u,h+=i;h>0;t[r+p]=255&f,p+=y,f/=256,h-=8);t[r+p-y]|=128*g;}),o$1}
@@ -983,7 +985,7 @@ class IOCore extends EventEmitter {
983
985
  close() {
984
986
  if (this._closed) return;
985
987
  this._closed = true;
986
-
988
+ // console.log('####### IOCOre.js close() called')
987
989
  // If auto-reconnect is disabled, we must stop the keep-alive timer.
988
990
  if (this.autoReconnect === false) {
989
991
  clearInterval(this.connectionCheckerIntervalID);
@@ -1204,13 +1206,26 @@ class IOCore extends EventEmitter {
1204
1206
  case IOMsg.PONG:
1205
1207
  break;
1206
1208
 
1209
+ case IOMsg.ECHO:
1210
+ try {
1211
+ let str = decoder$4.decode(buffer.subarray(1));
1212
+
1213
+ this.emit('echo', str);
1214
+ } catch (error) {
1215
+ this.emit('error', new Error('ECHO data error'));
1216
+ }
1217
+ break;
1218
+
1207
1219
  case IOMsg.IAM_RES:
1208
1220
  try {
1209
1221
  let str = decoder$4.decode(buffer.subarray(1));
1210
1222
  let jsonInfo = JSON.parse(str);
1211
- if (jsonInfo.ip) {
1212
- this.ip = jsonInfo.ip;
1213
- }
1223
+ if (jsonInfo.ip) { this.ip = jsonInfo.ip; }
1224
+ if (jsonInfo.nick) { this.nick = jsonInfo.nick; }
1225
+ if (jsonInfo.did) { this.did = jsonInfo.did; }
1226
+ if (jsonInfo.uid) { this.uid = jsonInfo.uid; }
1227
+ // TIP. cid from [CID_RES], level from [QUOTA_LEVEL]
1228
+ this.emit('iam_res', str );
1214
1229
  } catch (error) {
1215
1230
  this.emit('error', new Error('IAM_RES data error'));
1216
1231
  }
@@ -1229,13 +1244,14 @@ class IOCore extends EventEmitter {
1229
1244
  let quotaLevel = buffer[1];
1230
1245
  this.level = quotaLevel;
1231
1246
  this.quota = quotaTable[quotaLevel];
1232
- console.log('## QUOTA:', quotaLevel, JSON.stringify(this.quota));
1247
+ console.log('[QUOTA_LEVEL]', JSON.stringify(this.quota));
1233
1248
  break;
1234
1249
 
1235
1250
  case IOMsg.SERVER_CLEAR_AUTH:
1236
1251
  this.useAuth = false;
1237
1252
  this.boho.clearAuth();
1238
1253
  this.stop();
1254
+ console.log('[SERVER_CLEAR_AUTH] io stop.');
1239
1255
  break;
1240
1256
 
1241
1257
  case IOMsg.SERVER_REDIRECT:
@@ -1439,7 +1455,7 @@ class IOCore extends EventEmitter {
1439
1455
  */
1440
1456
  echo(args) {
1441
1457
  if (args) {
1442
- console.log('echo args:', args);
1458
+ // console.log('send echo args:', args)
1443
1459
  this.send_enc_mode(M$1.pack(
1444
1460
  M$1.MB('#MsgType', '8', IOMsg.ECHO),
1445
1461
  M$1.MB('#msg', args)
@@ -1528,13 +1544,13 @@ class IOCore extends EventEmitter {
1528
1544
  */
1529
1545
  setMsgPromise(mid) {
1530
1546
  return new Promise((resolve, reject) => {
1531
- this.promiseMap.set(mid, [resolve, reject]);
1532
- setTimeout(e => {
1547
+ const timeoutId = setTimeout(e => {
1533
1548
  if (this.promiseMap.has(mid)) {
1534
1549
  reject('timeout');
1535
1550
  this.promiseMap.delete(mid);
1536
1551
  }
1537
1552
  }, this.promiseTimeOut);
1553
+ this.promiseMap.set(mid, [resolve, reject, timeoutId]);
1538
1554
  })
1539
1555
  }
1540
1556
 
@@ -1548,7 +1564,8 @@ class IOCore extends EventEmitter {
1548
1564
  if (!res) return
1549
1565
 
1550
1566
  if (this.promiseMap.has(res.mid)) {
1551
- let [resolve, reject] = this.promiseMap.get(res.mid);
1567
+ let [resolve, reject, timeoutId] = this.promiseMap.get(res.mid);
1568
+ clearTimeout(timeoutId);
1552
1569
  this.promiseMap.delete(res.mid);
1553
1570
 
1554
1571
  if (res.status < 128) {
@@ -1657,9 +1674,9 @@ class IOCore extends EventEmitter {
1657
1674
 
1658
1675
 
1659
1676
  /**
1660
- * Sends a request to a target and topic.
1661
- * @param {string} target - The target of the request.
1662
- * @param {string} topic - The topic of the request.
1677
+ * Sends a request to a target and topic.(remote api call)
1678
+ * @param {string} target - The target(api name) of the request.
1679
+ * @param {string} topic - The topic(api function name) of the request.
1663
1680
  * @param {...any} args - Optional arguments for the request.
1664
1681
  * @returns {Promise<any>}
1665
1682
  */
@@ -1935,6 +1952,7 @@ class IOCore extends EventEmitter {
1935
1952
  // STATES constant name : string upperCase
1936
1953
  // eventName, .stateName : string lowerCase
1937
1954
  // .state : number
1955
+ // console.log('### stateChange reason:', emitEventAndMessage )
1938
1956
  let eventName = state.toLowerCase();
1939
1957
  this.state = STATES[state.toUpperCase()]; // state: number
1940
1958
  if (emitEventAndMessage) this.emit(eventName, emitEventAndMessage);
@@ -1957,6 +1975,8 @@ const CongType = {
1957
1975
  // support LittleEndian system.
1958
1976
  function pack(payload) {
1959
1977
 
1978
+ if( payload.byteLength == undefined ) payload = Buffer.from(payload);
1979
+
1960
1980
  if (payload.byteLength < 256) { //one byte len
1961
1981
  return M$1.pack(
1962
1982
  M$1.MB('#type', '8', CongType.TYPE_LEN1),
@@ -7586,9 +7606,10 @@ class IOWS extends IOCore {
7586
7606
 
7587
7607
  let serverOption = {
7588
7608
  clientTracking: false,
7589
- port: 0, //wsPort
7609
+ port: null, // WebSocket
7610
+ congPort: null, // CongSocket
7590
7611
  httpServer: null,
7591
- congPort: 0,
7612
+ wsPath: null,
7592
7613
  timeout: 50000,
7593
7614
  showMessage: 'none',
7594
7615
  showMetric: 0,
@@ -7630,10 +7651,10 @@ let serverOption = {
7630
7651
  },
7631
7652
 
7632
7653
  auth: {
7633
- delay_auth_fail: 1000,
7654
+ delay_auth_fail: 600,
7634
7655
  },
7635
7656
 
7636
- memberOnly: false
7657
+ membersOnly: false
7637
7658
 
7638
7659
  };
7639
7660
 
@@ -7660,15 +7681,13 @@ class RemoteCore {
7660
7681
  this.ssid = RemoteCore.ssid++; // ordered index number
7661
7682
  this.manager = manager;
7662
7683
 
7663
- this.cid; // Communication Id
7664
- this.did; // Device Id
7665
- this.uid; // User Id
7666
- this.nick = "";
7667
-
7684
+ this.cid = ''; // Communication Id
7685
+ this.did = ''; // Device Id
7686
+ this.uid = ''; // User Id
7687
+ this.nick = '';
7668
7688
 
7669
- this.lastEchoMessage = "N";
7670
7689
  this.privateNode = false;
7671
- this.HOME_CHANNEL = "";
7690
+ this.HOME_CHANNEL = '';
7672
7691
  this.level = serverOption.defaultQuotaIndex;
7673
7692
  this.quota = quotaTable[this.level];
7674
7693
  this.isAdmin = false;
@@ -7684,7 +7703,8 @@ class RemoteCore {
7684
7703
  setState(state) {
7685
7704
  this.state = state;
7686
7705
  if (serverOption.debug.showAuthInfo) {
7687
- this.stateLog.push(state);
7706
+ // let s = state + ":" + CLIENT_STATE[ state]
7707
+ this.stateLog.push( state );
7688
7708
  console.log(this.stateLog.join('>'));
7689
7709
  }
7690
7710
  }
@@ -7699,7 +7719,7 @@ class RemoteCore {
7699
7719
 
7700
7720
 
7701
7721
  showMessageLog(message, isBinary) {
7702
- let from = this.boho.isAuthorized ? `did: ${this.did} ${this.cid}@` : `${this.cid}@`;
7722
+ let from = this.boho.isAuthorized ? `[FROM]did: ${this.did} cid:${this.cid}` : `[FROM]cid:${this.cid}`;
7703
7723
  if (isBinary) {
7704
7724
  let msgTypeName = IOMsg[message[0]];
7705
7725
  if (!msgTypeName) msgTypeName = tt.BohoMsg[message[0]];
@@ -7796,6 +7816,7 @@ class RemoteCore {
7796
7816
  case IOMsg.CID_REQ:
7797
7817
  if (this.state < CLIENT_STATE.SENT_SERVER_READY) {
7798
7818
  // Protocol violation: CID_REQ was sent before receiving the SERVER_READY signal.
7819
+ console.log('CID_REQ before SERVER_READY');
7799
7820
  this.close();
7800
7821
  }
7801
7822
 
@@ -7813,22 +7834,16 @@ class RemoteCore {
7813
7834
  this.setState(CLIENT_STATE.CID_READY);
7814
7835
  break;
7815
7836
 
7816
-
7817
- case IOMsg.ECHO: // TEXT only
7818
- try {
7819
- let msg = decoder$3.decode(message.subarray(1));
7820
- this.lastEchoMessage = msg;
7821
- } catch (error) {
7822
- // console.log('ECHO message is not a text')
7837
+ case IOMsg.ECHO:
7838
+ if( message.byteLength < 30) {
7839
+ this.send(message, isBinary);
7823
7840
  }
7824
- this.send(message, isBinary);
7825
7841
  break;
7826
7842
 
7827
7843
  case IOMsg.IAM:
7828
- if (message.byteLength > 1) {
7829
- let iamInfo = message.subarray(1);
7830
- this.nick = decoder$3.decode(iamInfo);
7831
- console.log('iam nick reset', this.nick);
7844
+ if (message.byteLength > 1 && message.byteLength < 30) {
7845
+ // if iam request has a text message then reset the nick property.
7846
+ this.nick = decoder$3.decode(message.subarray(1));
7832
7847
  }
7833
7848
  this.iamResponse();
7834
7849
  break;
@@ -7849,7 +7864,7 @@ class RemoteCore {
7849
7864
 
7850
7865
  case IOMsg.UNSUBSCRIBE:
7851
7866
  if (message.byteLength == 2) {
7852
- this.manager.unsubscribe([""], this);
7867
+ this.manager.unsubscribe([''], this);
7853
7868
  } else if (message.byteLength >= 3) {
7854
7869
  let tagLen = message.readUInt8(1);
7855
7870
 
@@ -7923,7 +7938,7 @@ class RemoteCore {
7923
7938
  case tt.BohoMsg.AUTH_REQ:
7924
7939
  if (!this.manager.authManager) return
7925
7940
  if (this.state < CLIENT_STATE.SENT_SERVER_READY) {
7926
- // console.log('protocol error. auth_req before server_ready')
7941
+ console.log('protocol error. auth_req before server_ready');
7927
7942
  this.close();
7928
7943
  }
7929
7944
  this.setState(CLIENT_STATE.RECV_AUTH_REQ);
@@ -7936,22 +7951,23 @@ class RemoteCore {
7936
7951
  case tt.BohoMsg.AUTH_HMAC:
7937
7952
  if (!this.manager.authManager) return
7938
7953
  if (this.state < CLIENT_STATE.SENT_SERVER_NONCE) {
7939
- // console.log('protocol error. auth_hmac before server_nonce')
7954
+ console.log('protocol error. auth_hmac before server_nonce');
7940
7955
  this.close();
7941
7956
  }
7942
7957
  this.setState(CLIENT_STATE.RECV_AUTH_HMAC);
7943
7958
  //async
7944
7959
  this.manager.authManager.verify_auth_hmac(message, this)
7945
7960
  .then(authInfo => {
7946
- if (authInfo) {
7947
- this.manager.deligateSignal(remote, '@$name', authInfo.name);
7961
+ if (authInfo?.name) {
7962
+ this.manager.deligateSignal(this, '@$name', authInfo.name);
7948
7963
  // console.log('device login success authInfo', authInfo)
7949
7964
  // this.manager.sender('@$name', authInfo.name )
7950
7965
  }
7951
7966
 
7967
+ // console.log('[VERIFY AUTH_HMAC] SubClass of BohoAuth return authInfo:', authInfo )
7952
7968
  })
7953
7969
  .catch(e => {
7954
-
7970
+ console.log('authInfo return fail.', e );
7955
7971
  });
7956
7972
 
7957
7973
  return;
@@ -7977,23 +7993,19 @@ class RemoteCore {
7977
7993
  response(msgId, statusCode, body) {
7978
7994
  // console.log('response body', body)
7979
7995
  if (body) {
7980
-
7981
7996
  this.send_enc_mode(M$1.pack(
7982
7997
  MB('#type', '8', IOMsg.RESPONSE_MBP),
7983
7998
  MB('status', '8', statusCode),
7984
7999
  MB('mid', '16', msgId),
7985
8000
  MB('body', body)
7986
8001
  ));
7987
-
7988
8002
  } else {
7989
8003
  this.send_enc_mode(M$1.pack(
7990
8004
  MB('#type', '8', IOMsg.RESPONSE_MBP),
7991
8005
  MB('status', '8', statusCode),
7992
8006
  MB('mid', '16', msgId)
7993
8007
  ));
7994
-
7995
8008
  }
7996
-
7997
8009
  }
7998
8010
 
7999
8011
 
@@ -8034,9 +8046,9 @@ class RemoteCore {
8034
8046
 
8035
8047
 
8036
8048
 
8037
- iamResponse(info = "") {
8049
+ iamResponse(info = '') {
8038
8050
 
8039
- if (info == "") {
8051
+ if (info == '') {
8040
8052
  let channels = [];
8041
8053
  for (let tag of this.channels.keys()) {
8042
8054
  channels.push(tag);
@@ -8044,15 +8056,17 @@ class RemoteCore {
8044
8056
 
8045
8057
  info = {
8046
8058
  "ssid": this.ssid,
8047
- "uid": this.uid,
8048
8059
  "cid": this.cid,
8049
8060
  "did": this.did,
8061
+ "uid": this.uid,
8062
+ "level": this.level,
8050
8063
  "nick": this.nick,
8051
8064
  "ip": this.ip
8052
8065
  , 'tag': channels
8053
8066
  };
8054
8067
  }
8055
8068
 
8069
+ // console.log( 'iam res info', info )
8056
8070
  let pack = M$1.pack(
8057
8071
  MB('#MsgType', '8', IOMsg.IAM_RES),
8058
8072
  MB('#info', info)
@@ -8131,6 +8145,8 @@ class Remote extends RemoteCore {
8131
8145
  socket.on("ping", this.receiveMonitor.bind(this));
8132
8146
  socket.on("error", (e) => { console.log('Websocket error', e, e.code); });
8133
8147
  socket.onclose = (e) => {
8148
+ // Clean up all listeners on the socket to prevent leaks
8149
+ socket.removeAllListeners();
8134
8150
  this.manager.removeRemote(this);
8135
8151
  };
8136
8152
  } else { // TCP else
@@ -8150,6 +8166,9 @@ class Remote extends RemoteCore {
8150
8166
 
8151
8167
  socket.on('error', e => { console.log('TCP Socket error', e); });
8152
8168
  socket.on('close', e => {
8169
+ // Clean up listeners on both the socket and the parser
8170
+ socket.removeAllListeners();
8171
+ this.congRx.removeAllListeners();
8153
8172
  this.manager.removeRemote(this);
8154
8173
  });
8155
8174
 
@@ -8433,8 +8452,7 @@ class Metric {
8433
8452
  ip: remote.ip,
8434
8453
  uptime: Math.trunc((Date.now() - remote.socket.openTime) / 1000),
8435
8454
  nick: remote.nick,
8436
- ssid: remote.ssid,
8437
- echo: remote.lastEchoMessage
8455
+ ssid: remote.ssid
8438
8456
  }
8439
8457
  } else if (mode == 3) {
8440
8458
  return {
@@ -8649,11 +8667,11 @@ class Manager {
8649
8667
  }
8650
8668
 
8651
8669
  sender(tag, remote, message) {
8652
- if (serverOption.memberOnly && !remote.boho.isAuthorized) {
8653
- // console.log("## MemberOnly, reject unAuthorized remote." )
8670
+ if (serverOption.membersOnly && !remote.boho.isAuthorized) {
8671
+ console.log("[membersOnly] unAuthorized remote.",tag );
8654
8672
  remote.send(Buffer.from([IOMsg.SERVER_CLEAR_AUTH]));
8655
8673
  remote.close();
8656
- return ['err', 'not autrized']
8674
+ return ['err', 'unAuthorized']
8657
8675
  }
8658
8676
 
8659
8677
  if (tag.indexOf('$') == 0) return ['err', 'prefix $ is reserved for userSet tag.']
@@ -8873,7 +8891,22 @@ class Manager {
8873
8891
  }
8874
8892
  }
8875
8893
 
8894
+ close() {
8895
+ clearInterval(this.pingIntervalID);
8896
+ if (this.monitIntervalID) {
8897
+ clearInterval(this.monitIntervalID);
8898
+ }
8899
+
8900
+ this.remotes.forEach(remote => {
8901
+ remote.close();
8902
+ });
8903
+
8904
+ this.remotes.clear();
8905
+ this.cid2remote.clear();
8906
+ this.retain_messages.clear();
8907
+ this.channel_map.clear();
8876
8908
 
8909
+ }
8877
8910
  }
8878
8911
 
8879
8912
  const STATUS = {
@@ -8889,58 +8922,67 @@ class Server extends require$$0$3 {
8889
8922
  super();
8890
8923
  this.apiNames = new Set();
8891
8924
  this.wss = {};
8925
+ this.port = null;
8926
+ this.congPort = null;
8892
8927
 
8893
8928
  if (options.timeout) {
8894
8929
  let pingT = parseInt(options.timeout);
8895
8930
  if (pingT && pingT >= 1000) serverOption.timeout = pingT;
8896
8931
  }
8897
-
8898
8932
  if (options.monitorPeriod) {
8899
8933
  let monitorT = parseInt(options.monitorPeriod);
8900
8934
  if (monitorT && monitorT >= 1000) serverOption.monitorPeriod = monitorT;
8901
8935
  }
8902
-
8903
-
8904
8936
  if (options.showMessage) {
8905
8937
  serverOption.showMessage = options.showMessage;
8906
8938
  }
8907
-
8908
8939
  if (options.showMetric) {
8909
8940
  serverOption.showMetric = options.showMetric;
8910
8941
  }
8911
-
8912
- if (options.timeout) {
8913
- serverOption.timeout = options.timeout;
8942
+ if (options.membersOnly) {
8943
+ serverOption.membersOnly = options.membersOnly;
8914
8944
  }
8915
8945
 
8916
- if (options.port) {
8917
- serverOption.port = parseInt(options.port);
8946
+
8947
+ if (options.port || options.port == 0) {
8948
+ this.port = parseInt(options.port);
8918
8949
  }
8919
8950
 
8920
8951
  if (options.httpServer) {
8921
- serverOption.httpServer = options.httpServer;
8952
+ this.httpServer = options.httpServer;
8953
+ }
8954
+
8955
+ if (options.wsPath) {
8956
+ this.wsPath = options.wsPath;
8922
8957
  }
8923
8958
 
8924
- if (options.congPort) {
8925
- serverOption.congPort = parseInt(options.congPort);
8959
+ if (options.congPort || options.congPort == 0) {
8960
+ this.congPort = parseInt(options.congPort);
8926
8961
  }
8927
8962
 
8928
8963
  this.manager = new Manager(this, authManager);
8929
8964
 
8930
- if (serverOption.port || serverOption.httpServer) this.startWSServer(serverOption);
8931
- if (serverOption.congPort) this.startCongServer(serverOption.congPort);
8965
+ this.serverCountToListen = 0;
8966
+ this.listeningServerCount = 0;
8967
+ if (this.port || this.port == 0 || this.httpServer) this.serverCountToListen++;
8968
+ if (this.congPort || this.congPort == 0) this.serverCountToListen++;
8932
8969
 
8933
- }
8970
+ if (this.serverCountToListen === 0) {
8971
+ throw new Error('Cannot create Server: At least one of "port", "httpServer", or "congPort" must be specified.');
8972
+ } else {
8973
+ if (this.port || this.port == 0 || this.httpServer) this.startWSServer();
8974
+ if (this.congPort || this.congPort == 0) this.startCongServer();
8975
+ }
8934
8976
 
8977
+ }
8935
8978
 
8936
- startWSServer(serverOption) {
8937
8979
 
8938
- if (serverOption.port) {
8939
- console.log('opening WebSocket Server port:', serverOption.port);
8940
- this.wss = new WebSocketServer({ port: serverOption.port });
8941
- } else if (serverOption.httpServer) {
8942
- console.log('opening WebSocket Server external httpServer:');
8943
- this.wss = new WebSocketServer({ server: serverOption.httpServer });
8980
+ startWSServer() {
8981
+ // console.log('optoins', options )
8982
+ if (this.port || this.port == 0) {
8983
+ this.wss = new WebSocketServer({ port: this.port , path: this.wsPath });
8984
+ } else if (this.httpServer ) {
8985
+ this.wss = new WebSocketServer({ server: this.httpServer, path: this.wsPath });
8944
8986
  } else {
8945
8987
  throw new TypeError(
8946
8988
  'One and only one of the "port", "httpServer"' +
@@ -8948,9 +8990,16 @@ class Server extends require$$0$3 {
8948
8990
  );
8949
8991
  }
8950
8992
 
8993
+ this.wss.once('listening', () => {
8994
+ this.port = this.wss.address().port;
8995
+ console.log('wss server bound port:', this.port );
8996
+ this.listeningServerCount++;
8997
+ if (this.listeningServerCount === this.serverCountToListen) {
8998
+ this.emit('ready');
8999
+ }
9000
+ });
8951
9001
 
8952
9002
  // this.wss.setMaxListeners(0)
8953
-
8954
9003
  this.wss.on('error', (err) => {
8955
9004
  console.error('### ws server error:', err.message);
8956
9005
  if (err.code == 'EADDRINUSE') {
@@ -8958,8 +9007,8 @@ class Server extends require$$0$3 {
8958
9007
  }
8959
9008
  });
8960
9009
 
8961
- this.wss.on('close', (err) => {
8962
- console.log('### WS server closed.', err);
9010
+ this.wss.on('close', () => {
9011
+ console.log('### WS server is closed.');
8963
9012
  });
8964
9013
 
8965
9014
  this.wss.on('connection', (ws, req) => {
@@ -8970,9 +9019,7 @@ class Server extends require$$0$3 {
8970
9019
 
8971
9020
  }
8972
9021
 
8973
- startCongServer(congPort) {
8974
- console.log('opening CongSocket Server:', congPort);
8975
-
9022
+ startCongServer() {
8976
9023
  this.congServer = net.createServer((socket) => {
8977
9024
  this.manager.addRemote(socket);
8978
9025
  })
@@ -8981,61 +9028,120 @@ class Server extends require$$0$3 {
8981
9028
  if (err.code == 'EADDRINUSE') {
8982
9029
  process.exit();
8983
9030
  }
8984
- }).listen(congPort, () => {
8985
- // console.log('congsocket server bound' );
9031
+ }).on('close', () => {
9032
+ console.log('### congsocket server is closed.');
9033
+ }).listen(this.congPort, () => {
9034
+ this.congPort = this.congServer.address().port;
9035
+ console.log('congsocket server bound port:', this.congPort );
9036
+ this.listeningServerCount++;
9037
+ if (this.listeningServerCount === this.serverCountToListen) {
9038
+ this.emit('ready');
9039
+ }
8986
9040
  });
8987
9041
  }
8988
9042
 
8989
-
8990
-
9043
+ /**
9044
+ * API register.
9045
+ * target: api_name <string>
9046
+ * api: api_module <function module|class instance>
9047
+ * return this
9048
+ */
8991
9049
  api(target, api) {
8992
9050
 
8993
- this.apiNames.add(target);
8994
-
9051
+ if( typeof target !== 'string'){
9052
+ throw new Error(`api( target ,api ) target name is not a string.`)
9053
+ }
8995
9054
  if (!api.checkPermission || typeof api.checkPermission != 'function') {
8996
- throw new Error('wrong api interface. no checkPermission function.')
9055
+ throw new Error(`API ${target} : no checkPermission or not a function.`)
8997
9056
  }
8998
-
8999
-
9000
- if (typeof api.request == 'function' && Array.isArray(api.commands)) {
9001
- console.log(`API TYPE1. A Class with one request function. target: ${target} list: ${api.commands}`);
9002
-
9003
- this.on(target, (remote, req) => {
9004
- if (api.checkPermission(remote, req)) {
9005
- api.request(remote, req);
9006
- } else {
9007
- remote.response(req.mid, STATUS.ERROR, "NO_PERMISSION.");
9057
+ if(!api.commands || !Array.isArray(api.commands) ){
9058
+ throw new Error(`API ${target} : no commands or !Array.`)
9059
+ }
9060
+
9061
+ // API TYPE 1. single request() function.
9062
+ if ( api.request && typeof api.request == 'function' ) {
9063
+ this.on(target, async (remote, req) => {
9064
+ try {
9065
+ if (!api.checkPermission(remote, req)) {
9066
+ remote.response(req.mid, STATUS.ERROR, "NO_PERMISSION.");
9067
+ return
9068
+ }
9069
+ if( api.commands.includes( req.topic )){
9070
+ console.log('server api req', req );
9071
+ await api.request(remote, req);
9072
+ }else {
9073
+ remote.response(req.mid, STATUS.ERROR, "UNKNOWN_COMMAND");
9074
+ }
9075
+ } catch (error) {
9076
+ console.error(`Unhandled API Error in target [${target}]:`, error);
9077
+ remote.response(req.mid, STATUS.ERROR, "INTERNAL_SERVER_ERROR");
9008
9078
  }
9009
9079
  });
9010
-
9011
9080
  } else {
9012
- let apiList = [];
9013
- Object.keys(api).forEach(v => {
9014
- if (typeof api[v] === 'function') apiList.push(v);
9015
- console.log('api list', typeof api[v], v);
9016
- });
9017
-
9018
- console.log(`API TYPE2. Multiple function list. target: ${target} list:${apiList}`);
9019
-
9020
- this.on(target, (remote, req) => {
9021
- let r;
9022
- if (!api.checkPermission(remote, req)) {
9023
- r = "NO_PERMISSION.";
9024
- } else {
9025
- if (api[req.topic]) {
9026
- console.log(`API TYPE2. request target: ${target} has topic:${req.topic}`);
9027
- api[req.topic](remote, req);
9081
+ // API TYPE 2. multiple functions.
9082
+ this.on(target, async (remote, req) => {
9083
+ try {
9084
+ if (!api.checkPermission(remote, req)) {
9085
+ remote.response(req.mid, STATUS.ERROR, "NO_PERMISSION.");
9028
9086
  return
9029
- } else {
9030
- r = `target: ${req.target} has not topic name: ${req.topic}`;
9031
9087
  }
9088
+ if( api.commands.includes( req.topic )){
9089
+ await api[req.topic](remote, req);
9090
+ }else {
9091
+ remote.response(req.mid, STATUS.ERROR, "UNKNOWN_COMMAND");
9092
+ }
9093
+ } catch (error) {
9094
+ console.error(`Unhandled API Error in target [${target}]:`, error);
9095
+ remote.response(req.mid, STATUS.ERROR, "INTERNAL_SERVER_ERROR");
9032
9096
  }
9097
+ });
9098
+ }
9099
+ this.apiNames.add(target);
9100
+ console.log(`[API registed: ${target} ] accept commands: ${api.commands}`);
9101
+ return this
9102
+ }
9103
+
9104
+ close(callback) {
9105
+ console.log('closing iosignal server...');
9106
+ if (this.manager) {
9107
+ this.manager.close();
9108
+ }
9109
+
9110
+ this.apiNames.forEach(v => this.removeAllListeners(v));
9111
+ this.apiNames.clear();
9112
+
9113
+ let serverCount = 0;
9114
+ if (this.wss) serverCount++;
9115
+ if (this.congServer) serverCount++;
9033
9116
 
9034
- remote.response(req.mid, STATUS.ERROR, r);
9117
+ if (serverCount === 0) {
9118
+ if (callback) process.nextTick(callback);
9119
+ return;
9120
+ }
9121
+
9122
+ let closedCount = 0;
9123
+ const onClosed = () => {
9124
+ closedCount++;
9125
+ if (closedCount === serverCount) {
9126
+ console.log('IOSignal server is closed.');
9127
+ if (callback) callback();
9128
+ }
9129
+ };
9130
+
9131
+ if (this.wss) {
9132
+ this.wss.close(() => {
9133
+ // console.log('wss server closed.')
9134
+ onClosed();
9035
9135
  });
9036
9136
  }
9037
9137
 
9038
- return this
9138
+ if (this.congServer) {
9139
+ this.congServer.close((err) => {
9140
+ if (err) console.error('congServer close error', err);
9141
+ // console.log('cong server closed.')
9142
+ onClosed();
9143
+ });
9144
+ }
9039
9145
  }
9040
9146
 
9041
9147
  }
@@ -9055,10 +9161,9 @@ class BohoAuth {
9055
9161
  let peerInfo = `FAIL #${peer.ssid} reason:${reason} `;
9056
9162
  this.authLogger.log(peerInfo);
9057
9163
  }
9058
-
9059
- console.log('## AUTH_FAIL reason:', reason);
9060
- // peer.send( Buffer.from( [Boho.BohoMsg.AUTH_FAIL] ))
9164
+ // console.log('##### AUTH_FAIL reason: ', reason)
9061
9165
  peer.setState(CLIENT_STATE.AUTH_FAIL);
9166
+ // add some delay time.
9062
9167
  setTimeout(e => {
9063
9168
  peer.send(Buffer.from([tt.BohoMsg.AUTH_FAIL]));
9064
9169
  }, serverOption.auth.delay_auth_fail);
@@ -9068,30 +9173,31 @@ class BohoAuth {
9068
9173
  try {
9069
9174
  //1. unpack
9070
9175
  let infoPack = M$1.unpack(auth_hmac, tt.Meta.AUTH_HMAC);
9176
+
9071
9177
  if (!infoPack) {
9072
9178
  this.send_auth_fail(peer, 'unpack auth_pack fail');
9073
9179
  return
9074
9180
  }
9075
-
9181
+
9076
9182
  let id = "";
9077
9183
  if (infoPack.id8.includes(0)) {
9078
9184
  id = decoder.decode(infoPack.id8.subarray(0, infoPack.id8.indexOf(0)));
9079
9185
  } else {
9080
9186
  id = decoder.decode(infoPack.id8);
9081
9187
  }
9082
-
9188
+
9083
9189
  //2. get key of id from DB
9084
9190
  let authInfo = await this.getAuth(id);
9085
9191
 
9086
- // console.log('##### authInfo',authInfo )
9087
- if (serverOption.debug.showAuthInfo) {
9088
- }
9089
-
9090
9192
  if (!authInfo) {
9091
9193
  this.send_auth_fail(peer, 'NO ID:' + id);
9092
9194
  return
9093
9195
  }
9094
9196
 
9197
+ if (serverOption.debug.showAuthInfo) {
9198
+ console.log('[debug]showAuthInfo',authInfo );
9199
+ }
9200
+
9095
9201
  // console.log('db authInfo.key: ', authInfo.key)
9096
9202
  peer.boho.copy_id8(infoPack.id8);
9097
9203
  // type of key
@@ -9142,6 +9248,7 @@ class BohoAuth {
9142
9248
 
9143
9249
  //6. delete current (temp rand)cid if exist.
9144
9250
  if (peer.cid) {
9251
+ console.log('duplicate cid');
9145
9252
  peer.manager.cid2remote.delete(peer.cid);
9146
9253
  }
9147
9254
 
@@ -9149,6 +9256,7 @@ class BohoAuth {
9149
9256
  peer.did = id;
9150
9257
  peer.cid = authInfo.cid;
9151
9258
  peer.nick = authInfo.cid; // temporary: nick as cid
9259
+ if( authInfo.uid ) peer.uid = authInfo.uid;
9152
9260
 
9153
9261
  //8. setting quota level.
9154
9262
  let quotaLevel = serverOption.defaultQuotaIndex;
@@ -9187,14 +9295,12 @@ class BohoAuth {
9187
9295
  }
9188
9296
  return authInfo
9189
9297
  } catch (error) {
9190
- this.send_auth_fail(peer, 'caught: unknown error' + error);
9298
+ this.send_auth_fail(peer, '[BohoAuth]caught: unknown error:' + error);
9191
9299
  }
9192
9300
 
9193
9301
  }
9194
9302
 
9195
9303
 
9196
-
9197
-
9198
9304
  }
9199
9305
 
9200
9306
  class Auth_File extends BohoAuth {
@@ -9350,11 +9456,15 @@ class Auth_Redis extends BohoAuth {
9350
9456
 
9351
9457
  // get device key from DB. (for Boho auth.)
9352
9458
  async getAuth(id) {
9353
- let result = await this.redis.hGetAll(DEVICE_PREFIX + id);
9354
- if (result.key) return result
9459
+ try {
9460
+ let result = await this.redis.hGetAll(DEVICE_PREFIX + id);
9461
+ if (result.key) return result
9462
+ } catch (error) {
9463
+ console.log('getAuth', error);
9464
+ }
9355
9465
  }
9356
9466
 
9357
-
9467
+ //api_sudo call
9358
9468
  async getAuthIdList() {
9359
9469
  let result = await this.redis.keys(DEVICE_PREFIX + '*');
9360
9470
  result = result.map(v => {
@@ -9369,6 +9479,7 @@ class Auth_Redis extends BohoAuth {
9369
9479
  return this.redis.hSet(DEVICE_PREFIX + id, { 'key': Base64hashKey, 'cid': cid, 'level': level })
9370
9480
  }
9371
9481
 
9482
+ //api_sudo call
9372
9483
  async delAuth(id) {
9373
9484
  return this.redis.del(DEVICE_PREFIX + id)
9374
9485
  }
@@ -9379,16 +9490,16 @@ class Auth_Redis extends BohoAuth {
9379
9490
 
9380
9491
  }
9381
9492
 
9382
- const MIN_LEVEL$2 = 0;
9493
+ const commands$1 = ['echo', 'date', 'unixtime'];
9383
9494
 
9495
+ const MIN_LEVEL$2 = 0;
9384
9496
  function checkPermission$1(remote) {
9385
- if (remote.level >= MIN_LEVEL$2) {
9386
- return true
9387
- } else {
9388
- return false
9389
- }
9497
+ return (remote.level >= MIN_LEVEL$2) ? true : false;
9390
9498
  }
9391
9499
 
9500
+ /**
9501
+ * client api call example: await io.req('reply','echo','hello')
9502
+ */
9392
9503
  async function echo(remote, req) {
9393
9504
  if (!req.args)
9394
9505
  remote.response(req.mid, STATUS.ERROR, 'no message to echo');
@@ -9396,11 +9507,17 @@ async function echo(remote, req) {
9396
9507
  remote.response(req.mid, STATUS.OK, req.args);
9397
9508
  }
9398
9509
 
9510
+ /**
9511
+ * client api call example: await io.req('reply','date')
9512
+ */
9399
9513
  async function date(remote, req) {
9400
9514
  let r = new Date().toUTCString();
9401
9515
  remote.response(req.mid, STATUS.OK, r);
9402
9516
  }
9403
9517
 
9518
+ /**
9519
+ * client api call example: await io.req('reply','unixtime')
9520
+ */
9404
9521
  async function unixtime(remote, req) {
9405
9522
  let r = Math.floor(Date.now() / 1000);
9406
9523
  remote.response(req.mid, STATUS.OK, r);
@@ -9409,13 +9526,12 @@ async function unixtime(remote, req) {
9409
9526
  var api_reply = /*#__PURE__*/Object.freeze({
9410
9527
  __proto__: null,
9411
9528
  checkPermission: checkPermission$1,
9529
+ commands: commands$1,
9412
9530
  date: date,
9413
9531
  echo: echo,
9414
9532
  unixtime: unixtime
9415
9533
  });
9416
9534
 
9417
- const MIN_LEVEL$1 = 255;
9418
-
9419
9535
  const commands = [
9420
9536
  'cid', 'remotes', 'clients',
9421
9537
  'channels', 'subscribers',
@@ -9425,13 +9541,9 @@ const commands = [
9425
9541
  'adddevice', 'getdevice', 'deldevice'
9426
9542
  ];
9427
9543
 
9428
-
9544
+ const MIN_LEVEL$1 = 255;
9429
9545
  function checkPermission(remote) {
9430
- if (remote.level >= MIN_LEVEL$1) {
9431
- return true
9432
- } else {
9433
- return false
9434
- }
9546
+ return (remote.level >= MIN_LEVEL$1) ? true : false;
9435
9547
  }
9436
9548
 
9437
9549
  async function request(remote, req) {
@@ -9508,15 +9620,23 @@ var api_sudo = /*#__PURE__*/Object.freeze({
9508
9620
  request: request
9509
9621
  });
9510
9622
 
9623
+ /**
9624
+ * RedisAPI <IOSignal API>
9625
+ *
9626
+ * iosignal 클라이언트의 redis 질의 요청을 받으면,
9627
+ * redisClient 로 연결된 redis-server 에게 질의 후 결과를 되돌려 준다.
9628
+ *
9629
+ * client ex:
9630
+ * await io.req('redis','hget','user:id' )
9631
+ *
9632
+ */
9633
+
9511
9634
  const MIN_LEVEL = 200;
9512
9635
  const COMMANDS = [
9513
- 'GET', 'SET',
9514
- 'HGETALL', 'HGET',
9515
- 'HSET', 'SADD',
9516
- 'SISMEMBER', 'SMEMBERS',
9517
- 'EXISTS', 'SREM',
9518
- 'DEL', 'KEYS',
9519
- 'SAVE'
9636
+ 'GET', 'SET', 'HGETALL', 'HGET', 'HSET', 'SADD', 'SISMEMBER',
9637
+ 'SMEMBERS', 'EXISTS', 'SREM', 'DEL', 'KEYS', 'SAVE',
9638
+ 'get', 'set', 'hGetAll', 'hGet', 'hSet', 'sAdd', 'sIsMember',
9639
+ 'sMembers', 'exists', 'sRem', 'del', 'keys', 'save'
9520
9640
  ];
9521
9641
 
9522
9642
  class RedisAPI {
@@ -9529,44 +9649,27 @@ class RedisAPI {
9529
9649
  this.commands = COMMANDS;
9530
9650
  }
9531
9651
 
9532
-
9533
9652
  checkPermission(remote, req) {
9534
- console.log('checkPermission', remote.level, 'vs', this.minLevel);
9535
- if (remote.level >= this.minLevel) {
9536
- return true
9537
- } else {
9538
- return false
9539
- }
9653
+ return (remote.level >= this.minLevel) ? true : false;
9540
9654
  }
9541
9655
 
9542
-
9543
9656
  async request(remote, req) {
9544
-
9545
9657
  let result;
9546
- let status = STATUS.OK;
9547
9658
  try {
9548
9659
  let cmd = req.topic;
9549
- cmd = cmd.toUpperCase();
9550
- if (COMMANDS.includes(cmd)) {
9551
- console.log('RedisAPI call:', cmd, req.args);
9552
-
9553
- if (req.args.length > 0) {
9554
- result = await this.redisClient[cmd](...req.args);
9555
- } else {
9556
- result = await this.redisClient[cmd]();
9557
- }
9558
-
9559
-
9660
+ if (req.args?.length > 0) {
9661
+ result = await this.redisClient[cmd](...req.args);
9560
9662
  } else {
9561
- status = STATUS.ERROR;
9663
+ result = await this.redisClient[cmd]();
9562
9664
  }
9563
- remote.response(req.mid, status, result);
9665
+ remote.response(req.mid, STATUS.OK, result);
9564
9666
  } catch (e) {
9565
9667
  remote.response(req.mid, STATUS.ERROR, e.message);
9566
9668
  }
9567
-
9568
9669
  }
9569
9670
 
9570
9671
  }
9571
9672
 
9673
+ const version = pkg.version;
9674
+
9572
9675
  export { API_TYPE, Auth_Env, Auth_File, Auth_Redis, tt as Boho, BohoAuth, CLIENT_STATE, CongRx, ENC_MODE, FileLogger, IOWS as IO, IOCongSocket, IOMsg, M$1 as MBP, PAYLOAD_TYPE, RedisAPI, SIZE_LIMIT, STATES, STATUS$1 as STATUS, Server, api_reply, api_sudo, getPayloadFromSignalPack, getSignalPack, pack, parsePayload, serverOption, version };