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.
- package/README.md +248 -114
- package/dist/io.d.ts +481 -0
- package/dist/io.js +4 -5
- package/dist/io.js.map +1 -1
- package/dist/io.min.js +4 -5
- package/dist/io.min.js.map +1 -1
- package/dist/iosignal.js +9559 -0
- package/dist/types/client/IOCore.d.ts +408 -0
- package/dist/types/client/browser/IOWebSocket.d.ts +75 -0
- package/dist/types/common/constants.d.ts +169 -0
- package/dist/types/common/payload.d.ts +6 -0
- package/dist/types/common/quotaTable.d.ts +47 -0
- package/docs/README.ko.md +371 -0
- package/docs/iosignal_architecture.png +0 -0
- package/examples/react-chat-js/dist/assets/index-Cy4qaVcJ.js +56 -0
- package/examples/react-chat-js/dist/assets/index-NETrjYhz.css +1 -0
- package/examples/react-chat-js/dist/index.html +14 -0
- package/examples/react-chat-js/index.html +13 -0
- package/examples/react-chat-js/package-lock.json +1628 -0
- package/examples/react-chat-js/package.json +20 -0
- package/examples/react-chat-js/src/App.css +61 -0
- package/examples/react-chat-js/src/App.jsx +125 -0
- package/examples/react-chat-js/src/index.css +13 -0
- package/examples/react-chat-js/src/main.jsx +13 -0
- package/examples/react-chat-js/src/shared_io.js +12 -0
- package/examples/react-chat-js/vite.config.js +7 -0
- package/examples/server/index.js +12 -0
- package/examples/server/package.json +12 -0
- package/package.json +24 -12
- package/rollup.config.js +13 -8
- package/src/auth/Auth_File.js +1 -1
- package/src/client/IOCongSocket.js +20 -9
- package/src/client/IOCore.js +422 -77
- package/src/client/IOWS.js +12 -15
- package/src/client/browser/IOWebSocket.js +217 -0
- package/src/common/constants.js +91 -1
- package/test-nodejs/server-Auth_File.js +1 -1
- package/tsconfig.json +24 -0
- package/dist/iosignal.cjs +0 -23
- package/dist/iosignal.mjs +0 -23
- package/img/iosignal_stack.png +0 -0
- package/src/client/IOWebSocket.js +0 -118
- package/test-nodejs/client.cjs +0 -17
- package/test-nodejs/server-commonjs.cjs +0 -9
- /package/{auth_file.mjs → auth_file.js} +0 -0
- /package/test-nodejs/{client.mjs → client.js} +0 -0
- /package/test-nodejs/{client_api_reply.mjs → client_api_reply.js} +0 -0
- /package/test-nodejs/{server-esm.mjs → server.js} +0 -0
- /package/test-nodejs/{simple-server.mjs → simple-server.js} +0 -0
package/src/client/IOCore.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
420
|
-
|
|
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
|
-
|
|
451
|
-
|
|
452
|
-
|
|
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
|
-
|
|
1063
|
+
const linkSet = this.linkMap.get(to);
|
|
1064
|
+
if (!linkSet || !linkSet.has(tag)) return;
|
|
740
1065
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
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
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
for (
|
|
762
|
-
this.unsubscribe(
|
|
763
|
-
this.removeAllListeners(
|
|
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
|