iosignal 2.2.1 → 3.0.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.
Files changed (49) hide show
  1. package/README.md +248 -114
  2. package/dist/io.d.ts +481 -0
  3. package/dist/io.js +4 -5
  4. package/dist/io.js.map +1 -1
  5. package/dist/io.min.js +4 -5
  6. package/dist/io.min.js.map +1 -1
  7. package/dist/iosignal.js +9559 -0
  8. package/dist/types/client/IOCore.d.ts +408 -0
  9. package/dist/types/client/browser/IOWebSocket.d.ts +75 -0
  10. package/dist/types/common/constants.d.ts +169 -0
  11. package/dist/types/common/payload.d.ts +6 -0
  12. package/dist/types/common/quotaTable.d.ts +47 -0
  13. package/docs/README.ko.md +371 -0
  14. package/docs/iosignal_architecture.png +0 -0
  15. package/examples/react-chat-js/dist/assets/index-Cy4qaVcJ.js +56 -0
  16. package/examples/react-chat-js/dist/assets/index-NETrjYhz.css +1 -0
  17. package/examples/react-chat-js/dist/index.html +14 -0
  18. package/examples/react-chat-js/index.html +13 -0
  19. package/examples/react-chat-js/package-lock.json +1628 -0
  20. package/examples/react-chat-js/package.json +20 -0
  21. package/examples/react-chat-js/src/App.css +61 -0
  22. package/examples/react-chat-js/src/App.jsx +125 -0
  23. package/examples/react-chat-js/src/index.css +13 -0
  24. package/examples/react-chat-js/src/main.jsx +13 -0
  25. package/examples/react-chat-js/src/shared_io.js +12 -0
  26. package/examples/react-chat-js/vite.config.js +7 -0
  27. package/examples/server/index.js +12 -0
  28. package/examples/server/package.json +12 -0
  29. package/package.json +24 -12
  30. package/rollup.config.js +13 -8
  31. package/src/auth/Auth_File.js +1 -1
  32. package/src/client/IOCongSocket.js +20 -9
  33. package/src/client/IOCore.js +422 -77
  34. package/src/client/IOWS.js +12 -15
  35. package/src/client/browser/IOWebSocket.js +217 -0
  36. package/src/common/constants.js +91 -1
  37. package/test-nodejs/server-Auth_File.js +1 -1
  38. package/tsconfig.json +24 -0
  39. package/dist/iosignal.cjs +0 -23
  40. package/dist/iosignal.mjs +0 -23
  41. package/img/iosignal_stack.png +0 -0
  42. package/src/client/IOWebSocket.js +0 -118
  43. package/test-nodejs/client.cjs +0 -17
  44. package/test-nodejs/server-commonjs.cjs +0 -9
  45. /package/{auth_file.mjs → auth_file.js} +0 -0
  46. /package/test-nodejs/{client.mjs → client.js} +0 -0
  47. /package/test-nodejs/{client_api_reply.mjs → client_api_reply.js} +0 -0
  48. /package/test-nodejs/{server-esm.mjs → server.js} +0 -0
  49. /package/test-nodejs/{simple-server.mjs → simple-server.js} +0 -0
@@ -5,6 +5,19 @@ import { quotaTable } from '../common/quotaTable.js'
5
5
  import { getSignalPack } from '../common/payload.js';
6
6
  import Boho from "boho";
7
7
 
8
+ /**
9
+ * @typedef {import('meta-buffer-pack').MBP} MBP
10
+ * @typedef {import('../common/constants.js').IOMsg} IOMsg
11
+ * @typedef {import('../common/constants.js').PAYLOAD_TYPE} PAYLOAD_TYPE
12
+ * @typedef {import('../common/constants.js').SIZE_LIMIT} SIZE_LIMIT
13
+ * @typedef {import('../common/constants.js').ENC_MODE} ENC_MODE
14
+ * @typedef {import('../common/constants.js').STATES} STATES
15
+ * @typedef {import('../common/quotaTable.js').quotaTable} quotaTable
16
+
17
+ * @typedef {import('boho').Boho} Boho
18
+ * @typedef {import('boho').Buffer} Buffer
19
+ */
20
+
8
21
  const Buffer = MBP.Buffer;
9
22
  const encoder = new TextEncoder()
10
23
  const decoder = new TextDecoder()
@@ -18,76 +31,279 @@ function byteToUrl(buffer) {
18
31
  return address + ':' + port.toString()
19
32
  }
20
33
 
34
+ /**
35
+ * Core class for handling WebSocket communication.
36
+ * @augments {EventEmitter}
37
+ */
21
38
  export class IOCore extends EventEmitter {
39
+ /**
40
+ * @param {string} url - The WebSocket URL to connect to.
41
+ */
22
42
  constructor(url) {
23
43
  super();
44
+ /**
45
+ * Client ID received from the server.
46
+ * @type {string}
47
+ */
24
48
  this.cid = "" // get from the server CID_RES
49
+ /**
50
+ * IP address received from the server.
51
+ * @type {string}
52
+ */
25
53
  this.ip = "" // get from the server IAM_RES message.
54
+ /**
55
+ * The WebSocket instance.
56
+ * @type {WebSocket | null}
57
+ */
26
58
  this.socket = null;
59
+ /**
60
+ * The default server URL.
61
+ * @type {string}
62
+ */
27
63
  this.url = url; // init default server url
64
+ /**
65
+ * Current connection state (number).
66
+ * @type {number}
67
+ */
28
68
  this.state = STATES.CLOSED; // Number type
69
+ /**
70
+ * Current connection state (string).
71
+ * @type {string}
72
+ */
29
73
  this.stateName = this.getStateName() // String type
30
74
 
75
+ /**
76
+ * Transmitted message counter.
77
+ * @type {number}
78
+ */
31
79
  this.txCounter = 0;
80
+ /**
81
+ * Received message counter.
82
+ * @type {number}
83
+ */
32
84
  this.rxCounter = 0;
85
+ /**
86
+ * Transmitted bytes counter.
87
+ * @type {number}
88
+ */
33
89
  this.txBytes = 0;
90
+ /**
91
+ * Received bytes counter.
92
+ * @type {number}
93
+ */
34
94
  this.rxBytes = 0;
35
95
 
96
+ /**
97
+ * Last transmit/receive time.
98
+ * @type {number}
99
+ */
36
100
  this.lastTxRxTime = Date.now();
101
+ /**
102
+ * Period for connection checker.
103
+ * @type {number}
104
+ */
37
105
  this.connectionCheckerPeriod = SIZE_LIMIT.CONNECTION_CHECKER_PERIOD;
106
+ /**
107
+ * Interval ID for connection checker.
108
+ * @type {NodeJS.Timeout | null}
109
+ */
38
110
  this.connectionCheckerIntervalID = null;
39
111
 
112
+ /**
113
+ * Boho instance for encryption/decryption.
114
+ * @type {Boho}
115
+ */
40
116
  this.boho = new Boho()
117
+ /**
118
+ * Indicates if the connection is TLS (wss).
119
+ * @type {boolean}
120
+ */
41
121
  this.TLS = false // true if protocol is wss(TLS)
122
+ /**
123
+ * Encryption mode.
124
+ * @type {number}
125
+ */
42
126
  this.encMode = ENC_MODE.AUTO;
127
+ /**
128
+ * Indicates if authentication is used.
129
+ * @type {boolean}
130
+ */
43
131
  this.useAuth = false;
44
132
 
133
+ /**
134
+ * Nickname.
135
+ * @type {string}
136
+ */
45
137
  this.nick = "";
138
+ /**
139
+ * Set of subscribed channels.
140
+ * @type {Set<string>}
141
+ */
46
142
  this.channels = new Set()
143
+ /**
144
+ * Map of promises for message responses.
145
+ * @type {Map<number, Array<Function>>}
146
+ */
47
147
  this.promiseMap = new Map()
148
+ /**
149
+ * Timeout for message promises.
150
+ * @type {number}
151
+ */
48
152
  this.promiseTimeOut = SIZE_LIMIT.PROMISE_TIMEOUT
153
+ /**
154
+ * Message ID for promises.
155
+ * @type {number}
156
+ */
49
157
  this.mid = 0 // promise message id
50
158
 
159
+ /**
160
+ * Quota level.
161
+ * @type {number}
162
+ */
51
163
  this.level = 3; // also defaultQuotaLevel
164
+ /**
165
+ * Quota table for current level.
166
+ * @type {object}
167
+ */
52
168
  this.quota = quotaTable[this.level];
169
+ /**
170
+ * Server settings.
171
+ * @type {object}
172
+ */
53
173
  this.serverSet = {}
174
+ /**
175
+ * Map of linked channels.
176
+ * @type {Map<string, Set<string>>}
177
+ */
54
178
  this.linkMap = new Map()
55
179
 
180
+ /**
181
+ * Indicates if auto-reconnect is enabled.
182
+ * @type {boolean}
183
+ * @default true
184
+ * */
185
+ this.autoReconnect = true; // default true
186
+
187
+ /**
188
+ * A flag to prevent duplicate close operations.
189
+ * @type {boolean}
190
+ * @private
191
+ */
192
+ this._closed = false; // 중복 close 방지
193
+
56
194
  this.on('open', this.onOpen.bind(this))
57
195
  this.on('close', this.onClose.bind(this))
58
196
  this.on('socket_data', this.onData.bind(this))
59
197
  }
60
198
 
61
199
 
200
+
201
+ /**
202
+ * Performs common cleanup for the connection. It clears pending promises,
203
+ * resets the socket reference, and sets the state to closed.
204
+ * This method is guarded to only run once.
205
+ * If autoReconnect is false, it also clears the keep-alive timer.
206
+ */
207
+ close() {
208
+ if (this._closed) return;
209
+ this._closed = true;
210
+
211
+ // If auto-reconnect is disabled, we must stop the keep-alive timer.
212
+ if (this.autoReconnect === false) {
213
+ clearInterval(this.connectionCheckerIntervalID);
214
+ this.connectionCheckerIntervalID = null;
215
+ }
216
+
217
+ // socket clean
218
+ if (this.socket) {
219
+ try {
220
+ this.socket.close?.();
221
+ } catch {}
222
+ this.socket = null;
223
+ }
224
+ this.promiseMap.clear();
225
+ this.emit('closed');
226
+ this.stateChange('closed');
227
+ }
228
+
229
+ /**
230
+ * Disables auto-reconnect and closes the current connection.
231
+ * The instance can be re-opened manually later. For complete cleanup, use destroy().
232
+ */
233
+ stop() {
234
+ this.autoReconnect = false;
235
+ this.close();
236
+ }
237
+
238
+ /**
239
+ * Permanently destroys the instance, cleaning up all resources.
240
+ * The instance will not be usable after this.
241
+ */
242
+ destroy() {
243
+ this.stop();
244
+ this.removeAllListeners();
245
+
246
+ this.channels.clear();
247
+ this.linkMap.clear();
248
+
249
+ // Help GC
250
+ this.boho = null;
251
+ }
252
+
253
+ /**
254
+ * The core keep-alive logic. It checks if auto-reconnect is enabled.
255
+ * The actual check for the socket's state is implemented in the child classes.
256
+ */
257
+ keepAlive() {
258
+ if (!this.autoReconnect) return;
259
+ // The specific logic for checking the socket's state and reconnecting
260
+ // is implemented in the child classes (IOWS, IOCongSocket, etc.).
261
+ }
262
+
263
+ /**
264
+ * Redirects the connection to a new URL.
265
+ * @param {string} url2 - The new URL to redirect to.
266
+ */
62
267
  redirect(url2) {
63
268
  this.close()
64
269
  this.stateChange('redirecting')
65
270
  this.createConnection(url2)
66
271
  }
67
272
 
273
+ /**
274
+ * Opens the WebSocket connection.
275
+ * @param {string} [url] - Optional URL to connect to. If not provided, uses the instance's URL.
276
+ */
68
277
  open(url) {
69
- if (!url && !this.url) return;
278
+ // If a connection is already active or in progress, calling open() implies a reconnect.
279
+ // Close the existing socket first to ensure a clean state.
280
+ if (this.socket) {
281
+ this.close();
282
+ }
70
283
 
71
284
  if (url) {
72
- if (!this.url) { // default host url
73
- this.url = url
74
- } else if (url !== this.url) { // default host url change
75
- this.url = url;
76
- if (this.socket) {
77
- this.close()
78
- return
79
- }
80
- }
285
+ this.url = url;
286
+ }
287
+
288
+ if (!this.url) {
289
+ this.emit('error', new Error('URL is not set.'));
290
+ return;
81
291
  }
82
292
 
83
- this.createConnection(this.url)
293
+ // The actual connection is created here.
294
+ this.createConnection(this.url);
84
295
 
296
+ // Ensure the keep-alive timer is running.
85
297
  if (!this.connectionCheckerIntervalID) {
86
298
  this.connectionCheckerIntervalID = setInterval(this.keepAlive.bind(this), this.connectionCheckerPeriod);
87
299
  }
88
300
  }
89
301
 
302
+ /**
303
+ * Handles the 'open' event of the WebSocket. Resets the closed flag and sets the state to open.
304
+ */
90
305
  onOpen() {
306
+ this._closed = false;
91
307
  if (this.url.includes("wss://")) {
92
308
  this.TLS = true;
93
309
  } else {
@@ -96,39 +312,40 @@ export class IOCore extends EventEmitter {
96
312
  this.stateChange('open')
97
313
  }
98
314
 
315
+ /**
316
+ * Handles the 'close' event of the WebSocket.
317
+ */
99
318
  onClose() {
100
319
  this.boho.isAuthorized = false;
101
320
  this.cid = ""
102
321
  this.stateChange('closed')
103
322
  }
104
323
 
105
- // manual login
324
+ /**
325
+ * Manually logs in with provided ID and key.
326
+ * @param {string} id - The user ID.
327
+ * @param {string} key - The user key.
328
+ * @returns {boolean}
329
+ */
106
330
  login(id, key) {
107
- if (!id && !key) {
108
- console.log('no id and key.')
109
- return
110
- }
111
- console.log('manual login: ', id)
331
+ if (!this.auth(id, key)) return false;
112
332
 
113
- if (!key && id.includes('.')) {
114
- this.boho.set_id_key(id)
115
- } else if (id && key) {
116
- this.boho.set_id8(id)
117
- this.boho.set_key(key)
118
- } else {
119
- console.log('no id or key.')
120
- return
121
- }
122
333
  this.useAuth = true
123
334
  let auth_pack = this.boho.auth_req()
124
335
  this.send(auth_pack)
336
+ return true
125
337
  }
126
338
 
127
- // auto login
339
+ /**
340
+ * Sets up authentication for auto-login.
341
+ * @param {string} id - The user ID.
342
+ * @param {string} key - The user key.
343
+ * @returns {boolean}
344
+ */
128
345
  auth(id, key) {
129
346
  if (!id && !key) {
130
- console.log('no id and key.')
131
- return
347
+ this.emit('error', new Error('auth failed. no id and key.'))
348
+ return false
132
349
  }
133
350
 
134
351
  if (!key && id.includes('.')) {
@@ -137,12 +354,17 @@ export class IOCore extends EventEmitter {
137
354
  this.boho.set_id8(id)
138
355
  this.boho.set_key(key)
139
356
  } else {
140
- console.log('no id or key.')
141
- return
357
+ this.emit('error', new Error('auth failed. no id or key.'))
358
+ return false
142
359
  }
143
360
  this.useAuth = true
361
+ return true
144
362
  }
145
363
 
364
+ /**
365
+ * Handles incoming data from the WebSocket.
366
+ * @param {Buffer} buffer - The incoming data buffer.
367
+ */
146
368
  onData(buffer) {
147
369
  let msgType = buffer[0];
148
370
  let decoded;
@@ -202,10 +424,8 @@ export class IOCore extends EventEmitter {
202
424
  if (jsonInfo.ip) {
203
425
  this.ip = jsonInfo.ip;
204
426
  }
205
- console.log('<IAM_RES>', JSON.stringify(jsonInfo))
206
- // console.log('<IAM_RES>', JSON.stringify(jsonInfo,null,2))
207
427
  } catch (error) {
208
- // console.log('<IAM_RES> data error')
428
+ this.emit('error', new Error('IAM_RES data error'))
209
429
  }
210
430
  break;
211
431
 
@@ -272,7 +492,7 @@ export class IOCore extends EventEmitter {
272
492
  }
273
493
 
274
494
  } catch (error) {
275
- // console.log('<SERVER_SIGNAL> parsing error')
495
+ this.emit('error', new Error('SERVER_SIGNAL parsing error'))
276
496
  }
277
497
  break;
278
498
 
@@ -283,7 +503,7 @@ export class IOCore extends EventEmitter {
283
503
  this.emit(setPack.topic, ...setPack.args)
284
504
  }
285
505
  } catch (error) {
286
- // console.log('<SET> parsing error')
506
+ this.emit('error', new Error('SET parsing error'))
287
507
  }
288
508
  break;
289
509
 
@@ -312,7 +532,10 @@ export class IOCore extends EventEmitter {
312
532
  case PAYLOAD_TYPE.TEXT:
313
533
  // !! Must remove null char before decode in JS.
314
534
  // string payload contains null char for the c/cpp devices.
315
- let payloadStringWithoutNull = payloadBuffer.subarray(0, payloadBuffer.byteLength - 1)
535
+ let payloadStringWithoutNull = payloadBuffer
536
+ if (payloadBuffer[payloadBuffer.byteLength - 1] === 0) {
537
+ payloadStringWithoutNull = payloadBuffer.subarray(0, payloadBuffer.byteLength - 1)
538
+ }
316
539
  let oneString = decoder.decode(payloadStringWithoutNull)
317
540
  if (tag.indexOf('@') === 0) this.emit('@', oneString, tag)
318
541
  if (tag !== '@') this.emit(tag, oneString, tag)
@@ -348,7 +571,7 @@ export class IOCore extends EventEmitter {
348
571
  }
349
572
 
350
573
  } catch (err) {
351
- // console.log('## signal parse err', err)
574
+ this.emit('error', new Error('signal parse err'))
352
575
  }
353
576
  break;
354
577
 
@@ -392,6 +615,10 @@ export class IOCore extends EventEmitter {
392
615
  }
393
616
  }
394
617
 
618
+ /**
619
+ * Sends an IAM (I Am) message to the server.
620
+ * @param {string} [title] - Optional title for the IAM message.
621
+ */
395
622
  iam(title) {
396
623
  // console.log('iam', title)
397
624
  if (title) {
@@ -407,17 +634,25 @@ export class IOCore extends EventEmitter {
407
634
  }
408
635
 
409
636
 
637
+ /**
638
+ * Sends a PING message to the server.
639
+ */
410
640
  ping() {
411
641
  this.send(Buffer.from([IOMsg.PING]))
412
642
  }
413
643
 
644
+ /**
645
+ * Sends a PONG message to the server.
646
+ */
414
647
  pong() {
415
648
  this.send(Buffer.from([IOMsg.PONG]))
416
649
  }
417
650
 
418
651
 
419
- // application level ping tool.
420
- // simple message sending and reply.
652
+ /**
653
+ * Sends an ECHO message to the server.
654
+ * @param {*} [args] - Optional arguments to echo.
655
+ */
421
656
  echo(args) {
422
657
  if (args) {
423
658
  console.log('echo args:', args)
@@ -432,10 +667,18 @@ export class IOCore extends EventEmitter {
432
667
  }
433
668
 
434
669
 
670
+ /**
671
+ * Sends binary data.
672
+ * @param {...any} data - Data to send.
673
+ */
435
674
  bin(...data) {
436
675
  this.send(MBP.U8pack(...data))
437
676
  }
438
677
 
678
+ /**
679
+ * Sends data over the WebSocket.
680
+ * @param {Buffer} data - The data buffer to send.
681
+ */
439
682
  send(data) {
440
683
  if (data.byteLength > this.quota.signalSize) {
441
684
  this.emit('over_size')
@@ -446,22 +689,10 @@ export class IOCore extends EventEmitter {
446
689
  this.socket_send(data);
447
690
  }
448
691
 
449
- /*
450
- Policy. Should message do encrypt?
451
-
452
- if encMode == auto
453
- NO. if connection using TLS line.
454
- // ex. wss://url connection.
455
- YES. if no TLS line.
456
- // ex. ws://url connection.
457
-
458
- if encMode == YES
459
- YES. encrypt the message.
460
-
461
- if encMode == NO
462
- NO. do not ecnrypt message.
463
-
464
- */
692
+ /**
693
+ * Determines if encryption should be used based on current mode and TLS status.
694
+ * @returns {boolean}
695
+ */
465
696
  getEncryptionMode() {
466
697
  if (this.encMode === ENC_MODE.YES ||
467
698
  this.encMode === ENC_MODE.AUTO &&
@@ -473,6 +704,11 @@ export class IOCore extends EventEmitter {
473
704
  }
474
705
  }
475
706
 
707
+ /**
708
+ * Sends data with encryption based on the encryption mode.
709
+ * @param {Buffer} data - The data buffer to send.
710
+ * @param {boolean} [useEncryption] - Optional. Force encryption or not. If undefined, uses default policy.
711
+ */
476
712
  send_enc_mode(data, useEncryption) {
477
713
 
478
714
  // use default policy.
@@ -501,6 +737,11 @@ export class IOCore extends EventEmitter {
501
737
  }
502
738
 
503
739
 
740
+ /**
741
+ * Sets a message promise for a given message ID.
742
+ * @param {number} mid - The message ID.
743
+ * @returns {Promise<any>}
744
+ */
504
745
  setMsgPromise(mid) {
505
746
  return new Promise((resolve, reject) => {
506
747
  this.promiseMap.set(mid, [resolve, reject])
@@ -513,6 +754,10 @@ export class IOCore extends EventEmitter {
513
754
  })
514
755
  }
515
756
 
757
+ /**
758
+ * Tests and resolves/rejects a promise based on the incoming buffer.
759
+ * @param {Buffer} buffer - The incoming data buffer.
760
+ */
516
761
  testPromise(buffer) {
517
762
 
518
763
  let res = MBP.unpack(buffer)
@@ -537,11 +782,21 @@ export class IOCore extends EventEmitter {
537
782
  }
538
783
 
539
784
 
785
+ /**
786
+ * Publishes a signal.
787
+ * @param {...any} args - Arguments for the signal.
788
+ */
540
789
  publish(...args) {
541
790
  this.signal(...args)
542
791
  }
543
792
 
544
793
 
794
+ /**
795
+ * Sends a signal with a tag and arguments.
796
+ * @param {string} tag - The signal tag.
797
+ * @param {...any} args - Arguments for the signal.
798
+ * @throws {TypeError} If tag is not a string.
799
+ */
545
800
  signal(tag, ...args) {
546
801
  if (typeof tag !== 'string') throw TypeError('tag should be string.')
547
802
 
@@ -549,10 +804,23 @@ export class IOCore extends EventEmitter {
549
804
  this.send_enc_mode(signalPack)
550
805
  }
551
806
 
807
+ /**
808
+ * Decrypts E2E data.
809
+ * @param {Buffer} data - The encrypted data.
810
+ * @param {string} key - The decryption key.
811
+ * @returns {Buffer}
812
+ */
552
813
  decrypt_e2e(data, key) {
553
814
  return this.boho.decrypt_e2e(data, key)
554
815
  }
555
816
 
817
+ /**
818
+ * Sends an E2E (End-to-End) encrypted signal.
819
+ * @param {string} tag - The signal tag.
820
+ * @param {Buffer} data - The data to encrypt and send.
821
+ * @param {string} key - The encryption key.
822
+ * @throws {TypeError} If tag is not a string.
823
+ */
556
824
  signal_e2e(tag, data, key) {
557
825
 
558
826
  if (typeof tag !== 'string') throw TypeError('tag should be string.')
@@ -576,6 +844,12 @@ export class IOCore extends EventEmitter {
576
844
 
577
845
 
578
846
 
847
+ /**
848
+ * Sets a value in the store.
849
+ * @param {string} storeName - The name of the store.
850
+ * @param {...any} args - Arguments to set.
851
+ * @returns {Promise<any>}
852
+ */
579
853
  set(storeName, ...args) {
580
854
  if (!storeName || args.length == 0) {
581
855
  return Promise.reject(new Error('set need storeName and value)'))
@@ -583,6 +857,11 @@ export class IOCore extends EventEmitter {
583
857
  return this.req('store', 'set', storeName, ...args)
584
858
  }
585
859
 
860
+ /**
861
+ * Gets a value from the store.
862
+ * @param {string} storeName - The name of the store.
863
+ * @returns {Promise<any>}
864
+ */
586
865
  async get(storeName) {
587
866
  if (!storeName) {
588
867
  return Promise.reject(new Error('store get need storeName)'))
@@ -593,6 +872,13 @@ export class IOCore extends EventEmitter {
593
872
  }
594
873
 
595
874
 
875
+ /**
876
+ * Sends a request to a target and topic.
877
+ * @param {string} target - The target of the request.
878
+ * @param {string} topic - The topic of the request.
879
+ * @param {...any} args - Optional arguments for the request.
880
+ * @returns {Promise<any>}
881
+ */
596
882
  req(target, topic, ...args) {
597
883
  if (!target || !topic)
598
884
  return Promise.reject(new Error('request need target and topic)'))
@@ -618,6 +904,11 @@ export class IOCore extends EventEmitter {
618
904
  }
619
905
 
620
906
 
907
+ /**
908
+ * Subscribes to a channel or channels.
909
+ * @param {string} tag - The tag(s) of the channel(s) to subscribe to (comma-separated).
910
+ * @throws {TypeError} If tag is not a string or exceeds length limit.
911
+ */
621
912
  subscribe(tag) {
622
913
  if (typeof tag !== 'string') throw TypeError('tag should be string.')
623
914
  if (this.state !== STATES.READY) return
@@ -637,6 +928,12 @@ export class IOCore extends EventEmitter {
637
928
  tagEncoded]))
638
929
  }
639
930
 
931
+ /**
932
+ * Subscribes to a channel or channels with a promise.
933
+ * @param {string} tag - The tag(s) of the channel(s) to subscribe to (comma-separated).
934
+ * @returns {Promise<any>}
935
+ * @throws {TypeError} If tag is not a string or exceeds length limit.
936
+ */
640
937
  subscribe_promise(tag) {
641
938
  if (typeof tag !== 'string') throw TypeError('tag should be string.')
642
939
  if (this.state !== STATES.READY) {
@@ -655,6 +952,9 @@ export class IOCore extends EventEmitter {
655
952
  return this.setMsgPromise(this.mid)
656
953
  }
657
954
 
955
+ /**
956
+ * Subscribes to channels stored in memory (local cache).
957
+ */
658
958
  subscribe_memory_channels() { //local cache . auto_resubscribe
659
959
  if (this.channels.size == 0) return
660
960
  let chList = Array.from(this.channels).join(',')
@@ -668,6 +968,11 @@ export class IOCore extends EventEmitter {
668
968
 
669
969
  }
670
970
 
971
+ /**
972
+ * Unsubscribes from a channel or channels.
973
+ * @param {string} [tag=""] - The tag(s) of the channel(s) to unsubscribe from (comma-separated). If empty, unsubscribes from all.
974
+ * @throws {TypeError} If tag is not a string or exceeds length limit.
975
+ */
671
976
  unsubscribe(tag = "") {
672
977
  if (typeof tag !== 'string') throw TypeError('tag should be string.')
673
978
 
@@ -690,6 +995,12 @@ export class IOCore extends EventEmitter {
690
995
  }
691
996
 
692
997
 
998
+ /**
999
+ * Listens for signals on a specific tag.
1000
+ * @param {string} tag - The tag to listen on.
1001
+ * @param {Function} handler - The callback function to handle the signal.
1002
+ * @throws {TypeError} If tag is not a string, handler is not a function, or tag length is invalid.
1003
+ */
693
1004
  listen(tag, handler) {
694
1005
  if (typeof tag !== 'string') throw TypeError('tag should be string.')
695
1006
  if (tag.length > 255 || tag.length == 0) throw TypeError('tag string length range: 1~255')
@@ -706,6 +1017,13 @@ export class IOCore extends EventEmitter {
706
1017
 
707
1018
 
708
1019
 
1020
+ /**
1021
+ * Links a local target to a remote tag and sets up a handler.
1022
+ * @param {string} to - The local link target.
1023
+ * @param {string} tag - The remote tag.
1024
+ * @param {Function} handler - The callback function to handle the signal.
1025
+ * @throws {TypeError} If 'to' or 'tag' are not strings, handler is not a function, or tag length is invalid.
1026
+ */
709
1027
  link(to, tag, handler) {
710
1028
  if (typeof to !== 'string') throw TypeError('to(local link target) is not a string.')
711
1029
  if (typeof tag !== 'string') throw TypeError('tag is not a string.')
@@ -731,44 +1049,54 @@ export class IOCore extends EventEmitter {
731
1049
  }
732
1050
 
733
1051
 
1052
+ /**
1053
+ * Unlinks a specific tag from a local target.
1054
+ * @param {string} to - The local link target.
1055
+ * @param {string} tag - The tag to unlink.
1056
+ * @throws {TypeError} If 'to' or 'tag' are not strings or tag length is invalid.
1057
+ */
734
1058
  unlink(to, tag) {
735
1059
  if (typeof to !== 'string') throw TypeError('to(local link target) is not a string.')
736
1060
  if (typeof tag !== 'string') throw TypeError('tag is not a string.')
737
1061
  if (tag.length > 255 || tag.length == 0) throw TypeError('tag string length range: 1~255')
738
1062
 
739
- if (!this.linkMap.has(to)) return;
1063
+ const linkSet = this.linkMap.get(to);
1064
+ if (!linkSet || !linkSet.has(tag)) return;
740
1065
 
741
- let linkSet = this.linkMap.get(to)
742
- let tags = Array.from(linkSet)
743
- for (let i = 0; i < tags.length; i++) {
744
- if (tags[i] == tag) {
745
- this.unsubscribe(tag)
746
- this.removeAllListeners(tag)
747
- linkSet.delete(tag)
748
- this.linkMap.set(to, linkSet)
749
- break;
750
- }
751
- }
1066
+ this.unsubscribe(tag);
1067
+ this.removeAllListeners(tag);
1068
+ linkSet.delete(tag);
752
1069
 
1070
+ if (linkSet.size === 0) {
1071
+ this.linkMap.delete(to);
1072
+ }
753
1073
  }
754
1074
 
1075
+ /**
1076
+ * Unlinks all tags from a local target.
1077
+ * @param {string} to - The local link target.
1078
+ * @throws {TypeError} If 'to' is not a string.
1079
+ */
755
1080
  unlinkAll(to) {
756
1081
  if (typeof to !== 'string') throw TypeError('to(local link target) is not a string.')
757
- if (!this.linkMap.has(to)) return;
758
-
759
- let linkSet = this.linkMap.get(to)
760
- let tags = Array.from(linkSet)
761
- for (let i = 0; i < tags.length; i++) {
762
- this.unsubscribe(tags[i])
763
- this.removeAllListeners(tags[i])
764
- linkSet.delete(tags[i])
1082
+
1083
+ const linkSet = this.linkMap.get(to);
1084
+ if (!linkSet) return;
1085
+
1086
+ for (const tag of linkSet) {
1087
+ this.unsubscribe(tag);
1088
+ this.removeAllListeners(tag);
765
1089
  }
766
- this.linkMap.delete(to)
767
1090
 
1091
+ this.linkMap.delete(to);
768
1092
  }
769
1093
 
770
1094
 
771
1095
 
1096
+ /**
1097
+ * Gets connection metrics.
1098
+ * @returns {{tx: number, rx: number, txb: number, rxb: number, last: number}}
1099
+ */
772
1100
  getMetric() {
773
1101
  return {
774
1102
  tx: this.txCounter,
@@ -780,10 +1108,18 @@ export class IOCore extends EventEmitter {
780
1108
 
781
1109
  }
782
1110
 
1111
+ /**
1112
+ * Gets the current connection state.
1113
+ * @returns {number}
1114
+ */
783
1115
  getState() {
784
1116
  return this.state
785
1117
  }
786
1118
 
1119
+ /**
1120
+ * Gets the current connection state name.
1121
+ * @returns {string}
1122
+ */
787
1123
  getStateName() {
788
1124
  //state <number>
789
1125
  //value of constant STATES.NAME < number >
@@ -792,6 +1128,10 @@ export class IOCore extends EventEmitter {
792
1128
  return (STATES[this.state]).toLowerCase()
793
1129
  }
794
1130
 
1131
+ /**
1132
+ * Gets security-related information.
1133
+ * @returns {{useAuth: boolean, isTLS: boolean, isAuthorized: boolean, encMode: number, usingEncryption: boolean}}
1134
+ */
795
1135
  getSecurity() {
796
1136
  return {
797
1137
  useAuth: this.useAuth,
@@ -802,6 +1142,11 @@ export class IOCore extends EventEmitter {
802
1142
  }
803
1143
  }
804
1144
 
1145
+ /**
1146
+ * Changes the connection state and emits events.
1147
+ * @param {string} state - The new state name (e.g., 'ready', 'closed').
1148
+ * @param {string} [emitEventAndMessage] - Optional message to emit with the state change event.
1149
+ */
805
1150
  stateChange(state, emitEventAndMessage) {
806
1151
  // STATES constant name : string upperCase
807
1152
  // eventName, .stateName : string lowerCase