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.
@@ -14,7 +14,7 @@ import fs, { readFileSync } from 'fs';
14
14
  import { memoryUsage } from 'process';
15
15
  import path from 'path';
16
16
 
17
- var version$1 = "6.1.0";
17
+ var version$1 = "6.1.2";
18
18
  var pkg = {
19
19
  version: version$1};
20
20
 
@@ -832,6 +832,7 @@ class IOCore extends EventEmitter {
832
832
  * @type {string}
833
833
  */
834
834
  this.stateName = this.getStateName(); // String type
835
+ this._stateRevision = 0;
835
836
 
836
837
  /**
837
838
  * Transmitted message counter.
@@ -968,14 +969,14 @@ class IOCore extends EventEmitter {
968
969
  * If autoReconnect is false, it also clears the keep-alive timer.
969
970
  */
970
971
  close() {
971
- if (this._closed) return;
972
- this._closed = true;
973
972
  // console.log('####### IOCOre.js close() called')
974
973
  // If auto-reconnect is disabled, we must stop the keep-alive timer.
975
974
  if (this.autoReconnect === false) {
976
975
  clearInterval(this.connectionCheckerIntervalID);
977
976
  this.connectionCheckerIntervalID = null;
978
977
  }
978
+ if (this._closed) return;
979
+ this._closed = true;
979
980
 
980
981
  // socket clean
981
982
  if (this.socket) {
@@ -998,8 +999,11 @@ class IOCore extends EventEmitter {
998
999
  this.socket = null;
999
1000
  }
1000
1001
  this.promiseMap.clear();
1001
- this.emit('closed');
1002
- this.stateChange('closed');
1002
+ this.cid = '';
1003
+ if (this.boho) this.boho.isAuthorized = false;
1004
+ if (this.state !== STATE.STOP && this.stateChange('closed')) {
1005
+ this.emit('closed');
1006
+ }
1003
1007
  }
1004
1008
 
1005
1009
  /**
@@ -1072,6 +1076,7 @@ class IOCore extends EventEmitter {
1072
1076
 
1073
1077
  // The actual connection is created here.
1074
1078
  this.createConnection(this.url);
1079
+ if (this._closed) return; // A connecting listener may have stopped the client.
1075
1080
 
1076
1081
  // Ensure the keep-alive timer is running.
1077
1082
  if (!this.connectionCheckerIntervalID) {
@@ -1096,9 +1101,7 @@ class IOCore extends EventEmitter {
1096
1101
  * Handles the 'close' event of the WebSocket.
1097
1102
  */
1098
1103
  onClose() {
1099
- this.boho.isAuthorized = false;
1100
- this.cid = "";
1101
- this.stateChange('closed');
1104
+ this.close();
1102
1105
  }
1103
1106
 
1104
1107
  /**
@@ -1109,11 +1112,11 @@ class IOCore extends EventEmitter {
1109
1112
  */
1110
1113
  login(id, key) {
1111
1114
  if( this.serverTimeNonce ){
1112
- console.log('iosignal.login serverTimeNonce', this.serverTimeNonce);
1113
1115
  this.auth(id, key);
1114
1116
  this.useAuth = true;
1115
1117
  let auth_pack = this.boho.auth_req(this.serverTimeNonce );
1116
1118
  this.send(auth_pack);
1119
+ this.stateChange('auth_req', 'auth_req');
1117
1120
  }
1118
1121
  return this
1119
1122
  }
@@ -1259,14 +1262,13 @@ class IOCore extends EventEmitter {
1259
1262
  break;
1260
1263
 
1261
1264
  case it.BohoMsg.SERVER_TIME_NONCE: // SERVER_READY
1262
- this.stateChange('server_ready', 'server_ready');
1265
+ buffer.copy(this.serverTimeNonce);
1266
+ if (!this.stateChange('server_ready', 'server_ready')) break;
1263
1267
  if (this.useAuth) {
1264
1268
  this.send(this.boho.auth_req(buffer));
1265
1269
  this.stateChange('auth_req','auth_req');
1266
1270
  // CID_REQ will be called, after auth_res.
1267
1271
  } else {
1268
- // keep server_time_nonce for manual login()
1269
- buffer.copy( this.serverTimeNonce);
1270
1272
  // CID_REQ here, if not using auth.
1271
1273
  this.send(Buffer$1.from([IOMsg.CID_REQ]));
1272
1274
  }
@@ -1391,14 +1393,16 @@ class IOCore extends EventEmitter {
1391
1393
  break;
1392
1394
 
1393
1395
  case it.BohoMsg.AUTH_FAIL:
1396
+ this.boho.isAuthorized = false;
1394
1397
  this.stateChange('auth_fail', 'auth_fail from server.');
1395
1398
  break;
1396
1399
 
1397
1400
  case it.BohoMsg.AUTH_RES:
1398
1401
  if (this.boho.verify_auth_res(buffer)) {
1399
- this.stateChange('auth_res', 'server sent auth_res');
1402
+ if (!this.stateChange('auth_res', 'server sent auth_res')) break;
1400
1403
  this.send(Buffer$1.from([IOMsg.CID_REQ]));
1401
1404
  } else {
1405
+ this.boho.isAuthorized = false;
1402
1406
  this.stateChange('auth_fail', 'verify_auth_res() invalid server_hmac');
1403
1407
  }
1404
1408
  break;
@@ -1935,6 +1939,9 @@ class IOCore extends EventEmitter {
1935
1939
  * 1. 상태가 변경 될 때만 'change' 이벤트 호출된다.
1936
1940
  * 2. emitEventAndMessage 옵션 값이 지정되야 해당 이벤트 이름이 호출된다.
1937
1941
  * 보통 이벤트 이름과 동일하게 적거나 이벤트 상황 안내문을 넣는다.
1942
+ * 3. 두 상태값 갱신 후 change, 개별 이벤트 순서로 호출한다.
1943
+ * 콜백에서 다른 상태로 전이하면 이전 상태의 개별 이벤트는 생략한다.
1944
+ * @returns {boolean} Whether this transition is still current after callbacks.
1938
1945
  */
1939
1946
  stateChange(state, emitEventAndMessage) {
1940
1947
  // STATE constant name <string> upperCase
@@ -1942,16 +1949,19 @@ class IOCore extends EventEmitter {
1942
1949
  // .state <number>
1943
1950
  // console.log('### stateChange reason:', emitEventAndMessage )
1944
1951
  let eventName = state.toLowerCase();
1945
- this.state = STATE[state.toUpperCase()]; // state: number
1946
-
1947
- if (emitEventAndMessage) {
1948
- this.emit(eventName, emitEventAndMessage);
1949
- }
1950
-
1951
- if (this.stateName !== eventName) {
1952
- this.stateName = eventName;
1952
+ const nextState = STATE[state.toUpperCase()];
1953
+ if (typeof nextState !== 'number') throw new TypeError('Unknown state: ' + state);
1954
+ const changed = this.state !== nextState;
1955
+ this.state = nextState;
1956
+ this.stateName = eventName;
1957
+ if (changed) this._stateRevision++;
1958
+ const revision = this._stateRevision;
1959
+ if (changed) {
1953
1960
  this.emit('change', eventName);
1954
1961
  }
1962
+ if (revision !== this._stateRevision) return false;
1963
+ if (emitEventAndMessage) this.emit(eventName, emitEventAndMessage);
1964
+ return revision === this._stateRevision;
1955
1965
  }
1956
1966
 
1957
1967
  }
@@ -2112,6 +2122,7 @@ class IOCongSocket extends IOCore {
2112
2122
  this.congRx = null;
2113
2123
  }
2114
2124
  this.socket.removeAllListeners();
2125
+ this.socket.on('error', () => {});
2115
2126
  if (!this.socket.destroyed) {
2116
2127
  this.socket.destroy(); // destroy() is sufficient for forceful closing
2117
2128
  }
@@ -2136,23 +2147,29 @@ class IOCongSocket extends IOCore {
2136
2147
  if (urlObj.protocol != "cong:") {
2137
2148
  urlObj = new URL('cong://' + url);
2138
2149
  }
2139
- this.socket = net.createConnection(urlObj.port, urlObj.hostname);
2140
- this.stateChange('connecting','connecting');
2150
+ const socket = this.socket = net.createConnection(urlObj.port, urlObj.hostname);
2151
+ this._closed = false;
2141
2152
 
2142
2153
  this.socket.on('connect', () => {
2154
+ if (this.socket !== socket) return;
2143
2155
  this.congRx = new CongRx();
2144
2156
  this.socket.pipe(this.congRx);
2145
- this.congRx.on("data", this.onTCPSocketMessage.bind(this));
2157
+ this.congRx.on('data', data => {
2158
+ if (this.socket === socket) this.onTCPSocketMessage(data);
2159
+ });
2146
2160
  this.emit('open');
2147
2161
  });
2148
2162
 
2149
2163
  this.socket.on('error', e => {
2164
+ if (this.socket !== socket) return;
2150
2165
  this.emit('error', e);
2151
2166
  });
2152
2167
 
2153
2168
  this.socket.on('close', () => {
2169
+ if (this.socket !== socket) return;
2154
2170
  this.emit('close');
2155
2171
  });
2172
+ this.stateChange('connecting','connecting');
2156
2173
 
2157
2174
  }
2158
2175
 
@@ -7242,6 +7259,8 @@ class IOWS extends IOCore {
7242
7259
  close() {
7243
7260
  if (this.socket) {
7244
7261
  this.socket.removeAllListeners();
7262
+ // Closing a connecting ws can emit an asynchronous error after detachment.
7263
+ this.socket.on('error', () => {});
7245
7264
  if (this.socket.readyState !== WebSocket.CLOSED) {
7246
7265
  this.socket.close();
7247
7266
  }
@@ -7261,21 +7280,27 @@ class IOWS extends IOCore {
7261
7280
 
7262
7281
  createConnection(url) {
7263
7282
  // node WebSocket
7264
- this.socket = new WebSocket(url);
7265
- this.stateChange('connecting','connecting');
7283
+ const socket = this.socket = new WebSocket(url);
7284
+ this._closed = false;
7266
7285
 
7267
7286
  this.socket.onopen = () => {
7268
- this.socket.on('message', this.onWebSocketMessage.bind(this));
7287
+ if (this.socket !== socket) return;
7288
+ socket.on('message', data => {
7289
+ if (this.socket === socket) this.onWebSocketMessage(data);
7290
+ });
7269
7291
  this.emit('open');
7270
7292
  };
7271
7293
 
7272
7294
  this.socket.onerror = (e) => {
7295
+ if (this.socket !== socket) return;
7273
7296
  this.emit('error', e);
7274
7297
  };
7275
7298
 
7276
7299
  this.socket.onclose = () => {
7300
+ if (this.socket !== socket) return;
7277
7301
  this.emit('close');
7278
7302
  };
7303
+ this.stateChange('connecting','connecting');
7279
7304
  }
7280
7305
 
7281
7306
  onWebSocketMessage(data) {
@@ -7394,7 +7419,9 @@ class RemoteCore {
7394
7419
  static ssid = 1;
7395
7420
 
7396
7421
  setState(state) {
7422
+ const previous = this.state;
7397
7423
  this.state = state;
7424
+ if (previous !== state) this.manager.server?.emit('remote:state', this, previous);
7398
7425
  if (serverOption.debug.showAuthInfo) {
7399
7426
  this.stateLog.push( state + ":" + STATE[ state] );
7400
7427
  if( state == STATE.AUTH_RES){
@@ -7463,6 +7490,8 @@ class RemoteCore {
7463
7490
  // CongSocket or WebSocket
7464
7491
  onSocketMessage(message, isBinary = true) {
7465
7492
 
7493
+ if (this._closing || this.state === STATE.CLOSED) return;
7494
+
7466
7495
  this.receiveMonitor(); // rx-data, ping/pong and timeout check
7467
7496
  if (!this.rxQuotaChecker(message)) return
7468
7497
 
@@ -7516,10 +7545,11 @@ class RemoteCore {
7516
7545
  break;
7517
7546
 
7518
7547
  case IOMsg.CID_REQ:
7519
- if (this.state < STATE.SERVER_READY) {
7548
+ if (![STATE.SERVER_READY, STATE.AUTH_RES, STATE.AUTH_FAIL, STATE.CID_RES].includes(this.state)) {
7520
7549
  // Protocol violation: CID_REQ was sent before receiving the SERVER_READY signal.
7521
7550
  console.log('CID_REQ before SERVER_READY');
7522
7551
  this.close();
7552
+ return;
7523
7553
  }
7524
7554
 
7525
7555
  if (!this.cid) {
@@ -7639,9 +7669,12 @@ class RemoteCore {
7639
7669
  // client's auth requst
7640
7670
  case it.BohoMsg.AUTH_REQ:
7641
7671
  if (!this.manager.authManager) return
7642
- if (this.state < STATE.SERVER_READY) {
7672
+ // Do not start overlapping verification on the same connection.
7673
+ if (this.state === STATE.AUTH_REQ) return;
7674
+ if (![STATE.SERVER_READY, STATE.AUTH_RES, STATE.AUTH_FAIL, STATE.CID_RES].includes(this.state)) {
7643
7675
  console.log('protocol error. must called auth_req after server_ready');
7644
7676
  this.close();
7677
+ return;
7645
7678
  }
7646
7679
  this.setState(STATE.AUTH_REQ);
7647
7680
  //async
@@ -7990,6 +8023,7 @@ class Remote extends RemoteCore {
7990
8023
  }
7991
8024
 
7992
8025
  close(terminateNow = false) {
8026
+ this._closing = true;
7993
8027
  this.getTraffic();
7994
8028
  if (this.socketType === 'websocket') {
7995
8029
  if (terminateNow) this.socket.terminate();
@@ -8255,6 +8289,7 @@ class Manager {
8255
8289
  if (socket.isAlive === false) {
8256
8290
  console.log('## timeout. cid:', remo.cid);
8257
8291
  remo.close();
8292
+ return;
8258
8293
  }
8259
8294
 
8260
8295
  socket.txCounter++;
@@ -8294,6 +8329,9 @@ class Manager {
8294
8329
 
8295
8330
  removeRemote(remote) {
8296
8331
 
8332
+ remote._closing = true;
8333
+ remote.setState(STATE.CLOSED);
8334
+
8297
8335
  let remoteInfo = `- IP:${remote.ip} #${remote.ssid} cid:${remote.cid} ${remote?.socket.socketType === 'websocket' ? "WS" : "CS"} `;
8298
8336
  if (this.connectionLogger) this.connectionLogger.log(remoteInfo);
8299
8337
  this.deligateSignal(remote, '@state', 'close');
@@ -8903,6 +8941,7 @@ class BohoAuth {
8903
8941
  }
8904
8942
 
8905
8943
  send_auth_fail(peer, reason) {
8944
+ if (peer._closing || peer.state === STATE.CLOSED) return;
8906
8945
  peer.boho.isAuthorized = false;
8907
8946
  if (this.authLogger) {
8908
8947
  let peerInfo = `FAIL #${peer.ssid} reason:${reason} `;
@@ -8912,7 +8951,9 @@ class BohoAuth {
8912
8951
  peer.setState(STATE.AUTH_FAIL);
8913
8952
  // add some delay time.
8914
8953
  setTimeout(e => {
8915
- peer.send(Buffer.from([it.BohoMsg.AUTH_FAIL]));
8954
+ if (!peer._closing && peer.state === STATE.AUTH_FAIL) {
8955
+ peer.send(Buffer.from([it.BohoMsg.AUTH_FAIL]));
8956
+ }
8916
8957
  }, serverOption.auth.delay_auth_fail);
8917
8958
  }
8918
8959
 
@@ -8935,6 +8976,7 @@ class BohoAuth {
8935
8976
 
8936
8977
  //2. get key of id from DB
8937
8978
  let authInfo = await this.keyProvider.getAuth(id);
8979
+ if (peer._closing || peer.state === STATE.CLOSED) return;
8938
8980
  if (!authInfo) {
8939
8981
  this.send_auth_fail(peer, 'NO ID:' + id);
8940
8982
  return
@@ -8967,14 +9009,10 @@ class BohoAuth {
8967
9009
  //4. get info
8968
9010
 
8969
9011
  //5. check duplicate login.
8970
- if (peer.manager.cid2remote.has(authInfo.cid)) {
9012
+ if (peer.manager.cid2remote.has(authInfo.cid) && peer.manager.cid2remote.get(authInfo.cid) !== peer) {
8971
9013
  let old = peer.manager.cid2remote.get(authInfo.cid);
8972
9014
  console.log('[WARN]DUPLICATE_LOGIN detected.', old.cid);
8973
9015
  old.ping(); // check the connection.
8974
- if (old == peer) {
8975
- console.log('## trying to RELOGIN after login.');
8976
- return
8977
- }
8978
9016
 
8979
9017
  let authClearSignal = $$1.pack(
8980
9018
  $$1.MB('#MsgType', '8', IOMsg.AUTH_CLEAR),
@@ -8987,6 +9025,7 @@ class BohoAuth {
8987
9025
  if( peer.socketType == 'websocket'){
8988
9026
  this.send_auth_fail(peer, 'duplicate login');
8989
9027
  }else {
9028
+ peer.setState(STATE.AUTH_FAIL);
8990
9029
  peer.close(); //arduino
8991
9030
  }
8992
9031
  } else {
@@ -37,6 +37,7 @@ export class IOCore {
37
37
  * @type {string}
38
38
  */
39
39
  stateName: string;
40
+ _stateRevision: number;
40
41
  /**
41
42
  * Transmitted message counter.
42
43
  * @type {number}
@@ -404,8 +405,11 @@ export class IOCore {
404
405
  * 1. 상태가 변경 될 때만 'change' 이벤트 호출된다.
405
406
  * 2. emitEventAndMessage 옵션 값이 지정되야 해당 이벤트 이름이 호출된다.
406
407
  * 보통 이벤트 이름과 동일하게 적거나 이벤트 상황 안내문을 넣는다.
408
+ * 3. 두 상태값 갱신 후 change, 개별 이벤트 순서로 호출한다.
409
+ * 콜백에서 다른 상태로 전이하면 이전 상태의 개별 이벤트는 생략한다.
410
+ * @returns {boolean} Whether this transition is still current after callbacks.
407
411
  */
408
- stateChange(state: string, emitEventAndMessage?: string): void;
412
+ stateChange(state: string, emitEventAndMessage?: string): boolean;
409
413
  }
410
414
  export type MBP = import("meta-buffer-pack").MBP;
411
415
  export type IOMsg = import("../common/constants.js").IOMsg;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iosignal",
3
- "version": "6.1.0",
3
+ "version": "6.1.2",
4
4
  "description": "iosignal",
5
5
  "type": "module",
6
6
  "exports": {