iosignal 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.ko.md +566 -0
  2. package/README.md +561 -2
  3. package/dist/io.d.ts +481 -0
  4. package/dist/io.js +3 -3
  5. package/dist/io.js.map +1 -1
  6. package/dist/io.min.js +3 -3
  7. package/dist/io.min.js.map +1 -1
  8. package/dist/iosignal.js +558 -104
  9. package/dist/types/client/IOCore.d.ts +408 -0
  10. package/dist/types/client/browser/IOWebSocket.d.ts +75 -0
  11. package/dist/types/common/constants.d.ts +169 -0
  12. package/dist/types/common/payload.d.ts +6 -0
  13. package/dist/types/common/quotaTable.d.ts +47 -0
  14. package/examples/react-chat-js/dist/assets/index-Cy4qaVcJ.js +56 -0
  15. package/examples/react-chat-js/dist/assets/index-NETrjYhz.css +1 -0
  16. package/examples/react-chat-js/dist/index.html +14 -0
  17. package/examples/react-chat-js/index.html +13 -0
  18. package/examples/react-chat-js/package-lock.json +1723 -0
  19. package/examples/react-chat-js/package.json +20 -0
  20. package/examples/react-chat-js/src/App.css +61 -0
  21. package/examples/react-chat-js/src/App.jsx +125 -0
  22. package/examples/react-chat-js/src/index.css +13 -0
  23. package/examples/react-chat-js/src/main.jsx +13 -0
  24. package/examples/react-chat-js/src/shared_io.js +12 -0
  25. package/examples/react-chat-js/vite.config.js +7 -0
  26. package/examples/server/index.js +12 -0
  27. package/examples/server/package-lock.json +47 -0
  28. package/examples/server/package.json +12 -0
  29. package/examples/svelte-chat-js/.devtools +3 -0
  30. package/examples/svelte-chat-js/README.md +38 -0
  31. package/examples/svelte-chat-js/package-lock.json +1505 -0
  32. package/examples/svelte-chat-js/package.json +22 -0
  33. package/examples/svelte-chat-js/src/app.css +105 -0
  34. package/examples/svelte-chat-js/src/app.d.ts +13 -0
  35. package/examples/svelte-chat-js/src/app.html +12 -0
  36. package/examples/svelte-chat-js/src/lib/images/github.svg +1 -0
  37. package/examples/svelte-chat-js/src/lib/images/svelte-logo.svg +1 -0
  38. package/examples/svelte-chat-js/src/lib/images/svelte-welcome.png +0 -0
  39. package/examples/svelte-chat-js/src/lib/images/svelte-welcome.webp +0 -0
  40. package/examples/svelte-chat-js/src/lib/shared_io.js +12 -0
  41. package/examples/svelte-chat-js/src/routes/+layout.svelte +9 -0
  42. package/examples/svelte-chat-js/src/routes/+page.js +3 -0
  43. package/examples/svelte-chat-js/src/routes/+page.svelte +162 -0
  44. package/examples/svelte-chat-js/static/favicon.svg +1 -0
  45. package/examples/svelte-chat-js/static/global.css +19 -0
  46. package/examples/svelte-chat-js/static/robots.txt +3 -0
  47. package/examples/svelte-chat-js/svelte.config.js +15 -0
  48. package/examples/svelte-chat-js/vite.config.js +6 -0
  49. package/package.json +9 -3
  50. package/rollup.config.js +11 -4
  51. package/src/client/IOCongSocket.js +20 -9
  52. package/src/client/IOCore.js +437 -79
  53. package/src/client/IOWS.js +12 -15
  54. package/src/client/browser/IOWebSocket.js +101 -17
  55. package/src/common/constants.js +91 -1
  56. package/tsconfig.json +24 -0
  57. package/docs/README.kr.md +0 -230
@@ -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,292 @@ 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
+ // For WebSockets, readyState are: 0-CONNECTING, 1-OPEN, 2-CLOSING, 3-CLOSED
220
+ // Calling close() on a CONNECTING socket will cause a browser error.
221
+ if (this.socket.readyState === 0) { // 0 is WebSocket.CONNECTING
222
+ // To avoid the error, we wait for the connection to open, then immediately close it.
223
+ // We also clear other handlers to prevent any other logic from running.
224
+ const socket = this.socket;
225
+ socket.onopen = () => { if(socket) socket.close(); };
226
+ socket.onmessage = null;
227
+ socket.onerror = null;
228
+ socket.onclose = null;
229
+ } else {
230
+ try {
231
+ // For other sockets (like TCP) or other WebSocket states, close directly.
232
+ this.socket.close?.();
233
+ } catch {}
234
+ }
235
+ this.socket = null;
236
+ }
237
+ this.promiseMap.clear();
238
+ this.emit('closed');
239
+ this.stateChange('closed');
240
+ }
241
+
242
+ /**
243
+ * Disables auto-reconnect and closes the current connection.
244
+ * The instance can be re-opened manually later. For complete cleanup, use destroy().
245
+ */
246
+ stop() {
247
+ this.autoReconnect = false;
248
+ this.close();
249
+ }
250
+
251
+ /**
252
+ * Permanently destroys the instance, cleaning up all resources.
253
+ * The instance will not be usable after this.
254
+ */
255
+ destroy() {
256
+ this.stop();
257
+ this.removeAllListeners();
258
+
259
+ this.channels.clear();
260
+ this.linkMap.clear();
261
+
262
+ // Help GC
263
+ this.boho = null;
264
+ }
265
+
266
+ /**
267
+ * The core keep-alive logic. It checks if auto-reconnect is enabled.
268
+ * The actual check for the socket's state is implemented in the child classes.
269
+ */
270
+ keepAlive() {
271
+ if (!this.autoReconnect) return;
272
+ // The specific logic for checking the socket's state and reconnecting
273
+ // is implemented in the child classes (IOWS, IOCongSocket, etc.).
274
+ }
275
+
276
+ /**
277
+ * Redirects the connection to a new URL.
278
+ * @param {string} url2 - The new URL to redirect to.
279
+ */
62
280
  redirect(url2) {
63
281
  this.close()
64
282
  this.stateChange('redirecting')
65
283
  this.createConnection(url2)
66
284
  }
67
285
 
286
+ /**
287
+ * Opens the WebSocket connection.
288
+ * @param {string} [url] - Optional URL to connect to. If not provided, uses the instance's URL.
289
+ */
68
290
  open(url) {
69
- if (!url && !this.url) return;
291
+ // If a connection is already active or in progress, calling open() implies a reconnect.
292
+ // Close the existing socket first to ensure a clean state.
293
+ if (this.socket) {
294
+ this.close();
295
+ }
70
296
 
71
297
  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
- }
298
+ this.url = url;
81
299
  }
82
300
 
83
- this.createConnection(this.url)
301
+ if (!this.url) {
302
+ this.emit('error', new Error('URL is not set.'));
303
+ return;
304
+ }
305
+
306
+ // The actual connection is created here.
307
+ this.createConnection(this.url);
84
308
 
309
+ // Ensure the keep-alive timer is running.
85
310
  if (!this.connectionCheckerIntervalID) {
86
311
  this.connectionCheckerIntervalID = setInterval(this.keepAlive.bind(this), this.connectionCheckerPeriod);
87
312
  }
88
313
  }
89
314
 
315
+ /**
316
+ * Handles the 'open' event of the WebSocket. Resets the closed flag and sets the state to open.
317
+ */
90
318
  onOpen() {
319
+ this._closed = false;
91
320
  if (this.url.includes("wss://")) {
92
321
  this.TLS = true;
93
322
  } else {
@@ -96,39 +325,40 @@ export class IOCore extends EventEmitter {
96
325
  this.stateChange('open')
97
326
  }
98
327
 
328
+ /**
329
+ * Handles the 'close' event of the WebSocket.
330
+ */
99
331
  onClose() {
100
332
  this.boho.isAuthorized = false;
101
333
  this.cid = ""
102
334
  this.stateChange('closed')
103
335
  }
104
336
 
105
- // manual login
337
+ /**
338
+ * Manually logs in with provided ID and key.
339
+ * @param {string} id - The user ID.
340
+ * @param {string} key - The user key.
341
+ * @returns {boolean}
342
+ */
106
343
  login(id, key) {
107
- if (!id && !key) {
108
- console.log('no id and key.')
109
- return
110
- }
111
- console.log('manual login: ', id)
344
+ if (!this.auth(id, key)) return false;
112
345
 
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
346
  this.useAuth = true
123
347
  let auth_pack = this.boho.auth_req()
124
348
  this.send(auth_pack)
349
+ return true
125
350
  }
126
351
 
127
- // auto login
352
+ /**
353
+ * Sets up authentication for auto-login.
354
+ * @param {string} id - The user ID.
355
+ * @param {string} key - The user key.
356
+ * @returns {boolean}
357
+ */
128
358
  auth(id, key) {
129
359
  if (!id && !key) {
130
- console.log('no id and key.')
131
- return
360
+ this.emit('error', new Error('auth failed. no id and key.'))
361
+ return false
132
362
  }
133
363
 
134
364
  if (!key && id.includes('.')) {
@@ -137,12 +367,17 @@ export class IOCore extends EventEmitter {
137
367
  this.boho.set_id8(id)
138
368
  this.boho.set_key(key)
139
369
  } else {
140
- console.log('no id or key.')
141
- return
370
+ this.emit('error', new Error('auth failed. no id or key.'))
371
+ return false
142
372
  }
143
373
  this.useAuth = true
374
+ return true
144
375
  }
145
376
 
377
+ /**
378
+ * Handles incoming data from the WebSocket.
379
+ * @param {Buffer} buffer - The incoming data buffer.
380
+ */
146
381
  onData(buffer) {
147
382
  let msgType = buffer[0];
148
383
  let decoded;
@@ -163,8 +398,8 @@ export class IOCore extends EventEmitter {
163
398
  // console.log( 'ENC_E2E decoded ', decoded )
164
399
  msgType = decoded[0]
165
400
  // decoded has msg_header only.
166
- buffer.set(decoded, Boho.MetaSize.ENC_488) // set decoded signal_e2e headaer.
167
- buffer = buffer.subarray(Boho.MetaSize.ENC_488) // reset offset.
401
+ buffer.set(decoded, Boho.MetaSize.ENC_488) // set decoded signal_e2e headaer.
402
+ buffer = buffer.subarray(Boho.MetaSize.ENC_488) // reset offset.
168
403
  // console.log('DECODED MsgType:', IOMsg[ msgType ] )
169
404
  } else {
170
405
  // console.log('488 DEC_FAIL', buffer)
@@ -202,10 +437,8 @@ export class IOCore extends EventEmitter {
202
437
  if (jsonInfo.ip) {
203
438
  this.ip = jsonInfo.ip;
204
439
  }
205
- console.log('<IAM_RES>', JSON.stringify(jsonInfo))
206
- // console.log('<IAM_RES>', JSON.stringify(jsonInfo,null,2))
207
440
  } catch (error) {
208
- // console.log('<IAM_RES> data error')
441
+ this.emit('error', new Error('IAM_RES data error'))
209
442
  }
210
443
  break;
211
444
 
@@ -272,7 +505,7 @@ export class IOCore extends EventEmitter {
272
505
  }
273
506
 
274
507
  } catch (error) {
275
- // console.log('<SERVER_SIGNAL> parsing error')
508
+ this.emit('error', new Error('SERVER_SIGNAL parsing error'))
276
509
  }
277
510
  break;
278
511
 
@@ -283,7 +516,7 @@ export class IOCore extends EventEmitter {
283
516
  this.emit(setPack.topic, ...setPack.args)
284
517
  }
285
518
  } catch (error) {
286
- // console.log('<SET> parsing error')
519
+ this.emit('error', new Error('SET parsing error'))
287
520
  }
288
521
  break;
289
522
 
@@ -312,7 +545,10 @@ export class IOCore extends EventEmitter {
312
545
  case PAYLOAD_TYPE.TEXT:
313
546
  // !! Must remove null char before decode in JS.
314
547
  // string payload contains null char for the c/cpp devices.
315
- let payloadStringWithoutNull = payloadBuffer.subarray(0, payloadBuffer.byteLength - 1)
548
+ let payloadStringWithoutNull = payloadBuffer
549
+ if (payloadBuffer[payloadBuffer.byteLength - 1] === 0) {
550
+ payloadStringWithoutNull = payloadBuffer.subarray(0, payloadBuffer.byteLength - 1)
551
+ }
316
552
  let oneString = decoder.decode(payloadStringWithoutNull)
317
553
  if (tag.indexOf('@') === 0) this.emit('@', oneString, tag)
318
554
  if (tag !== '@') this.emit(tag, oneString, tag)
@@ -348,7 +584,7 @@ export class IOCore extends EventEmitter {
348
584
  }
349
585
 
350
586
  } catch (err) {
351
- // console.log('## signal parse err', err)
587
+ this.emit('error', new Error('signal parse err'))
352
588
  }
353
589
  break;
354
590
 
@@ -392,6 +628,10 @@ export class IOCore extends EventEmitter {
392
628
  }
393
629
  }
394
630
 
631
+ /**
632
+ * Sends an IAM (I Am) message to the server.
633
+ * @param {string} [title] - Optional title for the IAM message.
634
+ */
395
635
  iam(title) {
396
636
  // console.log('iam', title)
397
637
  if (title) {
@@ -407,17 +647,25 @@ export class IOCore extends EventEmitter {
407
647
  }
408
648
 
409
649
 
650
+ /**
651
+ * Sends a PING message to the server.
652
+ */
410
653
  ping() {
411
654
  this.send(Buffer.from([IOMsg.PING]))
412
655
  }
413
656
 
657
+ /**
658
+ * Sends a PONG message to the server.
659
+ */
414
660
  pong() {
415
661
  this.send(Buffer.from([IOMsg.PONG]))
416
662
  }
417
663
 
418
664
 
419
- // application level ping tool.
420
- // simple message sending and reply.
665
+ /**
666
+ * Sends an ECHO message to the server.
667
+ * @param {*} [args] - Optional arguments to echo.
668
+ */
421
669
  echo(args) {
422
670
  if (args) {
423
671
  console.log('echo args:', args)
@@ -432,10 +680,18 @@ export class IOCore extends EventEmitter {
432
680
  }
433
681
 
434
682
 
683
+ /**
684
+ * Sends binary data.
685
+ * @param {...any} data - Data to send.
686
+ */
435
687
  bin(...data) {
436
688
  this.send(MBP.U8pack(...data))
437
689
  }
438
690
 
691
+ /**
692
+ * Sends data over the WebSocket.
693
+ * @param {Buffer} data - The data buffer to send.
694
+ */
439
695
  send(data) {
440
696
  if (data.byteLength > this.quota.signalSize) {
441
697
  this.emit('over_size')
@@ -446,22 +702,10 @@ export class IOCore extends EventEmitter {
446
702
  this.socket_send(data);
447
703
  }
448
704
 
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
- */
705
+ /**
706
+ * Determines if encryption should be used based on current mode and TLS status.
707
+ * @returns {boolean}
708
+ */
465
709
  getEncryptionMode() {
466
710
  if (this.encMode === ENC_MODE.YES ||
467
711
  this.encMode === ENC_MODE.AUTO &&
@@ -473,6 +717,11 @@ export class IOCore extends EventEmitter {
473
717
  }
474
718
  }
475
719
 
720
+ /**
721
+ * Sends data with encryption based on the encryption mode.
722
+ * @param {Buffer} data - The data buffer to send.
723
+ * @param {boolean} [useEncryption] - Optional. Force encryption or not. If undefined, uses default policy.
724
+ */
476
725
  send_enc_mode(data, useEncryption) {
477
726
 
478
727
  // use default policy.
@@ -501,6 +750,11 @@ export class IOCore extends EventEmitter {
501
750
  }
502
751
 
503
752
 
753
+ /**
754
+ * Sets a message promise for a given message ID.
755
+ * @param {number} mid - The message ID.
756
+ * @returns {Promise<any>}
757
+ */
504
758
  setMsgPromise(mid) {
505
759
  return new Promise((resolve, reject) => {
506
760
  this.promiseMap.set(mid, [resolve, reject])
@@ -513,6 +767,10 @@ export class IOCore extends EventEmitter {
513
767
  })
514
768
  }
515
769
 
770
+ /**
771
+ * Tests and resolves/rejects a promise based on the incoming buffer.
772
+ * @param {Buffer} buffer - The incoming data buffer.
773
+ */
516
774
  testPromise(buffer) {
517
775
 
518
776
  let res = MBP.unpack(buffer)
@@ -537,11 +795,21 @@ export class IOCore extends EventEmitter {
537
795
  }
538
796
 
539
797
 
798
+ /**
799
+ * Publishes a signal.
800
+ * @param {...any} args - Arguments for the signal.
801
+ */
540
802
  publish(...args) {
541
803
  this.signal(...args)
542
804
  }
543
805
 
544
806
 
807
+ /**
808
+ * Sends a signal with a tag and arguments.
809
+ * @param {string} tag - The signal tag.
810
+ * @param {...any} args - Arguments for the signal.
811
+ * @throws {TypeError} If tag is not a string.
812
+ */
545
813
  signal(tag, ...args) {
546
814
  if (typeof tag !== 'string') throw TypeError('tag should be string.')
547
815
 
@@ -549,10 +817,23 @@ export class IOCore extends EventEmitter {
549
817
  this.send_enc_mode(signalPack)
550
818
  }
551
819
 
820
+ /**
821
+ * Decrypts E2E data.
822
+ * @param {Buffer} data - The encrypted data.
823
+ * @param {string} key - The decryption key.
824
+ * @returns {Buffer}
825
+ */
552
826
  decrypt_e2e(data, key) {
553
827
  return this.boho.decrypt_e2e(data, key)
554
828
  }
555
829
 
830
+ /**
831
+ * Sends an E2E (End-to-End) encrypted signal.
832
+ * @param {string} tag - The signal tag.
833
+ * @param {Buffer} data - The data to encrypt and send.
834
+ * @param {string} key - The encryption key.
835
+ * @throws {TypeError} If tag is not a string.
836
+ */
556
837
  signal_e2e(tag, data, key) {
557
838
 
558
839
  if (typeof tag !== 'string') throw TypeError('tag should be string.')
@@ -576,6 +857,12 @@ export class IOCore extends EventEmitter {
576
857
 
577
858
 
578
859
 
860
+ /**
861
+ * Sets a value in the store.
862
+ * @param {string} storeName - The name of the store.
863
+ * @param {...any} args - Arguments to set.
864
+ * @returns {Promise<any>}
865
+ */
579
866
  set(storeName, ...args) {
580
867
  if (!storeName || args.length == 0) {
581
868
  return Promise.reject(new Error('set need storeName and value)'))
@@ -583,6 +870,11 @@ export class IOCore extends EventEmitter {
583
870
  return this.req('store', 'set', storeName, ...args)
584
871
  }
585
872
 
873
+ /**
874
+ * Gets a value from the store.
875
+ * @param {string} storeName - The name of the store.
876
+ * @returns {Promise<any>}
877
+ */
586
878
  async get(storeName) {
587
879
  if (!storeName) {
588
880
  return Promise.reject(new Error('store get need storeName)'))
@@ -593,6 +885,13 @@ export class IOCore extends EventEmitter {
593
885
  }
594
886
 
595
887
 
888
+ /**
889
+ * Sends a request to a target and topic.
890
+ * @param {string} target - The target of the request.
891
+ * @param {string} topic - The topic of the request.
892
+ * @param {...any} args - Optional arguments for the request.
893
+ * @returns {Promise<any>}
894
+ */
596
895
  req(target, topic, ...args) {
597
896
  if (!target || !topic)
598
897
  return Promise.reject(new Error('request need target and topic)'))
@@ -618,6 +917,11 @@ export class IOCore extends EventEmitter {
618
917
  }
619
918
 
620
919
 
920
+ /**
921
+ * Subscribes to a channel or channels.
922
+ * @param {string} tag - The tag(s) of the channel(s) to subscribe to (comma-separated).
923
+ * @throws {TypeError} If tag is not a string or exceeds length limit.
924
+ */
621
925
  subscribe(tag) {
622
926
  if (typeof tag !== 'string') throw TypeError('tag should be string.')
623
927
  if (this.state !== STATES.READY) return
@@ -637,6 +941,12 @@ export class IOCore extends EventEmitter {
637
941
  tagEncoded]))
638
942
  }
639
943
 
944
+ /**
945
+ * Subscribes to a channel or channels with a promise.
946
+ * @param {string} tag - The tag(s) of the channel(s) to subscribe to (comma-separated).
947
+ * @returns {Promise<any>}
948
+ * @throws {TypeError} If tag is not a string or exceeds length limit.
949
+ */
640
950
  subscribe_promise(tag) {
641
951
  if (typeof tag !== 'string') throw TypeError('tag should be string.')
642
952
  if (this.state !== STATES.READY) {
@@ -655,6 +965,9 @@ export class IOCore extends EventEmitter {
655
965
  return this.setMsgPromise(this.mid)
656
966
  }
657
967
 
968
+ /**
969
+ * Subscribes to channels stored in memory (local cache).
970
+ */
658
971
  subscribe_memory_channels() { //local cache . auto_resubscribe
659
972
  if (this.channels.size == 0) return
660
973
  let chList = Array.from(this.channels).join(',')
@@ -668,6 +981,11 @@ export class IOCore extends EventEmitter {
668
981
 
669
982
  }
670
983
 
984
+ /**
985
+ * Unsubscribes from a channel or channels.
986
+ * @param {string} [tag=""] - The tag(s) of the channel(s) to unsubscribe from (comma-separated). If empty, unsubscribes from all.
987
+ * @throws {TypeError} If tag is not a string or exceeds length limit.
988
+ */
671
989
  unsubscribe(tag = "") {
672
990
  if (typeof tag !== 'string') throw TypeError('tag should be string.')
673
991
 
@@ -690,6 +1008,12 @@ export class IOCore extends EventEmitter {
690
1008
  }
691
1009
 
692
1010
 
1011
+ /**
1012
+ * Listens for signals on a specific tag.
1013
+ * @param {string} tag - The tag to listen on.
1014
+ * @param {Function} handler - The callback function to handle the signal.
1015
+ * @throws {TypeError} If tag is not a string, handler is not a function, or tag length is invalid.
1016
+ */
693
1017
  listen(tag, handler) {
694
1018
  if (typeof tag !== 'string') throw TypeError('tag should be string.')
695
1019
  if (tag.length > 255 || tag.length == 0) throw TypeError('tag string length range: 1~255')
@@ -706,6 +1030,13 @@ export class IOCore extends EventEmitter {
706
1030
 
707
1031
 
708
1032
 
1033
+ /**
1034
+ * Links a local target to a remote tag and sets up a handler.
1035
+ * @param {string} to - The local link target.
1036
+ * @param {string} tag - The remote tag.
1037
+ * @param {Function} handler - The callback function to handle the signal.
1038
+ * @throws {TypeError} If 'to' or 'tag' are not strings, handler is not a function, or tag length is invalid.
1039
+ */
709
1040
  link(to, tag, handler) {
710
1041
  if (typeof to !== 'string') throw TypeError('to(local link target) is not a string.')
711
1042
  if (typeof tag !== 'string') throw TypeError('tag is not a string.')
@@ -731,44 +1062,54 @@ export class IOCore extends EventEmitter {
731
1062
  }
732
1063
 
733
1064
 
1065
+ /**
1066
+ * Unlinks a specific tag from a local target.
1067
+ * @param {string} to - The local link target.
1068
+ * @param {string} tag - The tag to unlink.
1069
+ * @throws {TypeError} If 'to' or 'tag' are not strings or tag length is invalid.
1070
+ */
734
1071
  unlink(to, tag) {
735
1072
  if (typeof to !== 'string') throw TypeError('to(local link target) is not a string.')
736
1073
  if (typeof tag !== 'string') throw TypeError('tag is not a string.')
737
1074
  if (tag.length > 255 || tag.length == 0) throw TypeError('tag string length range: 1~255')
738
1075
 
739
- if (!this.linkMap.has(to)) return;
1076
+ const linkSet = this.linkMap.get(to);
1077
+ if (!linkSet || !linkSet.has(tag)) return;
740
1078
 
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
- }
1079
+ this.unsubscribe(tag);
1080
+ this.removeAllListeners(tag);
1081
+ linkSet.delete(tag);
752
1082
 
1083
+ if (linkSet.size === 0) {
1084
+ this.linkMap.delete(to);
1085
+ }
753
1086
  }
754
1087
 
1088
+ /**
1089
+ * Unlinks all tags from a local target.
1090
+ * @param {string} to - The local link target.
1091
+ * @throws {TypeError} If 'to' is not a string.
1092
+ */
755
1093
  unlinkAll(to) {
756
1094
  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])
1095
+
1096
+ const linkSet = this.linkMap.get(to);
1097
+ if (!linkSet) return;
1098
+
1099
+ for (const tag of linkSet) {
1100
+ this.unsubscribe(tag);
1101
+ this.removeAllListeners(tag);
765
1102
  }
766
- this.linkMap.delete(to)
767
1103
 
1104
+ this.linkMap.delete(to);
768
1105
  }
769
1106
 
770
1107
 
771
1108
 
1109
+ /**
1110
+ * Gets connection metrics.
1111
+ * @returns {{tx: number, rx: number, txb: number, rxb: number, last: number}}
1112
+ */
772
1113
  getMetric() {
773
1114
  return {
774
1115
  tx: this.txCounter,
@@ -780,10 +1121,18 @@ export class IOCore extends EventEmitter {
780
1121
 
781
1122
  }
782
1123
 
1124
+ /**
1125
+ * Gets the current connection state.
1126
+ * @returns {number}
1127
+ */
783
1128
  getState() {
784
1129
  return this.state
785
1130
  }
786
1131
 
1132
+ /**
1133
+ * Gets the current connection state name.
1134
+ * @returns {string}
1135
+ */
787
1136
  getStateName() {
788
1137
  //state <number>
789
1138
  //value of constant STATES.NAME < number >
@@ -792,6 +1141,10 @@ export class IOCore extends EventEmitter {
792
1141
  return (STATES[this.state]).toLowerCase()
793
1142
  }
794
1143
 
1144
+ /**
1145
+ * Gets security-related information.
1146
+ * @returns {{useAuth: boolean, isTLS: boolean, isAuthorized: boolean, encMode: number, usingEncryption: boolean}}
1147
+ */
795
1148
  getSecurity() {
796
1149
  return {
797
1150
  useAuth: this.useAuth,
@@ -802,6 +1155,11 @@ export class IOCore extends EventEmitter {
802
1155
  }
803
1156
  }
804
1157
 
1158
+ /**
1159
+ * Changes the connection state and emits events.
1160
+ * @param {string} state - The new state name (e.g., 'ready', 'closed').
1161
+ * @param {string} [emitEventAndMessage] - Optional message to emit with the state change event.
1162
+ */
805
1163
  stateChange(state, emitEventAndMessage) {
806
1164
  // STATES constant name : string upperCase
807
1165
  // eventName, .stateName : string lowerCase