iosignal 1.4.2 → 1.6.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.md +247 -240
- package/auth_file.json +13 -13
- package/auth_file.mjs +23 -23
- package/dist/iosignal.cjs +1 -1
- package/dist/iosignal.esm.js +2 -2
- package/dist/iosignal.esm.js.map +1 -1
- package/dist/iosignal.min.js +2 -2
- package/dist/iosignal.min.js.map +1 -1
- package/index.js +19 -19
- package/indexWebBrowser.js +13 -13
- package/package.json +59 -60
- package/rollup.config.js +69 -69
- package/src/api/RedisAPI.js +68 -68
- package/src/api/api_constant.js +7 -7
- package/src/api/api_reply.js +34 -34
- package/src/api/api_sudo.js +96 -96
- package/src/auth/Auth_Env.js +78 -78
- package/src/auth/Auth_File.js +75 -75
- package/src/auth/Auth_Redis.js +50 -50
- package/src/auth/BohoAuth.js +168 -168
- package/src/client/CongPacket.js +150 -150
- package/src/client/IOCongSocket.js +88 -88
- package/src/client/IOCore.js +851 -851
- package/src/client/IOWS.js +81 -81
- package/src/client/IOWebSocket.js +107 -95
- package/src/common/constants.js +159 -159
- package/src/common/payload.js +82 -82
- package/src/common/quotaTable.js +68 -68
- package/src/common/util.js +61 -61
- package/src/server/FileLogger.js +30 -30
- package/src/server/Manager.js +449 -449
- package/src/server/Metric.js +154 -154
- package/src/server/Server.js +166 -148
- package/src/server/ServerRemote.js +227 -227
- package/src/server/ServerRemoteCore.js +467 -467
- package/src/server/serverOption.js +56 -55
- package/test-browser/favicon.ico +0 -0
- package/test-browser/index.html +92 -92
- package/test-browser/server.js +11 -11
- package/test-client-nodejs/client.cjs +17 -17
- package/test-client-nodejs/client.mjs +17 -17
- package/test-client-nodejs/client_api_reply.mjs +15 -15
- package/test-client-nodejs/stateChange.mjs +19 -19
- package/test-server/server-Auth_Env.js +19 -19
- package/test-server/server-Auth_File.js +12 -12
- package/test-server/server-api_reply.js +6 -6
- package/test-server/server-api_sudo.js +7 -7
- package/test-server/server-commonjs.cjs +8 -8
- package/test-server/server-esm.mjs +19 -19
- package/test-server/simple-server.mjs +11 -11
- package/test-signal-payloadType/signalPack.js +34 -34
- package/test-signal-payloadType/signaling.js +60 -60
package/src/client/IOCore.js
CHANGED
|
@@ -1,851 +1,851 @@
|
|
|
1
|
-
import { MBP, Buffer } from 'meta-buffer-pack'
|
|
2
|
-
import EventEmitter from "eventemitter3";
|
|
3
|
-
import { Boho, BohoMsg, MetaSize } from "boho";
|
|
4
|
-
import { IOMsg, PAYLOAD_TYPE, SIZE_LIMIT, ENC_MODE, STATES } from '../common/constants.js'
|
|
5
|
-
import { quotaTable } from '../common/quotaTable.js'
|
|
6
|
-
import { getSignalPack } from '../common/payload.js';
|
|
7
|
-
|
|
8
|
-
const encoder = new TextEncoder()
|
|
9
|
-
const decoder = new TextDecoder()
|
|
10
|
-
|
|
11
|
-
function byteToUrl(buffer) {
|
|
12
|
-
//ipv4(4bytes) , port(2bytes)
|
|
13
|
-
if (buffer.byteLength != 6) return
|
|
14
|
-
let address = buffer[0].toString() + "." + buffer[1].toString()
|
|
15
|
-
+ "." + buffer[2].toString() + "." + buffer[3].toString();
|
|
16
|
-
let port = (buffer[4] << 8) + buffer[5]
|
|
17
|
-
return address + ':' + port.toString()
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export class IOCore extends EventEmitter {
|
|
21
|
-
constructor(url) {
|
|
22
|
-
super();
|
|
23
|
-
this.cid = "" // get from the server CID_RES
|
|
24
|
-
this.ip = "" // get from the server IAM_RES message.
|
|
25
|
-
this.socket = null;
|
|
26
|
-
this.url = url; // init default server url
|
|
27
|
-
this.state = STATES.CLOSED; // Number type
|
|
28
|
-
this.stateName = this.getStateName() // String type
|
|
29
|
-
|
|
30
|
-
this.txCounter = 0;
|
|
31
|
-
this.rxCounter = 0;
|
|
32
|
-
this.txBytes = 0;
|
|
33
|
-
this.rxBytes = 0;
|
|
34
|
-
|
|
35
|
-
this.lastTxRxTime = Date.now();
|
|
36
|
-
this.connectionCheckerPeriod = SIZE_LIMIT.CONNECTION_CHECKER_PERIOD;
|
|
37
|
-
this.connectionCheckerIntervalID = null;
|
|
38
|
-
|
|
39
|
-
this.boho = new Boho()
|
|
40
|
-
this.TLS = false // true if protocol is wss(TLS)
|
|
41
|
-
this.encMode = ENC_MODE.AUTO;
|
|
42
|
-
this.useAuth = false;
|
|
43
|
-
|
|
44
|
-
this.nick = "";
|
|
45
|
-
this.channels = new Set()
|
|
46
|
-
this.promiseMap = new Map()
|
|
47
|
-
this.promiseTimeOut = SIZE_LIMIT.PROMISE_TIMEOUT
|
|
48
|
-
this.mid = 0 // promise message id
|
|
49
|
-
|
|
50
|
-
this.level = 3; // also defaultQuotaLevel
|
|
51
|
-
this.quota = quotaTable[this.level];
|
|
52
|
-
this.serverSet = {}
|
|
53
|
-
this.linkMap = new Map()
|
|
54
|
-
|
|
55
|
-
this.on('open', this.onOpen.bind(this))
|
|
56
|
-
this.on('close', this.onClose.bind(this))
|
|
57
|
-
this.on('socket_data', this.onData.bind(this))
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
redirect(url2) {
|
|
62
|
-
this.close()
|
|
63
|
-
this.stateChange('redirecting')
|
|
64
|
-
this.createConnection(url2)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
open(url) {
|
|
68
|
-
if (!url && !this.url) return;
|
|
69
|
-
|
|
70
|
-
if (url) {
|
|
71
|
-
if (!this.url) { // default host url
|
|
72
|
-
this.url = url
|
|
73
|
-
} else if (url !== this.url) { // default host url change
|
|
74
|
-
this.url = url;
|
|
75
|
-
if (this.socket) {
|
|
76
|
-
this.close()
|
|
77
|
-
return
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
this.createConnection(this.url)
|
|
83
|
-
|
|
84
|
-
if (!this.connectionCheckerIntervalID) {
|
|
85
|
-
this.connectionCheckerIntervalID = setInterval(this.keepAlive.bind(this), this.connectionCheckerPeriod);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
onOpen() {
|
|
90
|
-
if (this.url.includes("wss://")) {
|
|
91
|
-
this.TLS = true;
|
|
92
|
-
} else {
|
|
93
|
-
this.TLS = false;
|
|
94
|
-
}
|
|
95
|
-
this.stateChange('open')
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
onClose() {
|
|
99
|
-
this.boho.isAuthorized = false;
|
|
100
|
-
this.cid = ""
|
|
101
|
-
this.stateChange('closed')
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// manual login
|
|
105
|
-
login(id, key) {
|
|
106
|
-
if (!id && !key) {
|
|
107
|
-
console.log('no id and key.')
|
|
108
|
-
return
|
|
109
|
-
}
|
|
110
|
-
console.log('manual login: ', id)
|
|
111
|
-
|
|
112
|
-
if (!key && id.includes('.')) {
|
|
113
|
-
this.boho.set_id_key(id)
|
|
114
|
-
} else if (id && key) {
|
|
115
|
-
this.boho.set_id8(id)
|
|
116
|
-
this.boho.set_key(key)
|
|
117
|
-
} else {
|
|
118
|
-
console.log('no id or key.')
|
|
119
|
-
return
|
|
120
|
-
}
|
|
121
|
-
this.useAuth = true
|
|
122
|
-
let auth_pack = this.boho.auth_req()
|
|
123
|
-
// console.log('auth_req_pack', auth_pack )
|
|
124
|
-
this.send(auth_pack)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// auto login
|
|
128
|
-
auth(id, key) {
|
|
129
|
-
if (!id && !key) {
|
|
130
|
-
console.log('no id and key.')
|
|
131
|
-
return
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (!key && id.includes('.')) {
|
|
135
|
-
this.boho.set_id_key(id)
|
|
136
|
-
} else if (id && key) {
|
|
137
|
-
this.boho.set_id8(id)
|
|
138
|
-
this.boho.set_key(key)
|
|
139
|
-
} else {
|
|
140
|
-
console.log('no id or key.')
|
|
141
|
-
return
|
|
142
|
-
}
|
|
143
|
-
this.useAuth = true
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
onData(buffer) {
|
|
147
|
-
// console.log('remote rcv socket_message', buffer )
|
|
148
|
-
//check first byte (remote message type)
|
|
149
|
-
let msgType = buffer[0];
|
|
150
|
-
let decoded;
|
|
151
|
-
|
|
152
|
-
if (msgType === BohoMsg.ENC_488) {
|
|
153
|
-
decoded = this.boho.decrypt_488(buffer)
|
|
154
|
-
if (decoded) {
|
|
155
|
-
// console.log( decoded )
|
|
156
|
-
msgType = decoded[0]
|
|
157
|
-
buffer = decoded
|
|
158
|
-
// console.log('DECODED MsgType:', IOMsg[ msgType ] )
|
|
159
|
-
} else {
|
|
160
|
-
// console.log('DEC_FAIL', buffer.byteLength)
|
|
161
|
-
}
|
|
162
|
-
} else if (msgType === BohoMsg.ENC_E2E) {
|
|
163
|
-
// console.log('rcv ENC_E2E' )
|
|
164
|
-
|
|
165
|
-
try {
|
|
166
|
-
decoded = this.boho.decrypt_488(buffer)
|
|
167
|
-
//헤더를 읽고 헤더크기만큼만 해석한다.
|
|
168
|
-
if (decoded) {
|
|
169
|
-
// console.log( 'ENC_E2E decoded ', decoded )
|
|
170
|
-
msgType = decoded[0]
|
|
171
|
-
// decoded has msg_header only.
|
|
172
|
-
buffer.set(decoded, MetaSize.ENC_488) // set decoded signal_e2e headaer.
|
|
173
|
-
buffer = buffer.subarray(MetaSize.ENC_488) // reset offset.
|
|
174
|
-
// console.log('DECODED MsgType:', IOMsg[ msgType ] )
|
|
175
|
-
} else {
|
|
176
|
-
// console.log('488 DEC_FAIL', buffer)
|
|
177
|
-
return
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
} catch (err) {
|
|
181
|
-
// console.log('E2E DEC_FAIL decryption error', err)
|
|
182
|
-
return
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
let type = IOMsg[msgType]
|
|
188
|
-
if (!type) type = BohoMsg[msgType]
|
|
189
|
-
|
|
190
|
-
// console.log( "MsgType: ", type , " LEN ", buffer.byteLength)
|
|
191
|
-
|
|
192
|
-
switch (msgType) {
|
|
193
|
-
case IOMsg.OVER_SIZE:
|
|
194
|
-
console.log('## server sent: over_size event.')
|
|
195
|
-
this.emit('over_size', 'over_size')
|
|
196
|
-
break;
|
|
197
|
-
case IOMsg.PING:
|
|
198
|
-
this.pong();
|
|
199
|
-
break;
|
|
200
|
-
|
|
201
|
-
case IOMsg.PONG:
|
|
202
|
-
break;
|
|
203
|
-
|
|
204
|
-
case IOMsg.IAM_RES:
|
|
205
|
-
try {
|
|
206
|
-
let str = decoder.decode(buffer.subarray(1))
|
|
207
|
-
let jsonInfo = JSON.parse(str)
|
|
208
|
-
if (jsonInfo.ip) {
|
|
209
|
-
this.ip = jsonInfo.ip;
|
|
210
|
-
}
|
|
211
|
-
console.log('<IAM_RES>', JSON.stringify(jsonInfo))
|
|
212
|
-
// console.log('<IAM_RES>', JSON.stringify(jsonInfo,null,2))
|
|
213
|
-
} catch (error) {
|
|
214
|
-
// console.log('<IAM_RES> data error')
|
|
215
|
-
}
|
|
216
|
-
break;
|
|
217
|
-
|
|
218
|
-
case IOMsg.CID_RES:
|
|
219
|
-
let cidStr = decoder.decode(buffer.subarray(1))
|
|
220
|
-
// console.log( '>> CID_RES: ' ,cidStr )
|
|
221
|
-
this.cid = cidStr;
|
|
222
|
-
// change state before subscribe.
|
|
223
|
-
this.stateChange('ready', 'cid_ready')
|
|
224
|
-
this.subscribe_memory_channels()
|
|
225
|
-
break;
|
|
226
|
-
|
|
227
|
-
case IOMsg.QUOTA_LEVEL:
|
|
228
|
-
let quotaLevel = buffer[1]
|
|
229
|
-
this.level = quotaLevel;
|
|
230
|
-
this.quota = quotaTable[quotaLevel];
|
|
231
|
-
console.log('## QUOTA:', quotaLevel, JSON.stringify(this.quota))
|
|
232
|
-
break;
|
|
233
|
-
|
|
234
|
-
case IOMsg.SERVER_CLEAR_AUTH:
|
|
235
|
-
this.useAuth = false;
|
|
236
|
-
this.boho.clearAuth();
|
|
237
|
-
this.stop();
|
|
238
|
-
break;
|
|
239
|
-
|
|
240
|
-
case IOMsg.SERVER_REDIRECT:
|
|
241
|
-
let host_port;
|
|
242
|
-
let url;
|
|
243
|
-
let protocol;
|
|
244
|
-
let addressType;
|
|
245
|
-
if (buffer.byteLength == 7) { // ipv4 ,port
|
|
246
|
-
addressType = 'IPV4:PORT'
|
|
247
|
-
host_port = byteToUrl(buffer.subarray(1))
|
|
248
|
-
protocol = 'cong://'
|
|
249
|
-
} else { // domain url
|
|
250
|
-
addressType = 'URL'
|
|
251
|
-
host_port = decoder.decode(buffer.subarray(1))
|
|
252
|
-
protocol = ''
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
url = protocol + host_port
|
|
256
|
-
this.redirect(url)
|
|
257
|
-
break;
|
|
258
|
-
|
|
259
|
-
case IOMsg.SERVER_READY:
|
|
260
|
-
this.stateChange('server_ready', 'server_ready')
|
|
261
|
-
if (this.useAuth) {
|
|
262
|
-
this.send(this.boho.auth_req())
|
|
263
|
-
// CID_REQ will be called, after auth_ack.
|
|
264
|
-
} else {
|
|
265
|
-
// CID_REQ here, if not using auth.
|
|
266
|
-
this.send(Buffer.from([IOMsg.CID_REQ]))
|
|
267
|
-
}
|
|
268
|
-
break;
|
|
269
|
-
|
|
270
|
-
case IOMsg.SERVER_SIGNAL:
|
|
271
|
-
try {
|
|
272
|
-
let str = decoder.decode(buffer.subarray(1))
|
|
273
|
-
let ss = JSON.parse(str)
|
|
274
|
-
// console.log('SERVER_SIGNAL', JSON.stringify(ss))
|
|
275
|
-
|
|
276
|
-
if (ss.event && ss.data) {
|
|
277
|
-
this.serverSet = ss.data;
|
|
278
|
-
this.emit(ss.event, ss.data)
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
} catch (error) {
|
|
282
|
-
// console.log('<SERVER_SIGNAL> parsing error')
|
|
283
|
-
}
|
|
284
|
-
break;
|
|
285
|
-
|
|
286
|
-
case IOMsg.SET:
|
|
287
|
-
try {
|
|
288
|
-
let setPack = MBP.unpack(buffer)
|
|
289
|
-
if (setPack) {
|
|
290
|
-
// console.log('[SET] topic: ', setPack.topic)
|
|
291
|
-
this.emit(setPack.topic, ...setPack.args)
|
|
292
|
-
}
|
|
293
|
-
} catch (error) {
|
|
294
|
-
// console.log('<SET> parsing error')
|
|
295
|
-
}
|
|
296
|
-
break;
|
|
297
|
-
|
|
298
|
-
case IOMsg.SIGNAL_E2E:
|
|
299
|
-
case IOMsg.SIGNAL:
|
|
300
|
-
try {
|
|
301
|
-
let tagLen = buffer.readUint8(1)
|
|
302
|
-
let tagBuf = buffer.subarray(2, 2 + tagLen)
|
|
303
|
-
let tag = decoder.decode(tagBuf)
|
|
304
|
-
|
|
305
|
-
let payloadType = buffer.readUint8(2 + tagLen)
|
|
306
|
-
let payloadBuffer = buffer.subarray(3 + tagLen)
|
|
307
|
-
|
|
308
|
-
/* three types of signal message.
|
|
309
|
-
> unicast message to me: tag includes @, no cid: '@*'
|
|
310
|
-
> cid_sub message: tag includes cid and @ both : 'cid@*'
|
|
311
|
-
> ch_sub message: else.
|
|
312
|
-
*/
|
|
313
|
-
switch (payloadType) {
|
|
314
|
-
|
|
315
|
-
case PAYLOAD_TYPE.EMPTY: // 0
|
|
316
|
-
if (tag.indexOf('@') === 0) this.emit('@', null, tag)
|
|
317
|
-
else this.emit(tag, null, tag)
|
|
318
|
-
break;
|
|
319
|
-
|
|
320
|
-
case PAYLOAD_TYPE.TEXT: // 1
|
|
321
|
-
// !! Must remove null char before decode in JS.
|
|
322
|
-
// string payload contains null char for the c/cpp devices.
|
|
323
|
-
let payloadStringWithoutNull = payloadBuffer.subarray(0, payloadBuffer.byteLength - 1)
|
|
324
|
-
let oneString = decoder.decode(payloadStringWithoutNull)
|
|
325
|
-
if (tag.indexOf('@') === 0) this.emit('@', oneString, tag)
|
|
326
|
-
if (tag !== '@') this.emit(tag, oneString, tag)
|
|
327
|
-
break;
|
|
328
|
-
|
|
329
|
-
case PAYLOAD_TYPE.BINARY: // 2
|
|
330
|
-
if (tag.indexOf('@') === 0) this.emit('@', payloadBuffer, tag)
|
|
331
|
-
if (tag !== '@') this.emit(tag, payloadBuffer, tag)
|
|
332
|
-
break;
|
|
333
|
-
|
|
334
|
-
case PAYLOAD_TYPE.OBJECT:
|
|
335
|
-
let oneObjectBuffer = decoder.decode(payloadBuffer)
|
|
336
|
-
let oneJSONObject = JSON.parse(oneObjectBuffer)
|
|
337
|
-
if (tag.indexOf('@') === 0) this.emit('@', oneJSONObject, tag)
|
|
338
|
-
if (tag !== '@') this.emit(tag, oneJSONObject, tag)
|
|
339
|
-
break;
|
|
340
|
-
|
|
341
|
-
case PAYLOAD_TYPE.MJSON:
|
|
342
|
-
let mjsonBuffer = decoder.decode(payloadBuffer)
|
|
343
|
-
let mjson = JSON.parse(mjsonBuffer)
|
|
344
|
-
if (tag.indexOf('@') === 0) this.emit('@', ...mjson, tag)
|
|
345
|
-
if (tag !== '@') this.emit(tag, ...mjson, tag)
|
|
346
|
-
break;
|
|
347
|
-
|
|
348
|
-
case PAYLOAD_TYPE.MBA:
|
|
349
|
-
let mbaObject = MBP.unpack(payloadBuffer)
|
|
350
|
-
if (tag.indexOf('@') === 0) this.emit('@', ...mbaObject.args, tag)
|
|
351
|
-
if (tag !== '@') this.emit(tag, ...mbaObject.args, tag)
|
|
352
|
-
break;
|
|
353
|
-
|
|
354
|
-
default:
|
|
355
|
-
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
} catch (err) {
|
|
359
|
-
// console.log('## signal parse err', err)
|
|
360
|
-
}
|
|
361
|
-
break;
|
|
362
|
-
|
|
363
|
-
case IOMsg.RESPONSE_MBP:
|
|
364
|
-
this.testPromise(buffer)
|
|
365
|
-
break;
|
|
366
|
-
|
|
367
|
-
case BohoMsg.AUTH_NONCE:
|
|
368
|
-
let auth_hmac = this.boho.auth_hmac(buffer)
|
|
369
|
-
if (auth_hmac) {
|
|
370
|
-
this.send(auth_hmac)
|
|
371
|
-
} else {
|
|
372
|
-
this.stateChange('auth_fail', 'Invalid local auth_hmac.')
|
|
373
|
-
}
|
|
374
|
-
break;
|
|
375
|
-
|
|
376
|
-
case BohoMsg.AUTH_FAIL:
|
|
377
|
-
this.stateChange('auth_fail', 'server reject auth.')
|
|
378
|
-
break;
|
|
379
|
-
|
|
380
|
-
case BohoMsg.AUTH_ACK:
|
|
381
|
-
if (this.boho.check_auth_ack_hmac(buffer)) {
|
|
382
|
-
this.stateChange('auth_ready', 'server sent auth_ack')
|
|
383
|
-
this.send(Buffer.from([IOMsg.CID_REQ]))
|
|
384
|
-
} else {
|
|
385
|
-
this.stateChange('auth_fail', 'invalid server_hmac')
|
|
386
|
-
}
|
|
387
|
-
break;
|
|
388
|
-
|
|
389
|
-
default:
|
|
390
|
-
try {
|
|
391
|
-
decoded = decoder.decode(buffer)
|
|
392
|
-
// console.log('text message:', decoded)
|
|
393
|
-
this.emit('text_message', decoded)
|
|
394
|
-
} catch (error) {
|
|
395
|
-
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
break;
|
|
399
|
-
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
iam(title) {
|
|
404
|
-
// console.log('iam', title)
|
|
405
|
-
if (title) {
|
|
406
|
-
this.send_enc_mode(MBP.pack(
|
|
407
|
-
MBP.MB('#MsgType', '8', IOMsg.IAM),
|
|
408
|
-
MBP.MB('#', title)
|
|
409
|
-
))
|
|
410
|
-
} else {
|
|
411
|
-
this.send_enc_mode(MBP.pack(
|
|
412
|
-
MBP.MB('#MsgType', '8', IOMsg.IAM)
|
|
413
|
-
))
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
ping() {
|
|
419
|
-
this.send(Buffer.from([IOMsg.PING]))
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
pong() {
|
|
423
|
-
this.send(Buffer.from([IOMsg.PONG]))
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
// application level ping tool.
|
|
428
|
-
// simple message sending and reply.
|
|
429
|
-
echo(args) {
|
|
430
|
-
if (args) {
|
|
431
|
-
console.log('echo args:', args)
|
|
432
|
-
this.send_enc_mode(MBP.pack(
|
|
433
|
-
MBP.MB('#MsgType', '8', IOMsg.ECHO),
|
|
434
|
-
MBP.MB('#msg', args)
|
|
435
|
-
))
|
|
436
|
-
} else {
|
|
437
|
-
// # do not encrypt blank echo #
|
|
438
|
-
this.send(Buffer.from([IOMsg.ECHO]))
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
bin(...data) {
|
|
444
|
-
this.send(MBP.U8pack(...data))
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
send(data) {
|
|
448
|
-
if (data.byteLength > this.quota.signalSize) {
|
|
449
|
-
this.emit('over_size')
|
|
450
|
-
console.log('## QUOTA LIMIT OVER!! \nsignal message.byteLength: ', data.byteLength)
|
|
451
|
-
console.log('## your maximum signalSize(bytes) is:', this.quota.signalSize)
|
|
452
|
-
return
|
|
453
|
-
}
|
|
454
|
-
this.socket_send(data);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
/*
|
|
458
|
-
Policy. Should message do encrypt?
|
|
459
|
-
|
|
460
|
-
if encMode == auto
|
|
461
|
-
NO. if connection using TLS line.
|
|
462
|
-
// ex. wss://url connection.
|
|
463
|
-
YES. if no TLS line.
|
|
464
|
-
// ex. ws://url connection.
|
|
465
|
-
|
|
466
|
-
if encMode == YES
|
|
467
|
-
YES. encrypt the message.
|
|
468
|
-
|
|
469
|
-
if encMode == NO
|
|
470
|
-
NO. do not ecnrypt message.
|
|
471
|
-
|
|
472
|
-
*/
|
|
473
|
-
getEncryptionMode() {
|
|
474
|
-
if (this.encMode === ENC_MODE.YES ||
|
|
475
|
-
this.encMode === ENC_MODE.AUTO &&
|
|
476
|
-
!this.TLS && this.boho.isAuthorized
|
|
477
|
-
) {
|
|
478
|
-
return true;
|
|
479
|
-
} else {
|
|
480
|
-
return false
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
send_enc_mode(data, useEncryption) {
|
|
485
|
-
|
|
486
|
-
// use default policy.
|
|
487
|
-
if (useEncryption === undefined) {
|
|
488
|
-
useEncryption = this.getEncryptionMode()
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
if (data[0] == IOMsg.SIGNAL_E2E && useEncryption) {
|
|
492
|
-
// input data: signal_header + e2ePayload
|
|
493
|
-
// encrypt signal_header area only. payload is encrypted with e2e key already.
|
|
494
|
-
let tagLen = data[1]
|
|
495
|
-
let encHeader = this.boho.encrypt_488(data.subarray(0, 3 + tagLen))
|
|
496
|
-
encHeader[0] = BohoMsg.ENC_E2E
|
|
497
|
-
this.send(Buffer.concat([encHeader, data.subarray(3 + tagLen)]))
|
|
498
|
-
// console.log('<< send_enc_mode [ ENC_E2E ]')
|
|
499
|
-
|
|
500
|
-
} else if (useEncryption) {
|
|
501
|
-
// console.log('<< send_enc_mode [ ENC_488 ]')
|
|
502
|
-
let encPack = this.boho.encrypt_488(data)
|
|
503
|
-
this.send(encPack)
|
|
504
|
-
} else {
|
|
505
|
-
// console.log('<< send_enc_mode [ PLAIN ]' )
|
|
506
|
-
this.send(data)
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
setMsgPromise(mid) {
|
|
513
|
-
return new Promise((resolve, reject) => {
|
|
514
|
-
this.promiseMap.set(mid, [resolve, reject])
|
|
515
|
-
// console.log('set promise. mid, size', mid, this.promiseMap.size)
|
|
516
|
-
setTimeout(e => {
|
|
517
|
-
if (this.promiseMap.has(mid)) {
|
|
518
|
-
reject('timeout');
|
|
519
|
-
this.promiseMap.delete(mid)
|
|
520
|
-
// console.log('promise timeout. mid, size:', mid, this.promiseMap.size)
|
|
521
|
-
}
|
|
522
|
-
}, this.promiseTimeOut);
|
|
523
|
-
})
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
testPromise(buffer) {
|
|
527
|
-
// console.log('mbp buffer : ', buffer , buffer.byteLength)
|
|
528
|
-
// let mbp = ( buffer.byteLength > 4 ) ? buffer.subarray(4) : ""
|
|
529
|
-
|
|
530
|
-
let res = MBP.unpack(buffer)
|
|
531
|
-
if (!res) return
|
|
532
|
-
// console.log( res )
|
|
533
|
-
|
|
534
|
-
// console.log(`RESPONSE_MBP MID: ${mid} status: ${status} ,mbp: ${ buffer.subarray(4)} `)
|
|
535
|
-
|
|
536
|
-
if (this.promiseMap.has(res.mid)) {
|
|
537
|
-
// console.log('res promise msg', mid)
|
|
538
|
-
let [resolve, reject] = this.promiseMap.get(res.mid)
|
|
539
|
-
this.promiseMap.delete(res.mid)
|
|
540
|
-
|
|
541
|
-
if (res.status < 128) {
|
|
542
|
-
res.ok = true;
|
|
543
|
-
// console.log( 'unpack meta:', meta)
|
|
544
|
-
resolve(res)
|
|
545
|
-
} else {
|
|
546
|
-
res.ok = false;
|
|
547
|
-
reject(res)
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
} else {
|
|
552
|
-
console.log('no promise id')
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
publish(...args) {
|
|
558
|
-
this.signal(...args)
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
signal(tag, ...args) {
|
|
563
|
-
if (typeof tag !== 'string') throw TypeError('tag should be string.')
|
|
564
|
-
|
|
565
|
-
let signalPack = getSignalPack(tag, ...args)
|
|
566
|
-
this.send_enc_mode(signalPack)
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
decrypt_e2e(data, key) {
|
|
570
|
-
return this.boho.decrypt_e2e(data, key)
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
signal_e2e(tag, data, key) {
|
|
574
|
-
|
|
575
|
-
if (typeof tag !== 'string') throw TypeError('tag should be string.')
|
|
576
|
-
let tagEncoded = encoder.encode(tag)
|
|
577
|
-
let dataPack = MBP.B8(data)
|
|
578
|
-
|
|
579
|
-
//encrypt payload area with key
|
|
580
|
-
let sercretPack = this.boho.encrypt_e2e(dataPack, key)
|
|
581
|
-
|
|
582
|
-
//change signal MsgType header into SIGNAL_E2E
|
|
583
|
-
let signalPack = MBP.pack(
|
|
584
|
-
MBP.MB('#MsgType', '8', IOMsg.SIGNAL_E2E),
|
|
585
|
-
MBP.MB('#tagLen', '8', tagEncoded.byteLength),
|
|
586
|
-
MBP.MB('#tag', tagEncoded),
|
|
587
|
-
MBP.MB('#payloadType', '8', PAYLOAD_TYPE.BINARY),
|
|
588
|
-
MBP.MB('#payload', sercretPack)
|
|
589
|
-
)
|
|
590
|
-
|
|
591
|
-
this.send_enc_mode(signalPack)
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
set(storeName, ...args) {
|
|
597
|
-
if (!storeName || args.length == 0) {
|
|
598
|
-
return Promise.reject(new Error('set need storeName and value)'))
|
|
599
|
-
}
|
|
600
|
-
return this.req('store', 'set', storeName, ...args)
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
async get(storeName) {
|
|
604
|
-
if (!storeName) {
|
|
605
|
-
return Promise.reject(new Error('store get need storeName)'))
|
|
606
|
-
}
|
|
607
|
-
let pack = await this.req('store', 'get', storeName)
|
|
608
|
-
let { $ } = MBP.unpack(pack.body)
|
|
609
|
-
return $
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
req(target, topic, ...args) {
|
|
614
|
-
// console.log('common_req args', args)
|
|
615
|
-
if (!target || !topic)
|
|
616
|
-
return Promise.reject(new Error('request need target and topic)'))
|
|
617
|
-
let sigPack;
|
|
618
|
-
if (args.length > 0) {
|
|
619
|
-
sigPack = MBP.pack(
|
|
620
|
-
MBP.MB('#MsgType', '8', IOMsg.REQUEST),
|
|
621
|
-
MBP.MB('mid', '16', ++this.mid),
|
|
622
|
-
MBP.MB('target', target),
|
|
623
|
-
MBP.MB('topic', topic),
|
|
624
|
-
MBP.MBA(...args)
|
|
625
|
-
)
|
|
626
|
-
} else {
|
|
627
|
-
sigPack = MBP.pack(
|
|
628
|
-
MBP.MB('#MsgType', '8', IOMsg.REQUEST),
|
|
629
|
-
MBP.MB('mid', '16', ++this.mid),
|
|
630
|
-
MBP.MB('target', target),
|
|
631
|
-
MBP.MB('topic', topic)
|
|
632
|
-
)
|
|
633
|
-
}
|
|
634
|
-
// console.log('<< adminPack', this.mid, sigPack)
|
|
635
|
-
this.send_enc_mode(sigPack)
|
|
636
|
-
return this.setMsgPromise(this.mid)
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
subscribe(tag) {
|
|
641
|
-
if (typeof tag !== 'string') throw TypeError('tag should be string.')
|
|
642
|
-
if (this.state !== STATES.READY) return
|
|
643
|
-
|
|
644
|
-
let tagList = tag.split(',')
|
|
645
|
-
tagList.forEach(tag => {
|
|
646
|
-
this.channels.add(tag)
|
|
647
|
-
})
|
|
648
|
-
|
|
649
|
-
let tagEncoded = encoder.encode(tag)
|
|
650
|
-
if (tagEncoded.byteLength > SIZE_LIMIT.TAG_LEN1) throw TypeError('please use tag string bytelength below:' + SIZE_LIMIT.TAG_LEN1)
|
|
651
|
-
|
|
652
|
-
this.send_enc_mode(
|
|
653
|
-
Buffer.concat([
|
|
654
|
-
MBP.NB('8', IOMsg.SUBSCRIBE),
|
|
655
|
-
MBP.NB('8', tagEncoded.byteLength),
|
|
656
|
-
tagEncoded]))
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
subscribe_promise(tag) {
|
|
660
|
-
if (typeof tag !== 'string') throw TypeError('tag should be string.')
|
|
661
|
-
if (this.state !== STATES.READY) {
|
|
662
|
-
// console.log('not ready state:', this.state )
|
|
663
|
-
return Promise.reject('subscribe_promise:: connection is not ready')
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
let tagEncoded = encoder.encode(tag)
|
|
667
|
-
if (tagEncoded.byteLength > SIZE_LIMIT.TAG_LEN2) throw TypeError('please use tag string bytelength: ' + SIZE_LIMIT.TAG_LEN2)
|
|
668
|
-
|
|
669
|
-
this.send_enc_mode(
|
|
670
|
-
Buffer.concat([
|
|
671
|
-
MBP.NB('8', IOMsg.SUBSCRIBE_REQ),
|
|
672
|
-
MBP.NB('16', ++this.mid),
|
|
673
|
-
MBP.NB('16', tagEncoded.byteLength),
|
|
674
|
-
tagEncoded]))
|
|
675
|
-
return this.setMsgPromise(this.mid)
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
subscribe_memory_channels() { //local cache . auto_resubscribe
|
|
679
|
-
if (this.channels.size == 0) return
|
|
680
|
-
let chList = Array.from(this.channels).join(',')
|
|
681
|
-
// console.log('<< subscibe memory channels by cid', chList , this.cid )
|
|
682
|
-
|
|
683
|
-
this.subscribe_promise(chList)
|
|
684
|
-
.then((res) => {
|
|
685
|
-
// console.log('>> SUBSCRIBE_REQ result', res ) // return code == map.size
|
|
686
|
-
}).catch((e) => {
|
|
687
|
-
console.log('>> SUBSCRIBE FAIL:', e)
|
|
688
|
-
})
|
|
689
|
-
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
unsubscribe(tag = "") {
|
|
693
|
-
// console.log('unsub', tag)
|
|
694
|
-
if (typeof tag !== 'string') throw TypeError('tag should be string.')
|
|
695
|
-
|
|
696
|
-
if (tag == "") {
|
|
697
|
-
// console.log('unsub all')
|
|
698
|
-
this.channels.clear();
|
|
699
|
-
} else {
|
|
700
|
-
let tagList = tag.split(',')
|
|
701
|
-
tagList.forEach(tag => {
|
|
702
|
-
this.channels.delete(tag)
|
|
703
|
-
})
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
let tagEncoded = encoder.encode(tag)
|
|
707
|
-
if (tagEncoded.byteLength > SIZE_LIMIT.TAG_LEN1) throw TypeError('please use tag string bytelength below:' + SIZE_LIMIT.TAG_LEN1)
|
|
708
|
-
|
|
709
|
-
this.send_enc_mode(Buffer.concat([
|
|
710
|
-
MBP.NB('8', IOMsg.UNSUBSCRIBE),
|
|
711
|
-
MBP.NB('8', tagEncoded.byteLength),
|
|
712
|
-
tagEncoded]))
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
listen(tag, handler) {
|
|
717
|
-
if (typeof tag !== 'string') throw TypeError('tag should be string.')
|
|
718
|
-
if (tag.length > 255 || tag.length == 0) throw TypeError('tag string length range: 1~255')
|
|
719
|
-
if (typeof handler !== 'function') throw TypeError('handler is not a function.')
|
|
720
|
-
|
|
721
|
-
if (tag.indexOf('@') !== 0) {
|
|
722
|
-
this.channels.add(tag)
|
|
723
|
-
}
|
|
724
|
-
// console.log('channels:', this.channels )
|
|
725
|
-
this.on(tag, handler)
|
|
726
|
-
// do not subscribe now.
|
|
727
|
-
// will subscribe when receive CID_RES signal from server.
|
|
728
|
-
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
link(to, tag, handler) {
|
|
734
|
-
if (typeof to !== 'string') throw TypeError('to(local link target) is not a string.')
|
|
735
|
-
if (typeof tag !== 'string') throw TypeError('tag is not a string.')
|
|
736
|
-
if (tag.length > 255 || tag.length == 0) throw TypeError('tag string length range: 1~255')
|
|
737
|
-
if (typeof handler !== 'function') throw TypeError('handler is not a function.')
|
|
738
|
-
|
|
739
|
-
if (tag.indexOf('@') !== 0) {
|
|
740
|
-
this.channels.add(tag)
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
let linkSet;
|
|
744
|
-
if (this.linkMap.has(to)) {
|
|
745
|
-
linkSet = this.linkMap.get(to)
|
|
746
|
-
} else {
|
|
747
|
-
linkSet = new Set()
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
linkSet.add(tag)
|
|
751
|
-
this.linkMap.set(to, linkSet)
|
|
752
|
-
this.on(tag, handler)
|
|
753
|
-
this.subscribe(tag)
|
|
754
|
-
// console.log('link [to] linkMap:', to, this.linkMap )
|
|
755
|
-
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
unlink(to, tag) {
|
|
760
|
-
if (typeof to !== 'string') throw TypeError('to(local link target) is not a string.')
|
|
761
|
-
if (typeof tag !== 'string') throw TypeError('tag is not a string.')
|
|
762
|
-
if (tag.length > 255 || tag.length == 0) throw TypeError('tag string length range: 1~255')
|
|
763
|
-
|
|
764
|
-
if (!this.linkMap.has(to)) return;
|
|
765
|
-
|
|
766
|
-
let linkSet = this.linkMap.get(to)
|
|
767
|
-
let tags = Array.from(linkSet)
|
|
768
|
-
for (let i = 0; i < tags.length; i++) {
|
|
769
|
-
if (tags[i] == tag) {
|
|
770
|
-
this.unsubscribe(tag)
|
|
771
|
-
this.removeAllListeners(tag)
|
|
772
|
-
linkSet.delete(tag)
|
|
773
|
-
this.linkMap.set(to, linkSet)
|
|
774
|
-
break;
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
// console.log('unlink linkMap result:', this.linkMap )
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
unlinkAll(to) {
|
|
782
|
-
if (typeof to !== 'string') throw TypeError('to(local link target) is not a string.')
|
|
783
|
-
if (!this.linkMap.has(to)) return;
|
|
784
|
-
|
|
785
|
-
let linkSet = this.linkMap.get(to)
|
|
786
|
-
let tags = Array.from(linkSet)
|
|
787
|
-
for (let i = 0; i < tags.length; i++) {
|
|
788
|
-
this.unsubscribe(tags[i])
|
|
789
|
-
this.removeAllListeners(tags[i])
|
|
790
|
-
linkSet.delete(tags[i])
|
|
791
|
-
}
|
|
792
|
-
this.linkMap.delete(to)
|
|
793
|
-
|
|
794
|
-
// console.log('unlinkAll linkMap result:', this.linkMap )
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
getMetric() {
|
|
800
|
-
return {
|
|
801
|
-
tx: this.txCounter,
|
|
802
|
-
rx: this.rxCounter,
|
|
803
|
-
txb: this.txBytes,
|
|
804
|
-
rxb: this.rxBytes,
|
|
805
|
-
last: (Date.now() - this.lastTxRxTime) / 1000
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
getState() {
|
|
811
|
-
return this.state
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
getStateName() {
|
|
815
|
-
//state <number>
|
|
816
|
-
//value of constant STATES.NAME < number >
|
|
817
|
-
//type of constant STATES.NAME name < string uppercase >
|
|
818
|
-
//stateName,eventName <string lowercase>
|
|
819
|
-
return (STATES[this.state]).toLowerCase()
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
getSecurity() {
|
|
823
|
-
return {
|
|
824
|
-
useAuth: this.useAuth,
|
|
825
|
-
isTLS: this.TLS,
|
|
826
|
-
isAuthorized: this.boho.isAuthorized,
|
|
827
|
-
encMode: this.encMode,
|
|
828
|
-
usingEncryption: this.getEncryptionMode()
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
stateChange(state, emitEventAndMessage) {
|
|
833
|
-
// STATES constant name : string upperCase
|
|
834
|
-
// eventName, .stateName : string lowerCase
|
|
835
|
-
// .state : number
|
|
836
|
-
let eventName = state.toLowerCase()
|
|
837
|
-
this.state = STATES[state.toUpperCase()] // state: number
|
|
838
|
-
if (emitEventAndMessage) this.emit(eventName, emitEventAndMessage)
|
|
839
|
-
|
|
840
|
-
if (this.stateName !== eventName) {
|
|
841
|
-
// console.log(`state: ${this.stateName} => ${eventName}` )
|
|
842
|
-
this.stateName = eventName
|
|
843
|
-
this.emit('change', eventName)
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
1
|
+
import { MBP, Buffer } from 'meta-buffer-pack'
|
|
2
|
+
import EventEmitter from "eventemitter3";
|
|
3
|
+
import { Boho, BohoMsg, MetaSize } from "boho";
|
|
4
|
+
import { IOMsg, PAYLOAD_TYPE, SIZE_LIMIT, ENC_MODE, STATES } from '../common/constants.js'
|
|
5
|
+
import { quotaTable } from '../common/quotaTable.js'
|
|
6
|
+
import { getSignalPack } from '../common/payload.js';
|
|
7
|
+
|
|
8
|
+
const encoder = new TextEncoder()
|
|
9
|
+
const decoder = new TextDecoder()
|
|
10
|
+
|
|
11
|
+
function byteToUrl(buffer) {
|
|
12
|
+
//ipv4(4bytes) , port(2bytes)
|
|
13
|
+
if (buffer.byteLength != 6) return
|
|
14
|
+
let address = buffer[0].toString() + "." + buffer[1].toString()
|
|
15
|
+
+ "." + buffer[2].toString() + "." + buffer[3].toString();
|
|
16
|
+
let port = (buffer[4] << 8) + buffer[5]
|
|
17
|
+
return address + ':' + port.toString()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class IOCore extends EventEmitter {
|
|
21
|
+
constructor(url) {
|
|
22
|
+
super();
|
|
23
|
+
this.cid = "" // get from the server CID_RES
|
|
24
|
+
this.ip = "" // get from the server IAM_RES message.
|
|
25
|
+
this.socket = null;
|
|
26
|
+
this.url = url; // init default server url
|
|
27
|
+
this.state = STATES.CLOSED; // Number type
|
|
28
|
+
this.stateName = this.getStateName() // String type
|
|
29
|
+
|
|
30
|
+
this.txCounter = 0;
|
|
31
|
+
this.rxCounter = 0;
|
|
32
|
+
this.txBytes = 0;
|
|
33
|
+
this.rxBytes = 0;
|
|
34
|
+
|
|
35
|
+
this.lastTxRxTime = Date.now();
|
|
36
|
+
this.connectionCheckerPeriod = SIZE_LIMIT.CONNECTION_CHECKER_PERIOD;
|
|
37
|
+
this.connectionCheckerIntervalID = null;
|
|
38
|
+
|
|
39
|
+
this.boho = new Boho()
|
|
40
|
+
this.TLS = false // true if protocol is wss(TLS)
|
|
41
|
+
this.encMode = ENC_MODE.AUTO;
|
|
42
|
+
this.useAuth = false;
|
|
43
|
+
|
|
44
|
+
this.nick = "";
|
|
45
|
+
this.channels = new Set()
|
|
46
|
+
this.promiseMap = new Map()
|
|
47
|
+
this.promiseTimeOut = SIZE_LIMIT.PROMISE_TIMEOUT
|
|
48
|
+
this.mid = 0 // promise message id
|
|
49
|
+
|
|
50
|
+
this.level = 3; // also defaultQuotaLevel
|
|
51
|
+
this.quota = quotaTable[this.level];
|
|
52
|
+
this.serverSet = {}
|
|
53
|
+
this.linkMap = new Map()
|
|
54
|
+
|
|
55
|
+
this.on('open', this.onOpen.bind(this))
|
|
56
|
+
this.on('close', this.onClose.bind(this))
|
|
57
|
+
this.on('socket_data', this.onData.bind(this))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
redirect(url2) {
|
|
62
|
+
this.close()
|
|
63
|
+
this.stateChange('redirecting')
|
|
64
|
+
this.createConnection(url2)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
open(url) {
|
|
68
|
+
if (!url && !this.url) return;
|
|
69
|
+
|
|
70
|
+
if (url) {
|
|
71
|
+
if (!this.url) { // default host url
|
|
72
|
+
this.url = url
|
|
73
|
+
} else if (url !== this.url) { // default host url change
|
|
74
|
+
this.url = url;
|
|
75
|
+
if (this.socket) {
|
|
76
|
+
this.close()
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
this.createConnection(this.url)
|
|
83
|
+
|
|
84
|
+
if (!this.connectionCheckerIntervalID) {
|
|
85
|
+
this.connectionCheckerIntervalID = setInterval(this.keepAlive.bind(this), this.connectionCheckerPeriod);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
onOpen() {
|
|
90
|
+
if (this.url.includes("wss://")) {
|
|
91
|
+
this.TLS = true;
|
|
92
|
+
} else {
|
|
93
|
+
this.TLS = false;
|
|
94
|
+
}
|
|
95
|
+
this.stateChange('open')
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
onClose() {
|
|
99
|
+
this.boho.isAuthorized = false;
|
|
100
|
+
this.cid = ""
|
|
101
|
+
this.stateChange('closed')
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// manual login
|
|
105
|
+
login(id, key) {
|
|
106
|
+
if (!id && !key) {
|
|
107
|
+
console.log('no id and key.')
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
console.log('manual login: ', id)
|
|
111
|
+
|
|
112
|
+
if (!key && id.includes('.')) {
|
|
113
|
+
this.boho.set_id_key(id)
|
|
114
|
+
} else if (id && key) {
|
|
115
|
+
this.boho.set_id8(id)
|
|
116
|
+
this.boho.set_key(key)
|
|
117
|
+
} else {
|
|
118
|
+
console.log('no id or key.')
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
this.useAuth = true
|
|
122
|
+
let auth_pack = this.boho.auth_req()
|
|
123
|
+
// console.log('auth_req_pack', auth_pack )
|
|
124
|
+
this.send(auth_pack)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// auto login
|
|
128
|
+
auth(id, key) {
|
|
129
|
+
if (!id && !key) {
|
|
130
|
+
console.log('no id and key.')
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!key && id.includes('.')) {
|
|
135
|
+
this.boho.set_id_key(id)
|
|
136
|
+
} else if (id && key) {
|
|
137
|
+
this.boho.set_id8(id)
|
|
138
|
+
this.boho.set_key(key)
|
|
139
|
+
} else {
|
|
140
|
+
console.log('no id or key.')
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
this.useAuth = true
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
onData(buffer) {
|
|
147
|
+
// console.log('remote rcv socket_message', buffer )
|
|
148
|
+
//check first byte (remote message type)
|
|
149
|
+
let msgType = buffer[0];
|
|
150
|
+
let decoded;
|
|
151
|
+
|
|
152
|
+
if (msgType === BohoMsg.ENC_488) {
|
|
153
|
+
decoded = this.boho.decrypt_488(buffer)
|
|
154
|
+
if (decoded) {
|
|
155
|
+
// console.log( decoded )
|
|
156
|
+
msgType = decoded[0]
|
|
157
|
+
buffer = decoded
|
|
158
|
+
// console.log('DECODED MsgType:', IOMsg[ msgType ] )
|
|
159
|
+
} else {
|
|
160
|
+
// console.log('DEC_FAIL', buffer.byteLength)
|
|
161
|
+
}
|
|
162
|
+
} else if (msgType === BohoMsg.ENC_E2E) {
|
|
163
|
+
// console.log('rcv ENC_E2E' )
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
decoded = this.boho.decrypt_488(buffer)
|
|
167
|
+
//헤더를 읽고 헤더크기만큼만 해석한다.
|
|
168
|
+
if (decoded) {
|
|
169
|
+
// console.log( 'ENC_E2E decoded ', decoded )
|
|
170
|
+
msgType = decoded[0]
|
|
171
|
+
// decoded has msg_header only.
|
|
172
|
+
buffer.set(decoded, MetaSize.ENC_488) // set decoded signal_e2e headaer.
|
|
173
|
+
buffer = buffer.subarray(MetaSize.ENC_488) // reset offset.
|
|
174
|
+
// console.log('DECODED MsgType:', IOMsg[ msgType ] )
|
|
175
|
+
} else {
|
|
176
|
+
// console.log('488 DEC_FAIL', buffer)
|
|
177
|
+
return
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
} catch (err) {
|
|
181
|
+
// console.log('E2E DEC_FAIL decryption error', err)
|
|
182
|
+
return
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
let type = IOMsg[msgType]
|
|
188
|
+
if (!type) type = BohoMsg[msgType]
|
|
189
|
+
|
|
190
|
+
// console.log( "MsgType: ", type , " LEN ", buffer.byteLength)
|
|
191
|
+
|
|
192
|
+
switch (msgType) {
|
|
193
|
+
case IOMsg.OVER_SIZE:
|
|
194
|
+
console.log('## server sent: over_size event.')
|
|
195
|
+
this.emit('over_size', 'over_size')
|
|
196
|
+
break;
|
|
197
|
+
case IOMsg.PING:
|
|
198
|
+
this.pong();
|
|
199
|
+
break;
|
|
200
|
+
|
|
201
|
+
case IOMsg.PONG:
|
|
202
|
+
break;
|
|
203
|
+
|
|
204
|
+
case IOMsg.IAM_RES:
|
|
205
|
+
try {
|
|
206
|
+
let str = decoder.decode(buffer.subarray(1))
|
|
207
|
+
let jsonInfo = JSON.parse(str)
|
|
208
|
+
if (jsonInfo.ip) {
|
|
209
|
+
this.ip = jsonInfo.ip;
|
|
210
|
+
}
|
|
211
|
+
console.log('<IAM_RES>', JSON.stringify(jsonInfo))
|
|
212
|
+
// console.log('<IAM_RES>', JSON.stringify(jsonInfo,null,2))
|
|
213
|
+
} catch (error) {
|
|
214
|
+
// console.log('<IAM_RES> data error')
|
|
215
|
+
}
|
|
216
|
+
break;
|
|
217
|
+
|
|
218
|
+
case IOMsg.CID_RES:
|
|
219
|
+
let cidStr = decoder.decode(buffer.subarray(1))
|
|
220
|
+
// console.log( '>> CID_RES: ' ,cidStr )
|
|
221
|
+
this.cid = cidStr;
|
|
222
|
+
// change state before subscribe.
|
|
223
|
+
this.stateChange('ready', 'cid_ready')
|
|
224
|
+
this.subscribe_memory_channels()
|
|
225
|
+
break;
|
|
226
|
+
|
|
227
|
+
case IOMsg.QUOTA_LEVEL:
|
|
228
|
+
let quotaLevel = buffer[1]
|
|
229
|
+
this.level = quotaLevel;
|
|
230
|
+
this.quota = quotaTable[quotaLevel];
|
|
231
|
+
console.log('## QUOTA:', quotaLevel, JSON.stringify(this.quota))
|
|
232
|
+
break;
|
|
233
|
+
|
|
234
|
+
case IOMsg.SERVER_CLEAR_AUTH:
|
|
235
|
+
this.useAuth = false;
|
|
236
|
+
this.boho.clearAuth();
|
|
237
|
+
this.stop();
|
|
238
|
+
break;
|
|
239
|
+
|
|
240
|
+
case IOMsg.SERVER_REDIRECT:
|
|
241
|
+
let host_port;
|
|
242
|
+
let url;
|
|
243
|
+
let protocol;
|
|
244
|
+
let addressType;
|
|
245
|
+
if (buffer.byteLength == 7) { // ipv4 ,port
|
|
246
|
+
addressType = 'IPV4:PORT'
|
|
247
|
+
host_port = byteToUrl(buffer.subarray(1))
|
|
248
|
+
protocol = 'cong://'
|
|
249
|
+
} else { // domain url
|
|
250
|
+
addressType = 'URL'
|
|
251
|
+
host_port = decoder.decode(buffer.subarray(1))
|
|
252
|
+
protocol = ''
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
url = protocol + host_port
|
|
256
|
+
this.redirect(url)
|
|
257
|
+
break;
|
|
258
|
+
|
|
259
|
+
case IOMsg.SERVER_READY:
|
|
260
|
+
this.stateChange('server_ready', 'server_ready')
|
|
261
|
+
if (this.useAuth) {
|
|
262
|
+
this.send(this.boho.auth_req())
|
|
263
|
+
// CID_REQ will be called, after auth_ack.
|
|
264
|
+
} else {
|
|
265
|
+
// CID_REQ here, if not using auth.
|
|
266
|
+
this.send(Buffer.from([IOMsg.CID_REQ]))
|
|
267
|
+
}
|
|
268
|
+
break;
|
|
269
|
+
|
|
270
|
+
case IOMsg.SERVER_SIGNAL:
|
|
271
|
+
try {
|
|
272
|
+
let str = decoder.decode(buffer.subarray(1))
|
|
273
|
+
let ss = JSON.parse(str)
|
|
274
|
+
// console.log('SERVER_SIGNAL', JSON.stringify(ss))
|
|
275
|
+
|
|
276
|
+
if (ss.event && ss.data) {
|
|
277
|
+
this.serverSet = ss.data;
|
|
278
|
+
this.emit(ss.event, ss.data)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
} catch (error) {
|
|
282
|
+
// console.log('<SERVER_SIGNAL> parsing error')
|
|
283
|
+
}
|
|
284
|
+
break;
|
|
285
|
+
|
|
286
|
+
case IOMsg.SET:
|
|
287
|
+
try {
|
|
288
|
+
let setPack = MBP.unpack(buffer)
|
|
289
|
+
if (setPack) {
|
|
290
|
+
// console.log('[SET] topic: ', setPack.topic)
|
|
291
|
+
this.emit(setPack.topic, ...setPack.args)
|
|
292
|
+
}
|
|
293
|
+
} catch (error) {
|
|
294
|
+
// console.log('<SET> parsing error')
|
|
295
|
+
}
|
|
296
|
+
break;
|
|
297
|
+
|
|
298
|
+
case IOMsg.SIGNAL_E2E:
|
|
299
|
+
case IOMsg.SIGNAL:
|
|
300
|
+
try {
|
|
301
|
+
let tagLen = buffer.readUint8(1)
|
|
302
|
+
let tagBuf = buffer.subarray(2, 2 + tagLen)
|
|
303
|
+
let tag = decoder.decode(tagBuf)
|
|
304
|
+
|
|
305
|
+
let payloadType = buffer.readUint8(2 + tagLen)
|
|
306
|
+
let payloadBuffer = buffer.subarray(3 + tagLen)
|
|
307
|
+
|
|
308
|
+
/* three types of signal message.
|
|
309
|
+
> unicast message to me: tag includes @, no cid: '@*'
|
|
310
|
+
> cid_sub message: tag includes cid and @ both : 'cid@*'
|
|
311
|
+
> ch_sub message: else.
|
|
312
|
+
*/
|
|
313
|
+
switch (payloadType) {
|
|
314
|
+
|
|
315
|
+
case PAYLOAD_TYPE.EMPTY: // 0
|
|
316
|
+
if (tag.indexOf('@') === 0) this.emit('@', null, tag)
|
|
317
|
+
else this.emit(tag, null, tag)
|
|
318
|
+
break;
|
|
319
|
+
|
|
320
|
+
case PAYLOAD_TYPE.TEXT: // 1
|
|
321
|
+
// !! Must remove null char before decode in JS.
|
|
322
|
+
// string payload contains null char for the c/cpp devices.
|
|
323
|
+
let payloadStringWithoutNull = payloadBuffer.subarray(0, payloadBuffer.byteLength - 1)
|
|
324
|
+
let oneString = decoder.decode(payloadStringWithoutNull)
|
|
325
|
+
if (tag.indexOf('@') === 0) this.emit('@', oneString, tag)
|
|
326
|
+
if (tag !== '@') this.emit(tag, oneString, tag)
|
|
327
|
+
break;
|
|
328
|
+
|
|
329
|
+
case PAYLOAD_TYPE.BINARY: // 2
|
|
330
|
+
if (tag.indexOf('@') === 0) this.emit('@', payloadBuffer, tag)
|
|
331
|
+
if (tag !== '@') this.emit(tag, payloadBuffer, tag)
|
|
332
|
+
break;
|
|
333
|
+
|
|
334
|
+
case PAYLOAD_TYPE.OBJECT:
|
|
335
|
+
let oneObjectBuffer = decoder.decode(payloadBuffer)
|
|
336
|
+
let oneJSONObject = JSON.parse(oneObjectBuffer)
|
|
337
|
+
if (tag.indexOf('@') === 0) this.emit('@', oneJSONObject, tag)
|
|
338
|
+
if (tag !== '@') this.emit(tag, oneJSONObject, tag)
|
|
339
|
+
break;
|
|
340
|
+
|
|
341
|
+
case PAYLOAD_TYPE.MJSON:
|
|
342
|
+
let mjsonBuffer = decoder.decode(payloadBuffer)
|
|
343
|
+
let mjson = JSON.parse(mjsonBuffer)
|
|
344
|
+
if (tag.indexOf('@') === 0) this.emit('@', ...mjson, tag)
|
|
345
|
+
if (tag !== '@') this.emit(tag, ...mjson, tag)
|
|
346
|
+
break;
|
|
347
|
+
|
|
348
|
+
case PAYLOAD_TYPE.MBA:
|
|
349
|
+
let mbaObject = MBP.unpack(payloadBuffer)
|
|
350
|
+
if (tag.indexOf('@') === 0) this.emit('@', ...mbaObject.args, tag)
|
|
351
|
+
if (tag !== '@') this.emit(tag, ...mbaObject.args, tag)
|
|
352
|
+
break;
|
|
353
|
+
|
|
354
|
+
default:
|
|
355
|
+
// console.log('## Unkown payloadtype', payloadType)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
} catch (err) {
|
|
359
|
+
// console.log('## signal parse err', err)
|
|
360
|
+
}
|
|
361
|
+
break;
|
|
362
|
+
|
|
363
|
+
case IOMsg.RESPONSE_MBP:
|
|
364
|
+
this.testPromise(buffer)
|
|
365
|
+
break;
|
|
366
|
+
|
|
367
|
+
case BohoMsg.AUTH_NONCE:
|
|
368
|
+
let auth_hmac = this.boho.auth_hmac(buffer)
|
|
369
|
+
if (auth_hmac) {
|
|
370
|
+
this.send(auth_hmac)
|
|
371
|
+
} else {
|
|
372
|
+
this.stateChange('auth_fail', 'Invalid local auth_hmac.')
|
|
373
|
+
}
|
|
374
|
+
break;
|
|
375
|
+
|
|
376
|
+
case BohoMsg.AUTH_FAIL:
|
|
377
|
+
this.stateChange('auth_fail', 'server reject auth.')
|
|
378
|
+
break;
|
|
379
|
+
|
|
380
|
+
case BohoMsg.AUTH_ACK:
|
|
381
|
+
if (this.boho.check_auth_ack_hmac(buffer)) {
|
|
382
|
+
this.stateChange('auth_ready', 'server sent auth_ack')
|
|
383
|
+
this.send(Buffer.from([IOMsg.CID_REQ]))
|
|
384
|
+
} else {
|
|
385
|
+
this.stateChange('auth_fail', 'invalid server_hmac')
|
|
386
|
+
}
|
|
387
|
+
break;
|
|
388
|
+
|
|
389
|
+
default:
|
|
390
|
+
try {
|
|
391
|
+
decoded = decoder.decode(buffer)
|
|
392
|
+
// console.log('text message:', decoded)
|
|
393
|
+
this.emit('text_message', decoded)
|
|
394
|
+
} catch (error) {
|
|
395
|
+
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
break;
|
|
399
|
+
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
iam(title) {
|
|
404
|
+
// console.log('iam', title)
|
|
405
|
+
if (title) {
|
|
406
|
+
this.send_enc_mode(MBP.pack(
|
|
407
|
+
MBP.MB('#MsgType', '8', IOMsg.IAM),
|
|
408
|
+
MBP.MB('#', title)
|
|
409
|
+
))
|
|
410
|
+
} else {
|
|
411
|
+
this.send_enc_mode(MBP.pack(
|
|
412
|
+
MBP.MB('#MsgType', '8', IOMsg.IAM)
|
|
413
|
+
))
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
ping() {
|
|
419
|
+
this.send(Buffer.from([IOMsg.PING]))
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
pong() {
|
|
423
|
+
this.send(Buffer.from([IOMsg.PONG]))
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
// application level ping tool.
|
|
428
|
+
// simple message sending and reply.
|
|
429
|
+
echo(args) {
|
|
430
|
+
if (args) {
|
|
431
|
+
console.log('echo args:', args)
|
|
432
|
+
this.send_enc_mode(MBP.pack(
|
|
433
|
+
MBP.MB('#MsgType', '8', IOMsg.ECHO),
|
|
434
|
+
MBP.MB('#msg', args)
|
|
435
|
+
))
|
|
436
|
+
} else {
|
|
437
|
+
// # do not encrypt blank echo #
|
|
438
|
+
this.send(Buffer.from([IOMsg.ECHO]))
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
bin(...data) {
|
|
444
|
+
this.send(MBP.U8pack(...data))
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
send(data) {
|
|
448
|
+
if (data.byteLength > this.quota.signalSize) {
|
|
449
|
+
this.emit('over_size')
|
|
450
|
+
console.log('## QUOTA LIMIT OVER!! \nsignal message.byteLength: ', data.byteLength)
|
|
451
|
+
console.log('## your maximum signalSize(bytes) is:', this.quota.signalSize)
|
|
452
|
+
return
|
|
453
|
+
}
|
|
454
|
+
this.socket_send(data);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/*
|
|
458
|
+
Policy. Should message do encrypt?
|
|
459
|
+
|
|
460
|
+
if encMode == auto
|
|
461
|
+
NO. if connection using TLS line.
|
|
462
|
+
// ex. wss://url connection.
|
|
463
|
+
YES. if no TLS line.
|
|
464
|
+
// ex. ws://url connection.
|
|
465
|
+
|
|
466
|
+
if encMode == YES
|
|
467
|
+
YES. encrypt the message.
|
|
468
|
+
|
|
469
|
+
if encMode == NO
|
|
470
|
+
NO. do not ecnrypt message.
|
|
471
|
+
|
|
472
|
+
*/
|
|
473
|
+
getEncryptionMode() {
|
|
474
|
+
if (this.encMode === ENC_MODE.YES ||
|
|
475
|
+
this.encMode === ENC_MODE.AUTO &&
|
|
476
|
+
!this.TLS && this.boho.isAuthorized
|
|
477
|
+
) {
|
|
478
|
+
return true;
|
|
479
|
+
} else {
|
|
480
|
+
return false
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
send_enc_mode(data, useEncryption) {
|
|
485
|
+
|
|
486
|
+
// use default policy.
|
|
487
|
+
if (useEncryption === undefined) {
|
|
488
|
+
useEncryption = this.getEncryptionMode()
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (data[0] == IOMsg.SIGNAL_E2E && useEncryption) {
|
|
492
|
+
// input data: signal_header + e2ePayload
|
|
493
|
+
// encrypt signal_header area only. payload is encrypted with e2e key already.
|
|
494
|
+
let tagLen = data[1]
|
|
495
|
+
let encHeader = this.boho.encrypt_488(data.subarray(0, 3 + tagLen))
|
|
496
|
+
encHeader[0] = BohoMsg.ENC_E2E
|
|
497
|
+
this.send(Buffer.concat([encHeader, data.subarray(3 + tagLen)]))
|
|
498
|
+
// console.log('<< send_enc_mode [ ENC_E2E ]')
|
|
499
|
+
|
|
500
|
+
} else if (useEncryption) {
|
|
501
|
+
// console.log('<< send_enc_mode [ ENC_488 ]')
|
|
502
|
+
let encPack = this.boho.encrypt_488(data)
|
|
503
|
+
this.send(encPack)
|
|
504
|
+
} else {
|
|
505
|
+
// console.log('<< send_enc_mode [ PLAIN ]' )
|
|
506
|
+
this.send(data)
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
setMsgPromise(mid) {
|
|
513
|
+
return new Promise((resolve, reject) => {
|
|
514
|
+
this.promiseMap.set(mid, [resolve, reject])
|
|
515
|
+
// console.log('set promise. mid, size', mid, this.promiseMap.size)
|
|
516
|
+
setTimeout(e => {
|
|
517
|
+
if (this.promiseMap.has(mid)) {
|
|
518
|
+
reject('timeout');
|
|
519
|
+
this.promiseMap.delete(mid)
|
|
520
|
+
// console.log('promise timeout. mid, size:', mid, this.promiseMap.size)
|
|
521
|
+
}
|
|
522
|
+
}, this.promiseTimeOut);
|
|
523
|
+
})
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
testPromise(buffer) {
|
|
527
|
+
// console.log('mbp buffer : ', buffer , buffer.byteLength)
|
|
528
|
+
// let mbp = ( buffer.byteLength > 4 ) ? buffer.subarray(4) : ""
|
|
529
|
+
|
|
530
|
+
let res = MBP.unpack(buffer)
|
|
531
|
+
if (!res) return
|
|
532
|
+
// console.log( res )
|
|
533
|
+
|
|
534
|
+
// console.log(`RESPONSE_MBP MID: ${mid} status: ${status} ,mbp: ${ buffer.subarray(4)} `)
|
|
535
|
+
|
|
536
|
+
if (this.promiseMap.has(res.mid)) {
|
|
537
|
+
// console.log('res promise msg', mid)
|
|
538
|
+
let [resolve, reject] = this.promiseMap.get(res.mid)
|
|
539
|
+
this.promiseMap.delete(res.mid)
|
|
540
|
+
|
|
541
|
+
if (res.status < 128) {
|
|
542
|
+
res.ok = true;
|
|
543
|
+
// console.log( 'unpack meta:', meta)
|
|
544
|
+
resolve(res)
|
|
545
|
+
} else {
|
|
546
|
+
res.ok = false;
|
|
547
|
+
reject(res)
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
} else {
|
|
552
|
+
console.log('no promise id')
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
publish(...args) {
|
|
558
|
+
this.signal(...args)
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
signal(tag, ...args) {
|
|
563
|
+
if (typeof tag !== 'string') throw TypeError('tag should be string.')
|
|
564
|
+
|
|
565
|
+
let signalPack = getSignalPack(tag, ...args)
|
|
566
|
+
this.send_enc_mode(signalPack)
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
decrypt_e2e(data, key) {
|
|
570
|
+
return this.boho.decrypt_e2e(data, key)
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
signal_e2e(tag, data, key) {
|
|
574
|
+
|
|
575
|
+
if (typeof tag !== 'string') throw TypeError('tag should be string.')
|
|
576
|
+
let tagEncoded = encoder.encode(tag)
|
|
577
|
+
let dataPack = MBP.B8(data)
|
|
578
|
+
|
|
579
|
+
//encrypt payload area with key
|
|
580
|
+
let sercretPack = this.boho.encrypt_e2e(dataPack, key)
|
|
581
|
+
|
|
582
|
+
//change signal MsgType header into SIGNAL_E2E
|
|
583
|
+
let signalPack = MBP.pack(
|
|
584
|
+
MBP.MB('#MsgType', '8', IOMsg.SIGNAL_E2E),
|
|
585
|
+
MBP.MB('#tagLen', '8', tagEncoded.byteLength),
|
|
586
|
+
MBP.MB('#tag', tagEncoded),
|
|
587
|
+
MBP.MB('#payloadType', '8', PAYLOAD_TYPE.BINARY),
|
|
588
|
+
MBP.MB('#payload', sercretPack)
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
this.send_enc_mode(signalPack)
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
set(storeName, ...args) {
|
|
597
|
+
if (!storeName || args.length == 0) {
|
|
598
|
+
return Promise.reject(new Error('set need storeName and value)'))
|
|
599
|
+
}
|
|
600
|
+
return this.req('store', 'set', storeName, ...args)
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
async get(storeName) {
|
|
604
|
+
if (!storeName) {
|
|
605
|
+
return Promise.reject(new Error('store get need storeName)'))
|
|
606
|
+
}
|
|
607
|
+
let pack = await this.req('store', 'get', storeName)
|
|
608
|
+
let { $ } = MBP.unpack(pack.body)
|
|
609
|
+
return $
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
req(target, topic, ...args) {
|
|
614
|
+
// console.log('common_req args', args)
|
|
615
|
+
if (!target || !topic)
|
|
616
|
+
return Promise.reject(new Error('request need target and topic)'))
|
|
617
|
+
let sigPack;
|
|
618
|
+
if (args.length > 0) {
|
|
619
|
+
sigPack = MBP.pack(
|
|
620
|
+
MBP.MB('#MsgType', '8', IOMsg.REQUEST),
|
|
621
|
+
MBP.MB('mid', '16', ++this.mid),
|
|
622
|
+
MBP.MB('target', target),
|
|
623
|
+
MBP.MB('topic', topic),
|
|
624
|
+
MBP.MBA(...args)
|
|
625
|
+
)
|
|
626
|
+
} else {
|
|
627
|
+
sigPack = MBP.pack(
|
|
628
|
+
MBP.MB('#MsgType', '8', IOMsg.REQUEST),
|
|
629
|
+
MBP.MB('mid', '16', ++this.mid),
|
|
630
|
+
MBP.MB('target', target),
|
|
631
|
+
MBP.MB('topic', topic)
|
|
632
|
+
)
|
|
633
|
+
}
|
|
634
|
+
// console.log('<< adminPack', this.mid, sigPack)
|
|
635
|
+
this.send_enc_mode(sigPack)
|
|
636
|
+
return this.setMsgPromise(this.mid)
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
subscribe(tag) {
|
|
641
|
+
if (typeof tag !== 'string') throw TypeError('tag should be string.')
|
|
642
|
+
if (this.state !== STATES.READY) return
|
|
643
|
+
|
|
644
|
+
let tagList = tag.split(',')
|
|
645
|
+
tagList.forEach(tag => {
|
|
646
|
+
this.channels.add(tag)
|
|
647
|
+
})
|
|
648
|
+
|
|
649
|
+
let tagEncoded = encoder.encode(tag)
|
|
650
|
+
if (tagEncoded.byteLength > SIZE_LIMIT.TAG_LEN1) throw TypeError('please use tag string bytelength below:' + SIZE_LIMIT.TAG_LEN1)
|
|
651
|
+
|
|
652
|
+
this.send_enc_mode(
|
|
653
|
+
Buffer.concat([
|
|
654
|
+
MBP.NB('8', IOMsg.SUBSCRIBE),
|
|
655
|
+
MBP.NB('8', tagEncoded.byteLength),
|
|
656
|
+
tagEncoded]))
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
subscribe_promise(tag) {
|
|
660
|
+
if (typeof tag !== 'string') throw TypeError('tag should be string.')
|
|
661
|
+
if (this.state !== STATES.READY) {
|
|
662
|
+
// console.log('not ready state:', this.state )
|
|
663
|
+
return Promise.reject('subscribe_promise:: connection is not ready')
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
let tagEncoded = encoder.encode(tag)
|
|
667
|
+
if (tagEncoded.byteLength > SIZE_LIMIT.TAG_LEN2) throw TypeError('please use tag string bytelength: ' + SIZE_LIMIT.TAG_LEN2)
|
|
668
|
+
|
|
669
|
+
this.send_enc_mode(
|
|
670
|
+
Buffer.concat([
|
|
671
|
+
MBP.NB('8', IOMsg.SUBSCRIBE_REQ),
|
|
672
|
+
MBP.NB('16', ++this.mid),
|
|
673
|
+
MBP.NB('16', tagEncoded.byteLength),
|
|
674
|
+
tagEncoded]))
|
|
675
|
+
return this.setMsgPromise(this.mid)
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
subscribe_memory_channels() { //local cache . auto_resubscribe
|
|
679
|
+
if (this.channels.size == 0) return
|
|
680
|
+
let chList = Array.from(this.channels).join(',')
|
|
681
|
+
// console.log('<< subscibe memory channels by cid', chList , this.cid )
|
|
682
|
+
|
|
683
|
+
this.subscribe_promise(chList)
|
|
684
|
+
.then((res) => {
|
|
685
|
+
// console.log('>> SUBSCRIBE_REQ result', res ) // return code == map.size
|
|
686
|
+
}).catch((e) => {
|
|
687
|
+
console.log('>> SUBSCRIBE FAIL:', e)
|
|
688
|
+
})
|
|
689
|
+
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
unsubscribe(tag = "") {
|
|
693
|
+
// console.log('unsub', tag)
|
|
694
|
+
if (typeof tag !== 'string') throw TypeError('tag should be string.')
|
|
695
|
+
|
|
696
|
+
if (tag == "") {
|
|
697
|
+
// console.log('unsub all')
|
|
698
|
+
this.channels.clear();
|
|
699
|
+
} else {
|
|
700
|
+
let tagList = tag.split(',')
|
|
701
|
+
tagList.forEach(tag => {
|
|
702
|
+
this.channels.delete(tag)
|
|
703
|
+
})
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
let tagEncoded = encoder.encode(tag)
|
|
707
|
+
if (tagEncoded.byteLength > SIZE_LIMIT.TAG_LEN1) throw TypeError('please use tag string bytelength below:' + SIZE_LIMIT.TAG_LEN1)
|
|
708
|
+
|
|
709
|
+
this.send_enc_mode(Buffer.concat([
|
|
710
|
+
MBP.NB('8', IOMsg.UNSUBSCRIBE),
|
|
711
|
+
MBP.NB('8', tagEncoded.byteLength),
|
|
712
|
+
tagEncoded]))
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
listen(tag, handler) {
|
|
717
|
+
if (typeof tag !== 'string') throw TypeError('tag should be string.')
|
|
718
|
+
if (tag.length > 255 || tag.length == 0) throw TypeError('tag string length range: 1~255')
|
|
719
|
+
if (typeof handler !== 'function') throw TypeError('handler is not a function.')
|
|
720
|
+
|
|
721
|
+
if (tag.indexOf('@') !== 0) {
|
|
722
|
+
this.channels.add(tag)
|
|
723
|
+
}
|
|
724
|
+
// console.log('channels:', this.channels )
|
|
725
|
+
this.on(tag, handler)
|
|
726
|
+
// do not subscribe now.
|
|
727
|
+
// will subscribe when receive CID_RES signal from server.
|
|
728
|
+
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
link(to, tag, handler) {
|
|
734
|
+
if (typeof to !== 'string') throw TypeError('to(local link target) is not a string.')
|
|
735
|
+
if (typeof tag !== 'string') throw TypeError('tag is not a string.')
|
|
736
|
+
if (tag.length > 255 || tag.length == 0) throw TypeError('tag string length range: 1~255')
|
|
737
|
+
if (typeof handler !== 'function') throw TypeError('handler is not a function.')
|
|
738
|
+
|
|
739
|
+
if (tag.indexOf('@') !== 0) {
|
|
740
|
+
this.channels.add(tag)
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
let linkSet;
|
|
744
|
+
if (this.linkMap.has(to)) {
|
|
745
|
+
linkSet = this.linkMap.get(to)
|
|
746
|
+
} else {
|
|
747
|
+
linkSet = new Set()
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
linkSet.add(tag)
|
|
751
|
+
this.linkMap.set(to, linkSet)
|
|
752
|
+
this.on(tag, handler)
|
|
753
|
+
this.subscribe(tag)
|
|
754
|
+
// console.log('link [to] linkMap:', to, this.linkMap )
|
|
755
|
+
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
unlink(to, tag) {
|
|
760
|
+
if (typeof to !== 'string') throw TypeError('to(local link target) is not a string.')
|
|
761
|
+
if (typeof tag !== 'string') throw TypeError('tag is not a string.')
|
|
762
|
+
if (tag.length > 255 || tag.length == 0) throw TypeError('tag string length range: 1~255')
|
|
763
|
+
|
|
764
|
+
if (!this.linkMap.has(to)) return;
|
|
765
|
+
|
|
766
|
+
let linkSet = this.linkMap.get(to)
|
|
767
|
+
let tags = Array.from(linkSet)
|
|
768
|
+
for (let i = 0; i < tags.length; i++) {
|
|
769
|
+
if (tags[i] == tag) {
|
|
770
|
+
this.unsubscribe(tag)
|
|
771
|
+
this.removeAllListeners(tag)
|
|
772
|
+
linkSet.delete(tag)
|
|
773
|
+
this.linkMap.set(to, linkSet)
|
|
774
|
+
break;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// console.log('unlink linkMap result:', this.linkMap )
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
unlinkAll(to) {
|
|
782
|
+
if (typeof to !== 'string') throw TypeError('to(local link target) is not a string.')
|
|
783
|
+
if (!this.linkMap.has(to)) return;
|
|
784
|
+
|
|
785
|
+
let linkSet = this.linkMap.get(to)
|
|
786
|
+
let tags = Array.from(linkSet)
|
|
787
|
+
for (let i = 0; i < tags.length; i++) {
|
|
788
|
+
this.unsubscribe(tags[i])
|
|
789
|
+
this.removeAllListeners(tags[i])
|
|
790
|
+
linkSet.delete(tags[i])
|
|
791
|
+
}
|
|
792
|
+
this.linkMap.delete(to)
|
|
793
|
+
|
|
794
|
+
// console.log('unlinkAll linkMap result:', this.linkMap )
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
getMetric() {
|
|
800
|
+
return {
|
|
801
|
+
tx: this.txCounter,
|
|
802
|
+
rx: this.rxCounter,
|
|
803
|
+
txb: this.txBytes,
|
|
804
|
+
rxb: this.rxBytes,
|
|
805
|
+
last: (Date.now() - this.lastTxRxTime) / 1000
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
getState() {
|
|
811
|
+
return this.state
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
getStateName() {
|
|
815
|
+
//state <number>
|
|
816
|
+
//value of constant STATES.NAME < number >
|
|
817
|
+
//type of constant STATES.NAME name < string uppercase >
|
|
818
|
+
//stateName,eventName <string lowercase>
|
|
819
|
+
return (STATES[this.state]).toLowerCase()
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
getSecurity() {
|
|
823
|
+
return {
|
|
824
|
+
useAuth: this.useAuth,
|
|
825
|
+
isTLS: this.TLS,
|
|
826
|
+
isAuthorized: this.boho.isAuthorized,
|
|
827
|
+
encMode: this.encMode,
|
|
828
|
+
usingEncryption: this.getEncryptionMode()
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
stateChange(state, emitEventAndMessage) {
|
|
833
|
+
// STATES constant name : string upperCase
|
|
834
|
+
// eventName, .stateName : string lowerCase
|
|
835
|
+
// .state : number
|
|
836
|
+
let eventName = state.toLowerCase()
|
|
837
|
+
this.state = STATES[state.toUpperCase()] // state: number
|
|
838
|
+
if (emitEventAndMessage) this.emit(eventName, emitEventAndMessage)
|
|
839
|
+
|
|
840
|
+
if (this.stateName !== eventName) {
|
|
841
|
+
// console.log(`state: ${this.stateName} => ${eventName}` )
|
|
842
|
+
this.stateName = eventName
|
|
843
|
+
this.emit('change', eventName)
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
|
|
850
|
+
|
|
851
|
+
|