iosignal 5.3.0 → 6.1.1

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.
@@ -4,19 +4,19 @@ var require$$0 = require('stream');
4
4
  var net = require('net');
5
5
  var require$$0$3 = require('events');
6
6
  var require$$1$1 = require('https');
7
- var require$$2$2 = require('http');
7
+ var require$$2$1 = require('http');
8
8
  var require$$4 = require('tls');
9
9
  var require$$1 = require('crypto');
10
10
  var require$$7 = require('url');
11
11
  var require$$0$1 = require('zlib');
12
- var fs = require('fs');
13
- var path = require('path');
14
- var require$$2 = require('os');
15
12
  var require$$0$2 = require('buffer');
16
- var require$$2$1 = require('util');
13
+ var require$$2 = require('util');
14
+ var os = require('os');
15
+ var fs = require('fs');
17
16
  var process$1 = require('process');
17
+ var path = require('path');
18
18
 
19
- var version$1 = "5.3.0";
19
+ var version$1 = "6.1.1";
20
20
  var pkg = {
21
21
  version: version$1};
22
22
 
@@ -667,6 +667,7 @@ const encoder$2 = new TextEncoder();
667
667
  function getSignalPack(tag, ...args) {
668
668
  if (typeof tag !== 'string') throw TypeError('tag should be string.')
669
669
  let tagEncoded = encoder$2.encode(tag);
670
+ if (tagEncoded.byteLength > SIZE_LIMIT.TAG_LEN1) throw TypeError('tag UTF-8 byte length exceeds 255')
670
671
  let payload = parsePayload(args);
671
672
 
672
673
  let sigPack;
@@ -774,6 +775,16 @@ const Buffer$1 = $$1.Buffer;
774
775
  const encoder$1 = new TextEncoder();
775
776
  const decoder$4 = new TextDecoder();
776
777
 
778
+ // Wire tag lengths use one byte, regardless of JavaScript string length.
779
+ function encodeTag(tag, allowEmpty = true) {
780
+ if (typeof tag !== 'string') throw TypeError('tag should be string.')
781
+ const encoded = encoder$1.encode(tag);
782
+ if ((!allowEmpty && encoded.byteLength === 0) || encoded.byteLength > SIZE_LIMIT.TAG_LEN1) {
783
+ throw TypeError('tag UTF-8 byte length must be ' + (allowEmpty ? '0' : '1') + '~255')
784
+ }
785
+ return encoded
786
+ }
787
+
777
788
  function byteToUrl(buffer) {
778
789
  //ipv4(4bytes) , port(2bytes)
779
790
  if (buffer.byteLength != 6) return
@@ -823,6 +834,7 @@ class IOCore extends EventEmitter {
823
834
  * @type {string}
824
835
  */
825
836
  this.stateName = this.getStateName(); // String type
837
+ this._stateRevision = 0;
826
838
 
827
839
  /**
828
840
  * Transmitted message counter.
@@ -959,14 +971,14 @@ class IOCore extends EventEmitter {
959
971
  * If autoReconnect is false, it also clears the keep-alive timer.
960
972
  */
961
973
  close() {
962
- if (this._closed) return;
963
- this._closed = true;
964
974
  // console.log('####### IOCOre.js close() called')
965
975
  // If auto-reconnect is disabled, we must stop the keep-alive timer.
966
976
  if (this.autoReconnect === false) {
967
977
  clearInterval(this.connectionCheckerIntervalID);
968
978
  this.connectionCheckerIntervalID = null;
969
979
  }
980
+ if (this._closed) return;
981
+ this._closed = true;
970
982
 
971
983
  // socket clean
972
984
  if (this.socket) {
@@ -989,8 +1001,11 @@ class IOCore extends EventEmitter {
989
1001
  this.socket = null;
990
1002
  }
991
1003
  this.promiseMap.clear();
992
- this.emit('closed');
993
- 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
+ }
994
1009
  }
995
1010
 
996
1011
  /**
@@ -1063,6 +1078,7 @@ class IOCore extends EventEmitter {
1063
1078
 
1064
1079
  // The actual connection is created here.
1065
1080
  this.createConnection(this.url);
1081
+ if (this._closed) return; // A connecting listener may have stopped the client.
1066
1082
 
1067
1083
  // Ensure the keep-alive timer is running.
1068
1084
  if (!this.connectionCheckerIntervalID) {
@@ -1087,9 +1103,7 @@ class IOCore extends EventEmitter {
1087
1103
  * Handles the 'close' event of the WebSocket.
1088
1104
  */
1089
1105
  onClose() {
1090
- this.boho.isAuthorized = false;
1091
- this.cid = "";
1092
- this.stateChange('closed');
1106
+ this.close();
1093
1107
  }
1094
1108
 
1095
1109
  /**
@@ -1100,11 +1114,11 @@ class IOCore extends EventEmitter {
1100
1114
  */
1101
1115
  login(id, key) {
1102
1116
  if( this.serverTimeNonce ){
1103
- console.log('iosignal.login serverTimeNonce', this.serverTimeNonce);
1104
1117
  this.auth(id, key);
1105
1118
  this.useAuth = true;
1106
1119
  let auth_pack = this.boho.auth_req(this.serverTimeNonce );
1107
1120
  this.send(auth_pack);
1121
+ this.stateChange('auth_req', 'auth_req');
1108
1122
  }
1109
1123
  return this
1110
1124
  }
@@ -1250,14 +1264,13 @@ class IOCore extends EventEmitter {
1250
1264
  break;
1251
1265
 
1252
1266
  case it.BohoMsg.SERVER_TIME_NONCE: // SERVER_READY
1253
- this.stateChange('server_ready', 'server_ready');
1267
+ buffer.copy(this.serverTimeNonce);
1268
+ if (!this.stateChange('server_ready', 'server_ready')) break;
1254
1269
  if (this.useAuth) {
1255
1270
  this.send(this.boho.auth_req(buffer));
1256
1271
  this.stateChange('auth_req','auth_req');
1257
1272
  // CID_REQ will be called, after auth_res.
1258
1273
  } else {
1259
- // keep server_time_nonce for manual login()
1260
- buffer.copy( this.serverTimeNonce);
1261
1274
  // CID_REQ here, if not using auth.
1262
1275
  this.send(Buffer$1.from([IOMsg.CID_REQ]));
1263
1276
  }
@@ -1308,10 +1321,10 @@ class IOCore extends EventEmitter {
1308
1321
  switch (payloadType) {
1309
1322
 
1310
1323
  case PAYLOAD_TYPE.EMPTY:
1311
- if (tag.indexOf('@') === 0) this.emit('@', tag, null);
1324
+ if (tag.indexOf('@') === 0) this.emit('@', tag);
1312
1325
  else {
1313
- this.emit(tag, tag, null);
1314
- this.emit('message', tag, null);
1326
+ this.emit(tag, tag);
1327
+ this.emit('message', tag);
1315
1328
  }
1316
1329
  break;
1317
1330
 
@@ -1382,14 +1395,16 @@ class IOCore extends EventEmitter {
1382
1395
  break;
1383
1396
 
1384
1397
  case it.BohoMsg.AUTH_FAIL:
1398
+ this.boho.isAuthorized = false;
1385
1399
  this.stateChange('auth_fail', 'auth_fail from server.');
1386
1400
  break;
1387
1401
 
1388
1402
  case it.BohoMsg.AUTH_RES:
1389
1403
  if (this.boho.verify_auth_res(buffer)) {
1390
- this.stateChange('auth_res', 'server sent auth_res');
1404
+ if (!this.stateChange('auth_res', 'server sent auth_res')) break;
1391
1405
  this.send(Buffer$1.from([IOMsg.CID_REQ]));
1392
1406
  } else {
1407
+ this.boho.isAuthorized = false;
1393
1408
  this.stateChange('auth_fail', 'verify_auth_res() invalid server_hmac');
1394
1409
  }
1395
1410
  break;
@@ -1595,6 +1610,7 @@ class IOCore extends EventEmitter {
1595
1610
  */
1596
1611
  signal(tag, ...args) {
1597
1612
  if (typeof tag !== 'string') throw TypeError('tag should be string.')
1613
+ if (tag.startsWith('@') && this.cid) encodeTag(this.cid + tag);
1598
1614
  let signalPack = getSignalPack(tag, ...args);
1599
1615
  this.send_enc_mode(signalPack);
1600
1616
  }
@@ -1618,9 +1634,9 @@ class IOCore extends EventEmitter {
1618
1634
  */
1619
1635
  signal_e2e(tag, data, key) {
1620
1636
 
1637
+ const tagEncoded = encodeTag(tag);
1638
+ if (tag.startsWith('@') && this.cid) encodeTag(this.cid + tag);
1621
1639
  if( !this.boho.isAuthorized ) return;
1622
- if (typeof tag !== 'string') throw TypeError('tag should be string.')
1623
- let tagEncoded = encoder$1.encode(tag);
1624
1640
  let dataPack = $$1.B8(data);
1625
1641
 
1626
1642
  //encrypt payload area with key
@@ -1705,39 +1721,37 @@ class IOCore extends EventEmitter {
1705
1721
  * @throws {TypeError} If tag is not a string or exceeds length limit.
1706
1722
  */
1707
1723
  subscribe(tag) {
1708
- if (typeof tag !== 'string') throw TypeError('tag should be string.')
1709
- if (tag.length > SIZE_LIMIT.TAG_LEN1) throw TypeError('please check tag string length limit:' + SIZE_LIMIT.TAG_LEN1)
1710
-
1711
- try {
1712
- let tagEncoded = encoder$1.encode(tag);
1713
- this.send_enc_mode(
1714
- Buffer$1.concat([
1715
- $$1.NB('8', IOMsg.SUBSCRIBE),
1716
- $$1.NB('8', tagEncoded.byteLength),
1717
- tagEncoded]));
1718
- } catch (error) { }
1719
-
1724
+ const tagEncoded = encodeTag(tag);
1725
+ this.send_enc_mode(Buffer$1.concat([
1726
+ $$1.NB('8', IOMsg.SUBSCRIBE),
1727
+ $$1.NB('8', tagEncoded.byteLength),
1728
+ tagEncoded]));
1720
1729
  }
1721
1730
 
1722
1731
 
1723
1732
  /**
1724
- * Subscribes stored channels.
1725
- * called client state become 'ready'
1733
+ * Sends subscriptions stored by listen()/link() on each CID-ready transition,
1734
+ * including reconnection. This automates subscription setup for simple clients.
1726
1735
  */
1727
1736
  subscribe_channels() {
1728
- if (this.state !== STATE.READY) return
1729
- if (this.channels.size == 0) return
1730
- let tag = Array.from(this.channels).join(',');
1731
-
1732
- try {
1733
- let tagEncoded = encoder$1.encode(tag);
1734
- this.send_enc_mode(
1735
- Buffer$1.concat([
1736
- $$1.NB('8', IOMsg.SUBSCRIBE),
1737
- $$1.NB('8', tagEncoded.byteLength),
1738
- tagEncoded]));
1739
- } catch (error) { }
1740
-
1737
+ if (this.state !== STATE.READY || this.channels.size === 0) return
1738
+ // Validate the entire list first, then split only at tag boundaries.
1739
+ // Preserve comma-separated entries accepted by existing registration APIs.
1740
+ const tags = Array.from(this.channels).join(',').split(',');
1741
+ const sizes = tags.map(tag => encodeTag(tag).byteLength);
1742
+ const batches = [];
1743
+ let batch = [], bytes = 0;
1744
+ tags.forEach((tag, i) => {
1745
+ if (batch.length && bytes + 1 + sizes[i] > SIZE_LIMIT.TAG_LEN1) {
1746
+ batches.push(batch.join(','));
1747
+ batch = [];
1748
+ bytes = 0;
1749
+ }
1750
+ bytes += (batch.length ? 1 : 0) + sizes[i];
1751
+ batch.push(tag);
1752
+ });
1753
+ if (batch.length) batches.push(batch.join(','));
1754
+ batches.forEach(tag => this.subscribe(tag));
1741
1755
  }
1742
1756
 
1743
1757
  /**
@@ -1746,7 +1760,7 @@ class IOCore extends EventEmitter {
1746
1760
  * @throws {TypeError} If tag is not a string or exceeds length limit.
1747
1761
  */
1748
1762
  unsubscribe(tag = "") {
1749
- if (typeof tag !== 'string') throw TypeError('tag should be string.')
1763
+ const tagEncoded = encodeTag(tag);
1750
1764
 
1751
1765
  if (tag == "") { // blank tag means unsubscribe all
1752
1766
  this.channels.clear();
@@ -1757,8 +1771,6 @@ class IOCore extends EventEmitter {
1757
1771
  });
1758
1772
  }
1759
1773
 
1760
- let tagEncoded = encoder$1.encode(tag);
1761
- if (tagEncoded.byteLength > SIZE_LIMIT.TAG_LEN1) throw TypeError('please use tag string bytelength below:' + SIZE_LIMIT.TAG_LEN1)
1762
1774
 
1763
1775
  this.send_enc_mode(Buffer$1.concat([
1764
1776
  $$1.NB('8', IOMsg.UNSUBSCRIBE),
@@ -1768,22 +1780,28 @@ class IOCore extends EventEmitter {
1768
1780
 
1769
1781
 
1770
1782
  /**
1771
- * Listens for signals on a specific tag.
1783
+ * Convenience API for simple clients (for example, CLI tools): register a
1784
+ * tag handler once and remember its subscription in channels. Register before
1785
+ * connection readiness; the CID-ready flow subscribes on initial connection
1786
+ * and again after reconnect, without application-level ready/subscribe code.
1787
+ * This does not send a subscription immediately, even if already ready.
1788
+ * For precise subscription/send ordering or dynamic subscriptions, use
1789
+ * on() with subscribe() in a ready handler instead. Direct signals use on('@').
1772
1790
  * @param {string} tag - The tag to listen on.
1773
1791
  * @param {Function} handler - The callback function to handle the signal.
1774
1792
  * @throws {TypeError} If tag is not a string, handler is not a function, or tag length is invalid.
1775
1793
  */
1776
1794
  listen(tag, handler) {
1777
1795
  if (typeof tag !== 'string') throw TypeError('tag should be string.')
1778
- if (tag.length > 255 || tag.length == 0) throw TypeError('tag string length range: 1~255')
1796
+ encodeTag(tag, false);
1779
1797
  if (typeof handler !== 'function') throw TypeError('handler is not a function.')
1780
1798
 
1781
1799
  if (tag.indexOf('@') !== 0) {
1782
1800
  this.channels.add(tag);
1783
1801
  }
1784
1802
  this.on(tag, handler);
1785
- // do not subscribe now.
1786
- // will subscribe when io state is 'ready'. (receive CID_RES from server)
1803
+ // Subscribe from the CID-ready flow, initially and after reconnect.
1804
+ // Keep registration separate from immediate subscription/send ordering.
1787
1805
 
1788
1806
  }
1789
1807
 
@@ -1799,7 +1817,7 @@ class IOCore extends EventEmitter {
1799
1817
  link(to, tag, handler) {
1800
1818
  if (typeof to !== 'string') throw TypeError('to(local link target) is not a string.')
1801
1819
  if (typeof tag !== 'string') throw TypeError('tag is not a string.')
1802
- if (tag.length > 255 || tag.length == 0) throw TypeError('tag string length range: 1~255')
1820
+ encodeTag(tag, false);
1803
1821
  if (typeof handler !== 'function') throw TypeError('handler is not a function.')
1804
1822
 
1805
1823
  if (tag.indexOf('@') !== 0) {
@@ -1830,7 +1848,7 @@ class IOCore extends EventEmitter {
1830
1848
  unlink(to, tag) {
1831
1849
  if (typeof to !== 'string') throw TypeError('to(local link target) is not a string.')
1832
1850
  if (typeof tag !== 'string') throw TypeError('tag is not a string.')
1833
- if (tag.length > 255 || tag.length == 0) throw TypeError('tag string length range: 1~255')
1851
+ encodeTag(tag, false);
1834
1852
 
1835
1853
  const linkSet = this.linkMap.get(to);
1836
1854
  if (!linkSet || !linkSet.has(tag)) return;
@@ -1923,6 +1941,9 @@ class IOCore extends EventEmitter {
1923
1941
  * 1. 상태가 변경 될 때만 'change' 이벤트 호출된다.
1924
1942
  * 2. emitEventAndMessage 옵션 값이 지정되야 해당 이벤트 이름이 호출된다.
1925
1943
  * 보통 이벤트 이름과 동일하게 적거나 이벤트 상황 안내문을 넣는다.
1944
+ * 3. 두 상태값 갱신 후 change, 개별 이벤트 순서로 호출한다.
1945
+ * 콜백에서 다른 상태로 전이하면 이전 상태의 개별 이벤트는 생략한다.
1946
+ * @returns {boolean} Whether this transition is still current after callbacks.
1926
1947
  */
1927
1948
  stateChange(state, emitEventAndMessage) {
1928
1949
  // STATE constant name <string> upperCase
@@ -1930,16 +1951,19 @@ class IOCore extends EventEmitter {
1930
1951
  // .state <number>
1931
1952
  // console.log('### stateChange reason:', emitEventAndMessage )
1932
1953
  let eventName = state.toLowerCase();
1933
- this.state = STATE[state.toUpperCase()]; // state: number
1934
-
1935
- if (emitEventAndMessage) {
1936
- this.emit(eventName, emitEventAndMessage);
1937
- }
1938
-
1939
- if (this.stateName !== eventName) {
1940
- 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) {
1941
1962
  this.emit('change', eventName);
1942
1963
  }
1964
+ if (revision !== this._stateRevision) return false;
1965
+ if (emitEventAndMessage) this.emit(eventName, emitEventAndMessage);
1966
+ return revision === this._stateRevision;
1943
1967
  }
1944
1968
 
1945
1969
  }
@@ -2100,6 +2124,7 @@ class IOCongSocket extends IOCore {
2100
2124
  this.congRx = null;
2101
2125
  }
2102
2126
  this.socket.removeAllListeners();
2127
+ this.socket.on('error', () => {});
2103
2128
  if (!this.socket.destroyed) {
2104
2129
  this.socket.destroy(); // destroy() is sufficient for forceful closing
2105
2130
  }
@@ -2124,23 +2149,29 @@ class IOCongSocket extends IOCore {
2124
2149
  if (urlObj.protocol != "cong:") {
2125
2150
  urlObj = new URL('cong://' + url);
2126
2151
  }
2127
- this.socket = net.createConnection(urlObj.port, urlObj.hostname);
2128
- this.stateChange('connecting','connecting');
2152
+ const socket = this.socket = net.createConnection(urlObj.port, urlObj.hostname);
2153
+ this._closed = false;
2129
2154
 
2130
2155
  this.socket.on('connect', () => {
2156
+ if (this.socket !== socket) return;
2131
2157
  this.congRx = new CongRx();
2132
2158
  this.socket.pipe(this.congRx);
2133
- this.congRx.on("data", this.onTCPSocketMessage.bind(this));
2159
+ this.congRx.on('data', data => {
2160
+ if (this.socket === socket) this.onTCPSocketMessage(data);
2161
+ });
2134
2162
  this.emit('open');
2135
2163
  });
2136
2164
 
2137
2165
  this.socket.on('error', e => {
2166
+ if (this.socket !== socket) return;
2138
2167
  this.emit('error', e);
2139
2168
  });
2140
2169
 
2141
2170
  this.socket.on('close', () => {
2171
+ if (this.socket !== socket) return;
2142
2172
  this.emit('close');
2143
2173
  });
2174
+ this.stateChange('connecting','connecting');
2144
2175
 
2145
2176
  }
2146
2177
 
@@ -2195,300 +2226,6 @@ function requireConstants () {
2195
2226
  return constants;
2196
2227
  }
2197
2228
 
2198
- var bufferutil = {exports: {}};
2199
-
2200
- function commonjsRequire(path) {
2201
- throw new Error('Could not dynamically require "' + path + '". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.');
2202
- }
2203
-
2204
- var nodeGypBuild$1 = {exports: {}};
2205
-
2206
- var nodeGypBuild;
2207
- var hasRequiredNodeGypBuild$1;
2208
-
2209
- function requireNodeGypBuild$1 () {
2210
- if (hasRequiredNodeGypBuild$1) return nodeGypBuild;
2211
- hasRequiredNodeGypBuild$1 = 1;
2212
- var fs$1 = fs;
2213
- var path$1 = path;
2214
- var os = require$$2;
2215
-
2216
- // Workaround to fix webpack's build warnings: 'the request of a dependency is an expression'
2217
- var runtimeRequire = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : commonjsRequire; // eslint-disable-line
2218
-
2219
- var vars = (process.config && process.config.variables) || {};
2220
- var prebuildsOnly = !!process.env.PREBUILDS_ONLY;
2221
- var abi = process.versions.modules; // TODO: support old node where this is undef
2222
- var runtime = isElectron() ? 'electron' : (isNwjs() ? 'node-webkit' : 'node');
2223
-
2224
- var arch = process.env.npm_config_arch || os.arch();
2225
- var platform = process.env.npm_config_platform || os.platform();
2226
- var libc = process.env.LIBC || (isAlpine(platform) ? 'musl' : 'glibc');
2227
- var armv = process.env.ARM_VERSION || (arch === 'arm64' ? '8' : vars.arm_version) || '';
2228
- var uv = (process.versions.uv || '').split('.')[0];
2229
-
2230
- nodeGypBuild = load;
2231
-
2232
- function load (dir) {
2233
- return runtimeRequire(load.resolve(dir))
2234
- }
2235
-
2236
- load.resolve = load.path = function (dir) {
2237
- dir = path$1.resolve(dir || '.');
2238
-
2239
- try {
2240
- var name = runtimeRequire(path$1.join(dir, 'package.json')).name.toUpperCase().replace(/-/g, '_');
2241
- if (process.env[name + '_PREBUILD']) dir = process.env[name + '_PREBUILD'];
2242
- } catch (err) {}
2243
-
2244
- if (!prebuildsOnly) {
2245
- var release = getFirst(path$1.join(dir, 'build/Release'), matchBuild);
2246
- if (release) return release
2247
-
2248
- var debug = getFirst(path$1.join(dir, 'build/Debug'), matchBuild);
2249
- if (debug) return debug
2250
- }
2251
-
2252
- var prebuild = resolve(dir);
2253
- if (prebuild) return prebuild
2254
-
2255
- var nearby = resolve(path$1.dirname(process.execPath));
2256
- if (nearby) return nearby
2257
-
2258
- var target = [
2259
- 'platform=' + platform,
2260
- 'arch=' + arch,
2261
- 'runtime=' + runtime,
2262
- 'abi=' + abi,
2263
- 'uv=' + uv,
2264
- armv ? 'armv=' + armv : '',
2265
- 'libc=' + libc,
2266
- 'node=' + process.versions.node,
2267
- process.versions.electron ? 'electron=' + process.versions.electron : '',
2268
- typeof __webpack_require__ === 'function' ? 'webpack=true' : '' // eslint-disable-line
2269
- ].filter(Boolean).join(' ');
2270
-
2271
- throw new Error('No native build was found for ' + target + '\n loaded from: ' + dir + '\n')
2272
-
2273
- function resolve (dir) {
2274
- // Find matching "prebuilds/<platform>-<arch>" directory
2275
- var tuples = readdirSync(path$1.join(dir, 'prebuilds')).map(parseTuple);
2276
- var tuple = tuples.filter(matchTuple(platform, arch)).sort(compareTuples)[0];
2277
- if (!tuple) return
2278
-
2279
- // Find most specific flavor first
2280
- var prebuilds = path$1.join(dir, 'prebuilds', tuple.name);
2281
- var parsed = readdirSync(prebuilds).map(parseTags);
2282
- var candidates = parsed.filter(matchTags(runtime, abi));
2283
- var winner = candidates.sort(compareTags(runtime))[0];
2284
- if (winner) return path$1.join(prebuilds, winner.file)
2285
- }
2286
- };
2287
-
2288
- function readdirSync (dir) {
2289
- try {
2290
- return fs$1.readdirSync(dir)
2291
- } catch (err) {
2292
- return []
2293
- }
2294
- }
2295
-
2296
- function getFirst (dir, filter) {
2297
- var files = readdirSync(dir).filter(filter);
2298
- return files[0] && path$1.join(dir, files[0])
2299
- }
2300
-
2301
- function matchBuild (name) {
2302
- return /\.node$/.test(name)
2303
- }
2304
-
2305
- function parseTuple (name) {
2306
- // Example: darwin-x64+arm64
2307
- var arr = name.split('-');
2308
- if (arr.length !== 2) return
2309
-
2310
- var platform = arr[0];
2311
- var architectures = arr[1].split('+');
2312
-
2313
- if (!platform) return
2314
- if (!architectures.length) return
2315
- if (!architectures.every(Boolean)) return
2316
-
2317
- return { name, platform, architectures }
2318
- }
2319
-
2320
- function matchTuple (platform, arch) {
2321
- return function (tuple) {
2322
- if (tuple == null) return false
2323
- if (tuple.platform !== platform) return false
2324
- return tuple.architectures.includes(arch)
2325
- }
2326
- }
2327
-
2328
- function compareTuples (a, b) {
2329
- // Prefer single-arch prebuilds over multi-arch
2330
- return a.architectures.length - b.architectures.length
2331
- }
2332
-
2333
- function parseTags (file) {
2334
- var arr = file.split('.');
2335
- var extension = arr.pop();
2336
- var tags = { file: file, specificity: 0 };
2337
-
2338
- if (extension !== 'node') return
2339
-
2340
- for (var i = 0; i < arr.length; i++) {
2341
- var tag = arr[i];
2342
-
2343
- if (tag === 'node' || tag === 'electron' || tag === 'node-webkit') {
2344
- tags.runtime = tag;
2345
- } else if (tag === 'napi') {
2346
- tags.napi = true;
2347
- } else if (tag.slice(0, 3) === 'abi') {
2348
- tags.abi = tag.slice(3);
2349
- } else if (tag.slice(0, 2) === 'uv') {
2350
- tags.uv = tag.slice(2);
2351
- } else if (tag.slice(0, 4) === 'armv') {
2352
- tags.armv = tag.slice(4);
2353
- } else if (tag === 'glibc' || tag === 'musl') {
2354
- tags.libc = tag;
2355
- } else {
2356
- continue
2357
- }
2358
-
2359
- tags.specificity++;
2360
- }
2361
-
2362
- return tags
2363
- }
2364
-
2365
- function matchTags (runtime, abi) {
2366
- return function (tags) {
2367
- if (tags == null) return false
2368
- if (tags.runtime && tags.runtime !== runtime && !runtimeAgnostic(tags)) return false
2369
- if (tags.abi && tags.abi !== abi && !tags.napi) return false
2370
- if (tags.uv && tags.uv !== uv) return false
2371
- if (tags.armv && tags.armv !== armv) return false
2372
- if (tags.libc && tags.libc !== libc) return false
2373
-
2374
- return true
2375
- }
2376
- }
2377
-
2378
- function runtimeAgnostic (tags) {
2379
- return tags.runtime === 'node' && tags.napi
2380
- }
2381
-
2382
- function compareTags (runtime) {
2383
- // Precedence: non-agnostic runtime, abi over napi, then by specificity.
2384
- return function (a, b) {
2385
- if (a.runtime !== b.runtime) {
2386
- return a.runtime === runtime ? -1 : 1
2387
- } else if (a.abi !== b.abi) {
2388
- return a.abi ? -1 : 1
2389
- } else if (a.specificity !== b.specificity) {
2390
- return a.specificity > b.specificity ? -1 : 1
2391
- } else {
2392
- return 0
2393
- }
2394
- }
2395
- }
2396
-
2397
- function isNwjs () {
2398
- return !!(process.versions && process.versions.nw)
2399
- }
2400
-
2401
- function isElectron () {
2402
- if (process.versions && process.versions.electron) return true
2403
- if (process.env.ELECTRON_RUN_AS_NODE) return true
2404
- return typeof window !== 'undefined' && window.process && window.process.type === 'renderer'
2405
- }
2406
-
2407
- function isAlpine (platform) {
2408
- return platform === 'linux' && fs$1.existsSync('/etc/alpine-release')
2409
- }
2410
-
2411
- // Exposed for unit tests
2412
- // TODO: move to lib
2413
- load.parseTags = parseTags;
2414
- load.matchTags = matchTags;
2415
- load.compareTags = compareTags;
2416
- load.parseTuple = parseTuple;
2417
- load.matchTuple = matchTuple;
2418
- load.compareTuples = compareTuples;
2419
- return nodeGypBuild;
2420
- }
2421
-
2422
- var hasRequiredNodeGypBuild;
2423
-
2424
- function requireNodeGypBuild () {
2425
- if (hasRequiredNodeGypBuild) return nodeGypBuild$1.exports;
2426
- hasRequiredNodeGypBuild = 1;
2427
- const runtimeRequire = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : commonjsRequire; // eslint-disable-line
2428
- if (typeof runtimeRequire.addon === 'function') { // if the platform supports native resolving prefer that
2429
- nodeGypBuild$1.exports = runtimeRequire.addon.bind(runtimeRequire);
2430
- } else { // else use the runtime version here
2431
- nodeGypBuild$1.exports = requireNodeGypBuild$1();
2432
- }
2433
- return nodeGypBuild$1.exports;
2434
- }
2435
-
2436
- var fallback$1;
2437
- var hasRequiredFallback$1;
2438
-
2439
- function requireFallback$1 () {
2440
- if (hasRequiredFallback$1) return fallback$1;
2441
- hasRequiredFallback$1 = 1;
2442
-
2443
- /**
2444
- * Masks a buffer using the given mask.
2445
- *
2446
- * @param {Buffer} source The buffer to mask
2447
- * @param {Buffer} mask The mask to use
2448
- * @param {Buffer} output The buffer where to store the result
2449
- * @param {Number} offset The offset at which to start writing
2450
- * @param {Number} length The number of bytes to mask.
2451
- * @public
2452
- */
2453
- const mask = (source, mask, output, offset, length) => {
2454
- for (var i = 0; i < length; i++) {
2455
- output[offset + i] = source[i] ^ mask[i & 3];
2456
- }
2457
- };
2458
-
2459
- /**
2460
- * Unmasks a buffer using the given mask.
2461
- *
2462
- * @param {Buffer} buffer The buffer to unmask
2463
- * @param {Buffer} mask The mask to use
2464
- * @public
2465
- */
2466
- const unmask = (buffer, mask) => {
2467
- // Required until https://github.com/nodejs/node/issues/9006 is resolved.
2468
- const length = buffer.length;
2469
- for (var i = 0; i < length; i++) {
2470
- buffer[i] ^= mask[i & 3];
2471
- }
2472
- };
2473
-
2474
- fallback$1 = { mask, unmask };
2475
- return fallback$1;
2476
- }
2477
-
2478
- var hasRequiredBufferutil;
2479
-
2480
- function requireBufferutil () {
2481
- if (hasRequiredBufferutil) return bufferutil.exports;
2482
- hasRequiredBufferutil = 1;
2483
-
2484
- try {
2485
- bufferutil.exports = requireNodeGypBuild()(__dirname);
2486
- } catch (e) {
2487
- bufferutil.exports = requireFallback$1();
2488
- }
2489
- return bufferutil.exports;
2490
- }
2491
-
2492
2229
  var hasRequiredBufferUtil;
2493
2230
 
2494
2231
  function requireBufferUtil () {
@@ -2609,7 +2346,7 @@ function requireBufferUtil () {
2609
2346
  /* istanbul ignore else */
2610
2347
  if (!process.env.WS_NO_BUFFER_UTIL) {
2611
2348
  try {
2612
- const bufferUtil$1 = requireBufferutil();
2349
+ const bufferUtil$1 = require('bufferutil');
2613
2350
 
2614
2351
  bufferUtil.exports.mask = function (source, mask, output, offset, length) {
2615
2352
  if (length < 48) _mask(source, mask, output, offset, length);
@@ -3230,92 +2967,6 @@ function requirePermessageDeflate () {
3230
2967
 
3231
2968
  var validation = {exports: {}};
3232
2969
 
3233
- var utf8Validate = {exports: {}};
3234
-
3235
- var fallback;
3236
- var hasRequiredFallback;
3237
-
3238
- function requireFallback () {
3239
- if (hasRequiredFallback) return fallback;
3240
- hasRequiredFallback = 1;
3241
-
3242
- /**
3243
- * Checks if a given buffer contains only correct UTF-8.
3244
- * Ported from https://www.cl.cam.ac.uk/%7Emgk25/ucs/utf8_check.c by
3245
- * Markus Kuhn.
3246
- *
3247
- * @param {Buffer} buf The buffer to check
3248
- * @return {Boolean} `true` if `buf` contains only correct UTF-8, else `false`
3249
- * @public
3250
- */
3251
- function isValidUTF8(buf) {
3252
- const len = buf.length;
3253
- let i = 0;
3254
-
3255
- while (i < len) {
3256
- if ((buf[i] & 0x80) === 0x00) { // 0xxxxxxx
3257
- i++;
3258
- } else if ((buf[i] & 0xe0) === 0xc0) { // 110xxxxx 10xxxxxx
3259
- if (
3260
- i + 1 === len ||
3261
- (buf[i + 1] & 0xc0) !== 0x80 ||
3262
- (buf[i] & 0xfe) === 0xc0 // overlong
3263
- ) {
3264
- return false;
3265
- }
3266
-
3267
- i += 2;
3268
- } else if ((buf[i] & 0xf0) === 0xe0) { // 1110xxxx 10xxxxxx 10xxxxxx
3269
- if (
3270
- i + 2 >= len ||
3271
- (buf[i + 1] & 0xc0) !== 0x80 ||
3272
- (buf[i + 2] & 0xc0) !== 0x80 ||
3273
- buf[i] === 0xe0 && (buf[i + 1] & 0xe0) === 0x80 || // overlong
3274
- buf[i] === 0xed && (buf[i + 1] & 0xe0) === 0xa0 // surrogate (U+D800 - U+DFFF)
3275
- ) {
3276
- return false;
3277
- }
3278
-
3279
- i += 3;
3280
- } else if ((buf[i] & 0xf8) === 0xf0) { // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
3281
- if (
3282
- i + 3 >= len ||
3283
- (buf[i + 1] & 0xc0) !== 0x80 ||
3284
- (buf[i + 2] & 0xc0) !== 0x80 ||
3285
- (buf[i + 3] & 0xc0) !== 0x80 ||
3286
- buf[i] === 0xf0 && (buf[i + 1] & 0xf0) === 0x80 || // overlong
3287
- buf[i] === 0xf4 && buf[i + 1] > 0x8f || buf[i] > 0xf4 // > U+10FFFF
3288
- ) {
3289
- return false;
3290
- }
3291
-
3292
- i += 4;
3293
- } else {
3294
- return false;
3295
- }
3296
- }
3297
-
3298
- return true;
3299
- }
3300
-
3301
- fallback = isValidUTF8;
3302
- return fallback;
3303
- }
3304
-
3305
- var hasRequiredUtf8Validate;
3306
-
3307
- function requireUtf8Validate () {
3308
- if (hasRequiredUtf8Validate) return utf8Validate.exports;
3309
- hasRequiredUtf8Validate = 1;
3310
-
3311
- try {
3312
- utf8Validate.exports = requireNodeGypBuild()(__dirname);
3313
- } catch (e) {
3314
- utf8Validate.exports = requireFallback();
3315
- }
3316
- return utf8Validate.exports;
3317
- }
3318
-
3319
2970
  var hasRequiredValidation;
3320
2971
 
3321
2972
  function requireValidation () {
@@ -3463,7 +3114,7 @@ function requireValidation () {
3463
3114
  };
3464
3115
  } /* istanbul ignore else */ else if (!process.env.WS_NO_UTF_8_VALIDATE) {
3465
3116
  try {
3466
- const isValidUTF8 = requireUtf8Validate();
3117
+ const isValidUTF8 = require('utf-8-validate');
3467
3118
 
3468
3119
  validation.exports.isValidUTF8 = function (buf) {
3469
3120
  return buf.length < 32 ? _isValidUTF8(buf) : isValidUTF8(buf);
@@ -4239,7 +3890,7 @@ function requireSender () {
4239
3890
  const { randomFillSync } = require$$1;
4240
3891
  const {
4241
3892
  types: { isUint8Array }
4242
- } = require$$2$1;
3893
+ } = require$$2;
4243
3894
 
4244
3895
  const PerMessageDeflate = requirePermessageDeflate();
4245
3896
  const { EMPTY_BUFFER, kWebSocket, NOOP } = requireConstants();
@@ -5363,7 +5014,7 @@ function requireWebsocket () {
5363
5014
 
5364
5015
  const EventEmitter = require$$0$3;
5365
5016
  const https = require$$1$1;
5366
- const http = require$$2$2;
5017
+ const http = require$$2$1;
5367
5018
  const net$1 = net;
5368
5019
  const tls = require$$4;
5369
5020
  const { randomBytes, createHash } = require$$1;
@@ -7032,7 +6683,7 @@ function requireWebsocketServer () {
7032
6683
  hasRequiredWebsocketServer = 1;
7033
6684
 
7034
6685
  const EventEmitter = require$$0$3;
7035
- const http = require$$2$2;
6686
+ const http = require$$2$1;
7036
6687
  const { Duplex } = require$$0;
7037
6688
  const { createHash } = require$$1;
7038
6689
 
@@ -7610,6 +7261,8 @@ class IOWS extends IOCore {
7610
7261
  close() {
7611
7262
  if (this.socket) {
7612
7263
  this.socket.removeAllListeners();
7264
+ // Closing a connecting ws can emit an asynchronous error after detachment.
7265
+ this.socket.on('error', () => {});
7613
7266
  if (this.socket.readyState !== WebSocket.CLOSED) {
7614
7267
  this.socket.close();
7615
7268
  }
@@ -7629,21 +7282,27 @@ class IOWS extends IOCore {
7629
7282
 
7630
7283
  createConnection(url) {
7631
7284
  // node WebSocket
7632
- this.socket = new WebSocket(url);
7633
- this.stateChange('connecting','connecting');
7285
+ const socket = this.socket = new WebSocket(url);
7286
+ this._closed = false;
7634
7287
 
7635
7288
  this.socket.onopen = () => {
7636
- 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
+ });
7637
7293
  this.emit('open');
7638
7294
  };
7639
7295
 
7640
7296
  this.socket.onerror = (e) => {
7297
+ if (this.socket !== socket) return;
7641
7298
  this.emit('error', e);
7642
7299
  };
7643
7300
 
7644
7301
  this.socket.onclose = () => {
7302
+ if (this.socket !== socket) return;
7645
7303
  this.emit('close');
7646
7304
  };
7305
+ this.stateChange('connecting','connecting');
7647
7306
  }
7648
7307
 
7649
7308
  onWebSocketMessage(data) {
@@ -7762,7 +7421,9 @@ class RemoteCore {
7762
7421
  static ssid = 1;
7763
7422
 
7764
7423
  setState(state) {
7424
+ const previous = this.state;
7765
7425
  this.state = state;
7426
+ if (previous !== state) this.manager.server?.emit('remote:state', this, previous);
7766
7427
  if (serverOption.debug.showAuthInfo) {
7767
7428
  this.stateLog.push( state + ":" + STATE[ state] );
7768
7429
  if( state == STATE.AUTH_RES){
@@ -7831,6 +7492,8 @@ class RemoteCore {
7831
7492
  // CongSocket or WebSocket
7832
7493
  onSocketMessage(message, isBinary = true) {
7833
7494
 
7495
+ if (this._closing || this.state === STATE.CLOSED) return;
7496
+
7834
7497
  this.receiveMonitor(); // rx-data, ping/pong and timeout check
7835
7498
  if (!this.rxQuotaChecker(message)) return
7836
7499
 
@@ -7884,10 +7547,11 @@ class RemoteCore {
7884
7547
  break;
7885
7548
 
7886
7549
  case IOMsg.CID_REQ:
7887
- if (this.state < STATE.SERVER_READY) {
7550
+ if (![STATE.SERVER_READY, STATE.AUTH_RES, STATE.AUTH_FAIL, STATE.CID_RES].includes(this.state)) {
7888
7551
  // Protocol violation: CID_REQ was sent before receiving the SERVER_READY signal.
7889
7552
  console.log('CID_REQ before SERVER_READY');
7890
7553
  this.close();
7554
+ return;
7891
7555
  }
7892
7556
 
7893
7557
  if (!this.cid) {
@@ -8007,9 +7671,12 @@ class RemoteCore {
8007
7671
  // client's auth requst
8008
7672
  case it.BohoMsg.AUTH_REQ:
8009
7673
  if (!this.manager.authManager) return
8010
- 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)) {
8011
7677
  console.log('protocol error. must called auth_req after server_ready');
8012
7678
  this.close();
7679
+ return;
8013
7680
  }
8014
7681
  this.setState(STATE.AUTH_REQ);
8015
7682
  //async
@@ -8171,7 +7838,7 @@ function getIPv4HexString(ipStr) {
8171
7838
  }
8172
7839
 
8173
7840
  function getLocalAddress() {
8174
- const nets = require$$2.networkInterfaces();
7841
+ const nets = os.networkInterfaces();
8175
7842
  // console.log(nets)
8176
7843
  const results = Object.create(null); // Or just '{}', an empty object
8177
7844
  let localAddress = '';
@@ -8358,6 +8025,7 @@ class Remote extends RemoteCore {
8358
8025
  }
8359
8026
 
8360
8027
  close(terminateNow = false) {
8028
+ this._closing = true;
8361
8029
  this.getTraffic();
8362
8030
  if (this.socketType === 'websocket') {
8363
8031
  if (terminateNow) this.socket.terminate();
@@ -8623,6 +8291,7 @@ class Manager {
8623
8291
  if (socket.isAlive === false) {
8624
8292
  console.log('## timeout. cid:', remo.cid);
8625
8293
  remo.close();
8294
+ return;
8626
8295
  }
8627
8296
 
8628
8297
  socket.txCounter++;
@@ -8662,6 +8331,9 @@ class Manager {
8662
8331
 
8663
8332
  removeRemote(remote) {
8664
8333
 
8334
+ remote._closing = true;
8335
+ remote.setState(STATE.CLOSED);
8336
+
8665
8337
  let remoteInfo = `- IP:${remote.ip} #${remote.ssid} cid:${remote.cid} ${remote?.socket.socketType === 'websocket' ? "WS" : "CS"} `;
8666
8338
  if (this.connectionLogger) this.connectionLogger.log(remoteInfo);
8667
8339
  this.deligateSignal(remote, '@state', 'close');
@@ -8699,7 +8371,7 @@ class Manager {
8699
8371
  }
8700
8372
 
8701
8373
  this.remotes.delete(remote);
8702
- this.cid2remote.delete(remote.cid);
8374
+ if (this.cid2remote.get(remote.cid) === remote) this.cid2remote.delete(remote.cid);
8703
8375
  remote = null;
8704
8376
 
8705
8377
  }
@@ -8729,6 +8401,7 @@ class Manager {
8729
8401
  let tagLen = buffer.readUint8(1);
8730
8402
  let newTagBuf = encoder.encode(newTag);
8731
8403
  let newTagLen = newTagBuf.byteLength;
8404
+ if (newTagLen > SIZE_LIMIT.TAG_LEN1) return null;
8732
8405
  let payloadCunk = buffer.subarray(2 + tagLen);
8733
8406
  let newBuffer = Buffer.concat([Buffer.from([msgType, newTagLen]), newTagBuf, payloadCunk]);
8734
8407
  return newBuffer
@@ -8766,6 +8439,7 @@ class Manager {
8766
8439
  }
8767
8440
 
8768
8441
  sender(tag, remote, message) {
8442
+ if (encoder.encode(tag).byteLength > SIZE_LIMIT.TAG_LEN1) return ['err', 'Tag exceeds 255 UTF-8 bytes']
8769
8443
  if (serverOption.membersOnly && !remote.boho.isAuthorized) {
8770
8444
  // console.log("### server reject client signal reason: [membersOnly]" ,tag, remote.cid )
8771
8445
  remote.send(Buffer.from([IOMsg.AUTH_CLEAR]));
@@ -8782,6 +8456,7 @@ class Manager {
8782
8456
  // modify signalpack with cid_appneded tag.
8783
8457
  tag = remote.cid + tag;
8784
8458
  message = this.getNewSignalTagMessage(message, tag);
8459
+ if (!message) return ['err', 'CID-prefixed tag exceeds 255 UTF-8 bytes']
8785
8460
 
8786
8461
  } else if (cidIndex > 0) {
8787
8462
  // uni-cast.
@@ -8791,6 +8466,7 @@ class Manager {
8791
8466
  let ommitCIdTag = '@' + tag.split('@')[1];
8792
8467
  // console.log('uni-cast tag change from', tag, 'to' , ommitCIdTag )
8793
8468
  message = this.getNewSignalTagMessage(message, ommitCIdTag);
8469
+ if (!message) return ['err', 'Tag exceeds 255 UTF-8 bytes']
8794
8470
  // console.log(`origin tag: ${tag} omitTag: ${this.getSignalTag(message)}`)
8795
8471
  this.cid2remote.get(targetCId).send_enc_mode(message);
8796
8472
  return ['ok', 1]
@@ -9267,6 +8943,8 @@ class BohoAuth {
9267
8943
  }
9268
8944
 
9269
8945
  send_auth_fail(peer, reason) {
8946
+ if (peer._closing || peer.state === STATE.CLOSED) return;
8947
+ peer.boho.isAuthorized = false;
9270
8948
  if (this.authLogger) {
9271
8949
  let peerInfo = `FAIL #${peer.ssid} reason:${reason} `;
9272
8950
  this.authLogger.log(peerInfo);
@@ -9275,7 +8953,9 @@ class BohoAuth {
9275
8953
  peer.setState(STATE.AUTH_FAIL);
9276
8954
  // add some delay time.
9277
8955
  setTimeout(e => {
9278
- 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
+ }
9279
8959
  }, serverOption.auth.delay_auth_fail);
9280
8960
  }
9281
8961
 
@@ -9298,6 +8978,7 @@ class BohoAuth {
9298
8978
 
9299
8979
  //2. get key of id from DB
9300
8980
  let authInfo = await this.keyProvider.getAuth(id);
8981
+ if (peer._closing || peer.state === STATE.CLOSED) return;
9301
8982
  if (!authInfo) {
9302
8983
  this.send_auth_fail(peer, 'NO ID:' + id);
9303
8984
  return
@@ -9330,33 +9011,31 @@ class BohoAuth {
9330
9011
  //4. get info
9331
9012
 
9332
9013
  //5. check duplicate login.
9333
- if (peer.manager.cid2remote.has(authInfo.cid)) {
9014
+ if (peer.manager.cid2remote.has(authInfo.cid) && peer.manager.cid2remote.get(authInfo.cid) !== peer) {
9334
9015
  let old = peer.manager.cid2remote.get(authInfo.cid);
9335
9016
  console.log('[WARN]DUPLICATE_LOGIN detected.', old.cid);
9336
9017
  old.ping(); // check the connection.
9337
- if (old == peer) {
9338
- console.log('## trying to RELOGIN after login.');
9339
- return
9340
- }
9341
9018
 
9342
9019
  let authClearSignal = $$1.pack(
9343
9020
  $$1.MB('#MsgType', '8', IOMsg.AUTH_CLEAR),
9344
9021
  $$1.MB('#reason', 'duplicate login.')
9345
9022
  );
9346
9023
  if (this.keepOldConnection) { // default true
9024
+ peer.boho.isAuthorized = false;
9347
9025
  // keep old connection. reject new connection by sending auth_fail signal.
9348
9026
  // peer.send(authClearSignal)
9349
9027
  if( peer.socketType == 'websocket'){
9350
9028
  this.send_auth_fail(peer, 'duplicate login');
9351
9029
  }else {
9030
+ peer.setState(STATE.AUTH_FAIL);
9352
9031
  peer.close(); //arduino
9353
9032
  }
9354
9033
  } else {
9355
9034
  // accept new connection. stop the old connection by sending auth_clear signal.
9356
9035
  old.send(authClearSignal);
9357
- peer.close();
9036
+ old.close();
9358
9037
  }
9359
- return
9038
+ if (this.keepOldConnection) return
9360
9039
  }
9361
9040
 
9362
9041
  //6. delete current (or anonymouse)cid if exist.