iosignal 6.1.0 → 6.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,7 +16,7 @@ var fs = require('fs');
16
16
  var process$1 = require('process');
17
17
  var path = require('path');
18
18
 
19
- var version$1 = "6.1.0";
19
+ var version$1 = "6.1.2";
20
20
  var pkg = {
21
21
  version: version$1};
22
22
 
@@ -834,6 +834,7 @@ class IOCore extends EventEmitter {
834
834
  * @type {string}
835
835
  */
836
836
  this.stateName = this.getStateName(); // String type
837
+ this._stateRevision = 0;
837
838
 
838
839
  /**
839
840
  * Transmitted message counter.
@@ -970,14 +971,14 @@ class IOCore extends EventEmitter {
970
971
  * If autoReconnect is false, it also clears the keep-alive timer.
971
972
  */
972
973
  close() {
973
- if (this._closed) return;
974
- this._closed = true;
975
974
  // console.log('####### IOCOre.js close() called')
976
975
  // If auto-reconnect is disabled, we must stop the keep-alive timer.
977
976
  if (this.autoReconnect === false) {
978
977
  clearInterval(this.connectionCheckerIntervalID);
979
978
  this.connectionCheckerIntervalID = null;
980
979
  }
980
+ if (this._closed) return;
981
+ this._closed = true;
981
982
 
982
983
  // socket clean
983
984
  if (this.socket) {
@@ -1000,8 +1001,11 @@ class IOCore extends EventEmitter {
1000
1001
  this.socket = null;
1001
1002
  }
1002
1003
  this.promiseMap.clear();
1003
- this.emit('closed');
1004
- this.stateChange('closed');
1004
+ this.cid = '';
1005
+ if (this.boho) this.boho.isAuthorized = false;
1006
+ if (this.state !== STATE.STOP && this.stateChange('closed')) {
1007
+ this.emit('closed');
1008
+ }
1005
1009
  }
1006
1010
 
1007
1011
  /**
@@ -1074,6 +1078,7 @@ class IOCore extends EventEmitter {
1074
1078
 
1075
1079
  // The actual connection is created here.
1076
1080
  this.createConnection(this.url);
1081
+ if (this._closed) return; // A connecting listener may have stopped the client.
1077
1082
 
1078
1083
  // Ensure the keep-alive timer is running.
1079
1084
  if (!this.connectionCheckerIntervalID) {
@@ -1098,9 +1103,7 @@ class IOCore extends EventEmitter {
1098
1103
  * Handles the 'close' event of the WebSocket.
1099
1104
  */
1100
1105
  onClose() {
1101
- this.boho.isAuthorized = false;
1102
- this.cid = "";
1103
- this.stateChange('closed');
1106
+ this.close();
1104
1107
  }
1105
1108
 
1106
1109
  /**
@@ -1111,11 +1114,11 @@ class IOCore extends EventEmitter {
1111
1114
  */
1112
1115
  login(id, key) {
1113
1116
  if( this.serverTimeNonce ){
1114
- console.log('iosignal.login serverTimeNonce', this.serverTimeNonce);
1115
1117
  this.auth(id, key);
1116
1118
  this.useAuth = true;
1117
1119
  let auth_pack = this.boho.auth_req(this.serverTimeNonce );
1118
1120
  this.send(auth_pack);
1121
+ this.stateChange('auth_req', 'auth_req');
1119
1122
  }
1120
1123
  return this
1121
1124
  }
@@ -1261,14 +1264,13 @@ class IOCore extends EventEmitter {
1261
1264
  break;
1262
1265
 
1263
1266
  case it.BohoMsg.SERVER_TIME_NONCE: // SERVER_READY
1264
- this.stateChange('server_ready', 'server_ready');
1267
+ buffer.copy(this.serverTimeNonce);
1268
+ if (!this.stateChange('server_ready', 'server_ready')) break;
1265
1269
  if (this.useAuth) {
1266
1270
  this.send(this.boho.auth_req(buffer));
1267
1271
  this.stateChange('auth_req','auth_req');
1268
1272
  // CID_REQ will be called, after auth_res.
1269
1273
  } else {
1270
- // keep server_time_nonce for manual login()
1271
- buffer.copy( this.serverTimeNonce);
1272
1274
  // CID_REQ here, if not using auth.
1273
1275
  this.send(Buffer$1.from([IOMsg.CID_REQ]));
1274
1276
  }
@@ -1393,14 +1395,16 @@ class IOCore extends EventEmitter {
1393
1395
  break;
1394
1396
 
1395
1397
  case it.BohoMsg.AUTH_FAIL:
1398
+ this.boho.isAuthorized = false;
1396
1399
  this.stateChange('auth_fail', 'auth_fail from server.');
1397
1400
  break;
1398
1401
 
1399
1402
  case it.BohoMsg.AUTH_RES:
1400
1403
  if (this.boho.verify_auth_res(buffer)) {
1401
- this.stateChange('auth_res', 'server sent auth_res');
1404
+ if (!this.stateChange('auth_res', 'server sent auth_res')) break;
1402
1405
  this.send(Buffer$1.from([IOMsg.CID_REQ]));
1403
1406
  } else {
1407
+ this.boho.isAuthorized = false;
1404
1408
  this.stateChange('auth_fail', 'verify_auth_res() invalid server_hmac');
1405
1409
  }
1406
1410
  break;
@@ -1937,6 +1941,9 @@ class IOCore extends EventEmitter {
1937
1941
  * 1. 상태가 변경 될 때만 'change' 이벤트 호출된다.
1938
1942
  * 2. emitEventAndMessage 옵션 값이 지정되야 해당 이벤트 이름이 호출된다.
1939
1943
  * 보통 이벤트 이름과 동일하게 적거나 이벤트 상황 안내문을 넣는다.
1944
+ * 3. 두 상태값 갱신 후 change, 개별 이벤트 순서로 호출한다.
1945
+ * 콜백에서 다른 상태로 전이하면 이전 상태의 개별 이벤트는 생략한다.
1946
+ * @returns {boolean} Whether this transition is still current after callbacks.
1940
1947
  */
1941
1948
  stateChange(state, emitEventAndMessage) {
1942
1949
  // STATE constant name <string> upperCase
@@ -1944,16 +1951,19 @@ class IOCore extends EventEmitter {
1944
1951
  // .state <number>
1945
1952
  // console.log('### stateChange reason:', emitEventAndMessage )
1946
1953
  let eventName = state.toLowerCase();
1947
- this.state = STATE[state.toUpperCase()]; // state: number
1948
-
1949
- if (emitEventAndMessage) {
1950
- this.emit(eventName, emitEventAndMessage);
1951
- }
1952
-
1953
- if (this.stateName !== eventName) {
1954
- this.stateName = eventName;
1954
+ const nextState = STATE[state.toUpperCase()];
1955
+ if (typeof nextState !== 'number') throw new TypeError('Unknown state: ' + state);
1956
+ const changed = this.state !== nextState;
1957
+ this.state = nextState;
1958
+ this.stateName = eventName;
1959
+ if (changed) this._stateRevision++;
1960
+ const revision = this._stateRevision;
1961
+ if (changed) {
1955
1962
  this.emit('change', eventName);
1956
1963
  }
1964
+ if (revision !== this._stateRevision) return false;
1965
+ if (emitEventAndMessage) this.emit(eventName, emitEventAndMessage);
1966
+ return revision === this._stateRevision;
1957
1967
  }
1958
1968
 
1959
1969
  }
@@ -2114,6 +2124,7 @@ class IOCongSocket extends IOCore {
2114
2124
  this.congRx = null;
2115
2125
  }
2116
2126
  this.socket.removeAllListeners();
2127
+ this.socket.on('error', () => {});
2117
2128
  if (!this.socket.destroyed) {
2118
2129
  this.socket.destroy(); // destroy() is sufficient for forceful closing
2119
2130
  }
@@ -2138,23 +2149,29 @@ class IOCongSocket extends IOCore {
2138
2149
  if (urlObj.protocol != "cong:") {
2139
2150
  urlObj = new URL('cong://' + url);
2140
2151
  }
2141
- this.socket = net.createConnection(urlObj.port, urlObj.hostname);
2142
- this.stateChange('connecting','connecting');
2152
+ const socket = this.socket = net.createConnection(urlObj.port, urlObj.hostname);
2153
+ this._closed = false;
2143
2154
 
2144
2155
  this.socket.on('connect', () => {
2156
+ if (this.socket !== socket) return;
2145
2157
  this.congRx = new CongRx();
2146
2158
  this.socket.pipe(this.congRx);
2147
- this.congRx.on("data", this.onTCPSocketMessage.bind(this));
2159
+ this.congRx.on('data', data => {
2160
+ if (this.socket === socket) this.onTCPSocketMessage(data);
2161
+ });
2148
2162
  this.emit('open');
2149
2163
  });
2150
2164
 
2151
2165
  this.socket.on('error', e => {
2166
+ if (this.socket !== socket) return;
2152
2167
  this.emit('error', e);
2153
2168
  });
2154
2169
 
2155
2170
  this.socket.on('close', () => {
2171
+ if (this.socket !== socket) return;
2156
2172
  this.emit('close');
2157
2173
  });
2174
+ this.stateChange('connecting','connecting');
2158
2175
 
2159
2176
  }
2160
2177
 
@@ -7244,6 +7261,8 @@ class IOWS extends IOCore {
7244
7261
  close() {
7245
7262
  if (this.socket) {
7246
7263
  this.socket.removeAllListeners();
7264
+ // Closing a connecting ws can emit an asynchronous error after detachment.
7265
+ this.socket.on('error', () => {});
7247
7266
  if (this.socket.readyState !== WebSocket.CLOSED) {
7248
7267
  this.socket.close();
7249
7268
  }
@@ -7263,21 +7282,27 @@ class IOWS extends IOCore {
7263
7282
 
7264
7283
  createConnection(url) {
7265
7284
  // node WebSocket
7266
- this.socket = new WebSocket(url);
7267
- this.stateChange('connecting','connecting');
7285
+ const socket = this.socket = new WebSocket(url);
7286
+ this._closed = false;
7268
7287
 
7269
7288
  this.socket.onopen = () => {
7270
- this.socket.on('message', this.onWebSocketMessage.bind(this));
7289
+ if (this.socket !== socket) return;
7290
+ socket.on('message', data => {
7291
+ if (this.socket === socket) this.onWebSocketMessage(data);
7292
+ });
7271
7293
  this.emit('open');
7272
7294
  };
7273
7295
 
7274
7296
  this.socket.onerror = (e) => {
7297
+ if (this.socket !== socket) return;
7275
7298
  this.emit('error', e);
7276
7299
  };
7277
7300
 
7278
7301
  this.socket.onclose = () => {
7302
+ if (this.socket !== socket) return;
7279
7303
  this.emit('close');
7280
7304
  };
7305
+ this.stateChange('connecting','connecting');
7281
7306
  }
7282
7307
 
7283
7308
  onWebSocketMessage(data) {
@@ -7396,7 +7421,9 @@ class RemoteCore {
7396
7421
  static ssid = 1;
7397
7422
 
7398
7423
  setState(state) {
7424
+ const previous = this.state;
7399
7425
  this.state = state;
7426
+ if (previous !== state) this.manager.server?.emit('remote:state', this, previous);
7400
7427
  if (serverOption.debug.showAuthInfo) {
7401
7428
  this.stateLog.push( state + ":" + STATE[ state] );
7402
7429
  if( state == STATE.AUTH_RES){
@@ -7465,6 +7492,8 @@ class RemoteCore {
7465
7492
  // CongSocket or WebSocket
7466
7493
  onSocketMessage(message, isBinary = true) {
7467
7494
 
7495
+ if (this._closing || this.state === STATE.CLOSED) return;
7496
+
7468
7497
  this.receiveMonitor(); // rx-data, ping/pong and timeout check
7469
7498
  if (!this.rxQuotaChecker(message)) return
7470
7499
 
@@ -7518,10 +7547,11 @@ class RemoteCore {
7518
7547
  break;
7519
7548
 
7520
7549
  case IOMsg.CID_REQ:
7521
- if (this.state < STATE.SERVER_READY) {
7550
+ if (![STATE.SERVER_READY, STATE.AUTH_RES, STATE.AUTH_FAIL, STATE.CID_RES].includes(this.state)) {
7522
7551
  // Protocol violation: CID_REQ was sent before receiving the SERVER_READY signal.
7523
7552
  console.log('CID_REQ before SERVER_READY');
7524
7553
  this.close();
7554
+ return;
7525
7555
  }
7526
7556
 
7527
7557
  if (!this.cid) {
@@ -7641,9 +7671,12 @@ class RemoteCore {
7641
7671
  // client's auth requst
7642
7672
  case it.BohoMsg.AUTH_REQ:
7643
7673
  if (!this.manager.authManager) return
7644
- if (this.state < STATE.SERVER_READY) {
7674
+ // Do not start overlapping verification on the same connection.
7675
+ if (this.state === STATE.AUTH_REQ) return;
7676
+ if (![STATE.SERVER_READY, STATE.AUTH_RES, STATE.AUTH_FAIL, STATE.CID_RES].includes(this.state)) {
7645
7677
  console.log('protocol error. must called auth_req after server_ready');
7646
7678
  this.close();
7679
+ return;
7647
7680
  }
7648
7681
  this.setState(STATE.AUTH_REQ);
7649
7682
  //async
@@ -7992,6 +8025,7 @@ class Remote extends RemoteCore {
7992
8025
  }
7993
8026
 
7994
8027
  close(terminateNow = false) {
8028
+ this._closing = true;
7995
8029
  this.getTraffic();
7996
8030
  if (this.socketType === 'websocket') {
7997
8031
  if (terminateNow) this.socket.terminate();
@@ -8257,6 +8291,7 @@ class Manager {
8257
8291
  if (socket.isAlive === false) {
8258
8292
  console.log('## timeout. cid:', remo.cid);
8259
8293
  remo.close();
8294
+ return;
8260
8295
  }
8261
8296
 
8262
8297
  socket.txCounter++;
@@ -8296,6 +8331,9 @@ class Manager {
8296
8331
 
8297
8332
  removeRemote(remote) {
8298
8333
 
8334
+ remote._closing = true;
8335
+ remote.setState(STATE.CLOSED);
8336
+
8299
8337
  let remoteInfo = `- IP:${remote.ip} #${remote.ssid} cid:${remote.cid} ${remote?.socket.socketType === 'websocket' ? "WS" : "CS"} `;
8300
8338
  if (this.connectionLogger) this.connectionLogger.log(remoteInfo);
8301
8339
  this.deligateSignal(remote, '@state', 'close');
@@ -8905,6 +8943,7 @@ class BohoAuth {
8905
8943
  }
8906
8944
 
8907
8945
  send_auth_fail(peer, reason) {
8946
+ if (peer._closing || peer.state === STATE.CLOSED) return;
8908
8947
  peer.boho.isAuthorized = false;
8909
8948
  if (this.authLogger) {
8910
8949
  let peerInfo = `FAIL #${peer.ssid} reason:${reason} `;
@@ -8914,7 +8953,9 @@ class BohoAuth {
8914
8953
  peer.setState(STATE.AUTH_FAIL);
8915
8954
  // add some delay time.
8916
8955
  setTimeout(e => {
8917
- peer.send(Buffer.from([it.BohoMsg.AUTH_FAIL]));
8956
+ if (!peer._closing && peer.state === STATE.AUTH_FAIL) {
8957
+ peer.send(Buffer.from([it.BohoMsg.AUTH_FAIL]));
8958
+ }
8918
8959
  }, serverOption.auth.delay_auth_fail);
8919
8960
  }
8920
8961
 
@@ -8937,6 +8978,7 @@ class BohoAuth {
8937
8978
 
8938
8979
  //2. get key of id from DB
8939
8980
  let authInfo = await this.keyProvider.getAuth(id);
8981
+ if (peer._closing || peer.state === STATE.CLOSED) return;
8940
8982
  if (!authInfo) {
8941
8983
  this.send_auth_fail(peer, 'NO ID:' + id);
8942
8984
  return
@@ -8969,14 +9011,10 @@ class BohoAuth {
8969
9011
  //4. get info
8970
9012
 
8971
9013
  //5. check duplicate login.
8972
- if (peer.manager.cid2remote.has(authInfo.cid)) {
9014
+ if (peer.manager.cid2remote.has(authInfo.cid) && peer.manager.cid2remote.get(authInfo.cid) !== peer) {
8973
9015
  let old = peer.manager.cid2remote.get(authInfo.cid);
8974
9016
  console.log('[WARN]DUPLICATE_LOGIN detected.', old.cid);
8975
9017
  old.ping(); // check the connection.
8976
- if (old == peer) {
8977
- console.log('## trying to RELOGIN after login.');
8978
- return
8979
- }
8980
9018
 
8981
9019
  let authClearSignal = $$1.pack(
8982
9020
  $$1.MB('#MsgType', '8', IOMsg.AUTH_CLEAR),
@@ -8989,6 +9027,7 @@ class BohoAuth {
8989
9027
  if( peer.socketType == 'websocket'){
8990
9028
  this.send_auth_fail(peer, 'duplicate login');
8991
9029
  }else {
9030
+ peer.setState(STATE.AUTH_FAIL);
8992
9031
  peer.close(); //arduino
8993
9032
  }
8994
9033
  } else {