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.
- package/README.ko.md +566 -0
- package/README.md +561 -2
- package/dist/io.d.ts +481 -0
- package/dist/io.js +3 -3
- package/dist/io.js.map +1 -1
- package/dist/io.min.js +3 -3
- package/dist/io.min.js.map +1 -1
- package/dist/iosignal.js +558 -104
- 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/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 +1723 -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-lock.json +47 -0
- package/examples/server/package.json +12 -0
- package/examples/svelte-chat-js/.devtools +3 -0
- package/examples/svelte-chat-js/README.md +38 -0
- package/examples/svelte-chat-js/package-lock.json +1505 -0
- package/examples/svelte-chat-js/package.json +22 -0
- package/examples/svelte-chat-js/src/app.css +105 -0
- package/examples/svelte-chat-js/src/app.d.ts +13 -0
- package/examples/svelte-chat-js/src/app.html +12 -0
- package/examples/svelte-chat-js/src/lib/images/github.svg +1 -0
- package/examples/svelte-chat-js/src/lib/images/svelte-logo.svg +1 -0
- package/examples/svelte-chat-js/src/lib/images/svelte-welcome.png +0 -0
- package/examples/svelte-chat-js/src/lib/images/svelte-welcome.webp +0 -0
- package/examples/svelte-chat-js/src/lib/shared_io.js +12 -0
- package/examples/svelte-chat-js/src/routes/+layout.svelte +9 -0
- package/examples/svelte-chat-js/src/routes/+page.js +3 -0
- package/examples/svelte-chat-js/src/routes/+page.svelte +162 -0
- package/examples/svelte-chat-js/static/favicon.svg +1 -0
- package/examples/svelte-chat-js/static/global.css +19 -0
- package/examples/svelte-chat-js/static/robots.txt +3 -0
- package/examples/svelte-chat-js/svelte.config.js +15 -0
- package/examples/svelte-chat-js/vite.config.js +6 -0
- package/package.json +9 -3
- package/rollup.config.js +11 -4
- package/src/client/IOCongSocket.js +20 -9
- package/src/client/IOCore.js +437 -79
- package/src/client/IOWS.js +12 -15
- package/src/client/browser/IOWebSocket.js +101 -17
- package/src/common/constants.js +91 -1
- package/tsconfig.json +24 -0
- package/docs/README.kr.md +0 -230
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,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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
420
|
-
|
|
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
|
-
|
|
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
|
-
*/
|
|
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
|
-
|
|
1076
|
+
const linkSet = this.linkMap.get(to);
|
|
1077
|
+
if (!linkSet || !linkSet.has(tag)) return;
|
|
740
1078
|
|
|
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
|
-
}
|
|
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
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
for (
|
|
762
|
-
this.unsubscribe(
|
|
763
|
-
this.removeAllListeners(
|
|
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
|