iosignal 1.6.1 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +254 -247
  2. package/auth_file.json +13 -13
  3. package/auth_file.mjs +23 -23
  4. package/dist/iosignal.cjs +2 -2
  5. package/dist/iosignal.esm.js.map +1 -1
  6. package/dist/iosignal.min.js.map +1 -1
  7. package/index.js +19 -19
  8. package/indexWebBrowser.js +13 -13
  9. package/package.json +60 -59
  10. package/rollup.config.js +69 -69
  11. package/src/api/RedisAPI.js +65 -68
  12. package/src/api/api_constant.js +7 -7
  13. package/src/api/api_reply.js +31 -34
  14. package/src/api/api_sudo.js +91 -96
  15. package/src/auth/Auth_Env.js +76 -78
  16. package/src/auth/Auth_File.js +71 -75
  17. package/src/auth/Auth_Redis.js +44 -50
  18. package/src/auth/BohoAuth.js +164 -168
  19. package/src/client/CongPacket.js +141 -150
  20. package/src/client/IOCongSocket.js +81 -88
  21. package/src/client/IOCore.js +822 -851
  22. package/src/client/IOWS.js +80 -81
  23. package/src/client/IOWebSocket.js +111 -112
  24. package/src/common/constants.js +148 -159
  25. package/src/common/payload.js +81 -82
  26. package/src/common/quotaTable.js +68 -68
  27. package/src/common/util.js +61 -61
  28. package/src/server/FileLogger.js +30 -30
  29. package/src/server/Manager.js +433 -449
  30. package/src/server/Metric.js +155 -154
  31. package/src/server/Remote.js +207 -0
  32. package/src/server/{ServerRemoteCore.js → RemoteCore.js} +439 -467
  33. package/src/server/Server.js +163 -166
  34. package/src/server/serverOption.js +56 -56
  35. package/test-browser/favicon.ico +0 -0
  36. package/test-browser/index.html +96 -93
  37. package/test-browser/server.js +11 -11
  38. package/test-client-nodejs/client.cjs +17 -17
  39. package/test-client-nodejs/client.mjs +17 -17
  40. package/test-client-nodejs/client_api_reply.mjs +15 -15
  41. package/test-client-nodejs/stateChange.mjs +19 -19
  42. package/test-server/server-Auth_Env.js +19 -19
  43. package/test-server/server-Auth_File.js +12 -12
  44. package/test-server/server-api_reply.js +6 -6
  45. package/test-server/server-api_sudo.js +7 -7
  46. package/test-server/server-commonjs.cjs +8 -8
  47. package/test-server/server-esm.mjs +20 -19
  48. package/test-server/simple-server.mjs +11 -11
  49. package/test-signal-payloadType/signalPack.js +34 -34
  50. package/test-signal-payloadType/signaling.js +60 -60
  51. package/src/server/ServerRemote.js +0 -227
@@ -1,449 +1,433 @@
1
- import { MBP } from 'meta-buffer-pack'
2
- import { ServerRemote } from './ServerRemote.js'
3
- import { serverOption } from './serverOption.js'
4
- import { IOMsg, CLIENT_STATE } from '../common/constants.js'
5
- import { getSignalPack } from '../common/payload.js';
6
- import { FileLogger } from './FileLogger.js'
7
- import { Metric } from './Metric.js'
8
-
9
- const encoder = new TextEncoder()
10
- const decoder = new TextDecoder()
11
-
12
- export class Manager{
13
-
14
- constructor( server, authManager ) {
15
- this.server = server;
16
- this.txBytes = 0;
17
- this.rxBytes = 0;
18
-
19
- this.authManager = authManager;
20
- // TIP. authManager module is option.
21
- // if exist: used by ServerRemoteCore for the auth. process.
22
-
23
- this.connectionLogger;
24
- if (serverOption.fileLogger.connection.use) {
25
- this.connectionLogger = new FileLogger( serverOption.fileLogger.connection.path)
26
- console.log('Manager: begin file logger [connection]')
27
- }
28
-
29
- this.attackLogger;
30
- if( serverOption.fileLogger.attack.use ){
31
- this.attackLogger = new FileLogger( serverOption.fileLogger.attack.path )
32
- console.log('Manager: begin file logger.[attack]')
33
- }
34
-
35
- this.remotes = new Set(); // total ServerRemotes
36
- this.channel_map = new Map() // key: channelName value: < ServerRemote : Set >
37
- this.CHANNEL_PREFIX = 'CH:'
38
- this.CID_PREFIX = 'CID:'
39
- this.cid2remote = new Map(); // map:[ key:cid -> value:remote ]
40
- this.retain_messages = new Map() // key: tag, data: signalMessage
41
- this.metric = new Metric(this)
42
- this.lastSSID = 0;
43
-
44
- this.pingIntervalID = setInterval(e => {
45
- this.remotes.forEach(function each(remo) {
46
- let socket = remo.socket;
47
- if (socket.isAlive === false) {
48
- console.log('## timeout. cid:', remo.cid)
49
- remo.close();
50
- }
51
-
52
- socket.txCounter++;
53
- socket.isAlive = false;
54
- if (remo.socketType === 'websocket') {
55
- socket.ping();
56
- } else {
57
- remo.ping()
58
- }
59
- // console.log('<< PING')
60
- });
61
- }, serverOption.timeout);
62
-
63
- if (serverOption.showMetric) {
64
- this.monitIntervalID = setInterval((e) => {
65
- this.monitor()
66
- }, serverOption.monitorPeriod);
67
- }
68
-
69
-
70
- }
71
-
72
-
73
- addRemote(socket, req) {
74
- socket.isAlive = true;
75
- let remote = new ServerRemote(socket, req, this)
76
- this.remotes.add(remote)
77
- remote.send(Buffer.from([IOMsg.SERVER_READY]))
78
- remote.setState(CLIENT_STATE.SENT_SERVER_READY)
79
- this.lastSSID = remote.ssid;
80
- let connectionInfo = `+ IP:${remote.ip} #${remote.ssid} ${socket.socketType === 'websocket' ? "WS" : "CS"} `;
81
- // console.log( connectionInfo)
82
- if (this.connectionLogger) this.connectionLogger.log(connectionInfo)
83
- };
84
-
85
-
86
- removeRemote(remote) {
87
- // console.log('#### manager.removeRemote()', remote.cid, remote.uid)
88
- let remoteInfo = `- IP:${remote.ip} #${remote.ssid} cid:${remote.cid} ${remote?.socket.socketType === 'websocket' ? "WS" : "CS"} `
89
- if (this.connectionLogger) this.connectionLogger.log(remoteInfo)
90
- this.deligateSignal(remote, '@state', 'close')
91
- this.deligateSignal(remote, '@$state', 'close')
92
-
93
- // remove subscriber
94
- for (let ch of remote.channels.keys()) {
95
- if (this.channel_map.has(ch)) {
96
- const remotes = this.channel_map.get(ch)
97
- // console.log(`-- channel [ ${ch} ] removes subscriber id: ${remote.cid} `)
98
- remotes.delete(remote)
99
- if (remotes.size == 0) this.channel_map.delete(ch)
100
- }else{
101
- // console.log('no such a ch: ', ch )
102
- }
103
- }
104
-
105
- // retain message policy
106
- // type 1. keep if authUser, delete if anonymouse.
107
- if( remote.boho.isAuthorized ){
108
- //keep retain signal.
109
- }else{
110
- // anonymouse: remove the cid publish channel
111
- for (let ch of remote.retain_signal.keys()) {
112
- // console.log('cid retain_signal.keys:', ch )
113
- ch = this.CID_PREFIX + remote.cid + '@' + ch
114
- if (this.channel_map.has(ch)) {
115
- this.channel_map.delete(ch)
116
- // console.log(`-- deleted cid pub ch [ ${ch} ]. cid publisher id: ${remote.cid} `)
117
- }else{
118
- // console.log('no such a retain_signal tag: ', ch )
119
- }
120
- }
121
- }
122
-
123
-
124
- if( remote.uid && this.server.apiNames.has('account')){
125
- let req = { topic:'detachUserRemote'}
126
- req.args = ['caller:manager.removeRemote']
127
- this.server.emit('account', remote, req )
128
- }
129
-
130
- this.remotes.delete(remote)
131
- this.cid2remote.delete(remote.cid)
132
- remote = null
133
-
134
- }
135
-
136
-
137
- // unix wall like
138
- serverSignal(obj) {
139
- let sigPack = MBP.pack(
140
- MBP.MB('#MsgType', '8', IOMsg.SERVER_SIGNAL),
141
- MBP.MB('#signalObject', obj)
142
- )
143
-
144
- this.remotes.forEach(remote => {
145
- remote.send_enc_mode(sigPack)
146
- })
147
- }
148
-
149
- getSignalTag(buffer) {
150
- let tagLen = buffer.readUint8(1)
151
- let tagBuf = buffer.subarray(2, 2 + tagLen)
152
- let tag = decoder.decode(tagBuf)
153
- return tag
154
- }
155
-
156
- getNewSignalTagMessage(buffer, newTag) {
157
- let msgType = buffer[0]
158
- let tagLen = buffer.readUint8(1)
159
- let newTagBuf = encoder.encode(newTag)
160
- let newTagLen = newTagBuf.byteLength;
161
- let payloadCunk = buffer.subarray(2 + tagLen)
162
- let newBuffer = Buffer.concat([Buffer.from([msgType, newTagLen]), newTagBuf, payloadCunk])
163
- return newBuffer
164
- }
165
-
166
-
167
- // signaling to the cid subscribers.
168
- // example. sending cid close signal.
169
- deligateSignal(remote, tag, ...args) {
170
- let sigPack = getSignalPack(tag, ...args)
171
- this.sender(tag, remote, sigPack)
172
- }
173
-
174
- adminSignal( cid, message){
175
- if (this.cid2remote.has(cid)) {
176
- this.cid2remote.get(cid).send_enc_mode(message)
177
- // return ['ok', 1]
178
- return
179
- }else{
180
- return "no cid"
181
- }
182
- }
183
-
184
- serverSignalTo( tag, ...args ){
185
- let cid = tag.split('@')[0]
186
- let topic = tag.split('@')[1]
187
- let sigPack = getSignalPack( '@'+topic, ...args )
188
- // console.log('tag split', cid )
189
- if (cid && this.cid2remote.has(cid)) {
190
- let targetRemote = this.cid2remote.get(cid)
191
- console.log('target', targetRemote.state ,targetRemote.cid )
192
- targetRemote.send_enc_mode(sigPack)
193
- return
194
- }else{
195
- return "no cid"
196
- }
197
- }
198
-
199
- sender(tag, remote, message) {
200
- if(serverOption.memberOnly && !remote.boho.isAuthorized){
201
- // console.log("## MemberOnly, reject unAuthorized remote." )
202
- remote.send(Buffer.from([IOMsg.SERVER_CLEAR_AUTH]))
203
- remote.close()
204
- return ['err', 'not autrized']
205
- }
206
-
207
- if( tag.indexOf('$') == 0 ) return ['err', 'prefix $ is reserved for userSet tag.']
208
-
209
- let cidIndex = tag.indexOf('@');
210
- if (cidIndex === 0) {
211
- // ** CID_PUB multi-cast
212
- // [cid_pub] @topic , @$retainTopic
213
- // modify signalpack with cid_appneded tag.
214
- tag = remote.cid + tag;
215
- message = this.getNewSignalTagMessage(message, tag)
216
-
217
- // console.log('cid_pub new tag:', tag, this.getSignalTag(message ))
218
- } else if (cidIndex > 0) {
219
- // uni-cast.
220
- // console.log('unicast to:', tag)
221
- let targetCId = tag.split('@')[0];
222
- if (this.cid2remote.has(targetCId)) {
223
- //rm cid from tag.
224
- let ommitCIdTag = '@' + tag.split('@')[1]
225
- // console.log('uni-cast tag change from', tag, 'to' , ommitCIdTag )
226
- message = this.getNewSignalTagMessage(message, ommitCIdTag)
227
- // console.log(`origin tag: ${tag} omitTag: ${this.getSignalTag(message)}`)
228
- this.cid2remote.get(targetCId).send_enc_mode(message)
229
- return ['ok', 1]
230
- }
231
- return ['err', 'Invalid cid']
232
- // pub message from cid.
233
- // you already subscribe the cid.
234
- } else {
235
- // console.log('ch_pub tag:', tag)
236
- //else channel publish message
237
- }
238
-
239
-
240
- /**
241
- * multi-cast:
242
- * channel_publish
243
- * or cid_publish
244
- */
245
-
246
- // HOME_CHANNEL substitution.
247
- // (blank)#topic -> home_channel#topic.
248
- if (tag.indexOf('#') === 0) {
249
- tag = remote.HOME_CHANNEL + tag;
250
- } else if( tag.includes('@')){
251
- tag = this.CID_PREFIX + tag;
252
- } else {
253
- tag = this.CHANNEL_PREFIX + tag;
254
- }
255
-
256
- // retain signal message
257
- if (tag.includes('$')
258
- // && remote.boho.isAuthorized
259
- && serverOption.retain.isAvailable
260
- && serverOption.retain.limitCounter > this.retain_messages.size
261
- && serverOption.retain.limitSize > message.byteLength
262
- ) {
263
- // cid retain signal
264
- if (tag.includes('@')) {
265
- // cid retain signal stored inside remote.
266
- let retainTag = tag.split('@')[1]
267
- remote.retain_signal.set(retainTag, message)
268
- // console.log('## cid retain tag, size: ', retainTag, remote.retain_signal.size)
269
- } else {
270
- // channel retain signal stored inside manager.
271
- this.retain_messages.set(tag, message)
272
- // console.log('## channel retain tag, size: ', tag, this.retain_messages.size)
273
- }
274
-
275
- } else {
276
- // console.log('no retain. serverOption.retain:', serverOption.retain )
277
- }
278
-
279
- let sentCounter = 0;
280
- if (this.channel_map.has(tag)) {
281
- // console.log('raw channel_map matched tag:', tag)
282
- let remotes = this.channel_map.get(tag);
283
- if (remotes.size >= 1) {
284
- let limit;
285
- if (serverOption.useQuota.publishCounter) {
286
- limit = Math.min(remote.quota.publishCounter, remotes.size)
287
- } else {
288
- limit = remotes.size;
289
- }
290
- let peers = remotes.values();
291
- while (sentCounter < limit) {
292
- let c = peers.next().value
293
- if (!c) {
294
- // console.log('## null remote' )
295
- break;
296
- }
297
- if (c !== remote) {
298
- c.send_enc_mode(message);
299
- sentCounter++;
300
- }
301
- }
302
-
303
- if (serverOption.useQuota.publishCounter) {
304
- // console.log(`pub >> [${tag}] sent: ${sentCounter} [use quota limit: ${remote.quota.publishCounter }] total: ${remotes.size} subscribers. ` )
305
- } else {
306
- // console.log(`pub >> [${tag}] sent: ${sentCounter} [no quota limit] total: ${remotes.size} subscribers. ` )
307
- }
308
- return ['ok', sentCounter]
309
- }
310
- }
311
- // console.log('-- No subscriber. ch: ' , tag )
312
- return ['err', 'No subscriber.']
313
- }
314
-
315
-
316
-
317
- subscribe(chArr, remote) {
318
- // console.log('ChannelManager:: subscribe: ',chArr)
319
- chArr.forEach(tag => {
320
- if (tag.indexOf('#') === 0) {
321
- tag = remote.HOME_CHANNEL + tag
322
- } else if( tag.includes('@')){
323
- tag = this.CID_PREFIX + tag;
324
- } else {
325
- tag = this.CHANNEL_PREFIX + tag
326
- }
327
-
328
- // 1. set channel map
329
- if (this.channel_map.has(tag)) {
330
- this.channel_map.get(tag).add(remote)
331
- } else {
332
- this.channel_map.set(tag, new Set([remote]))
333
- }
334
- // console.log('Manager::map:', this.channel_map.keys() )
335
-
336
- // 2.add to remote channels.
337
- remote.channels.add(tag)
338
- // console.log('remote.channels set.', remote.channels )
339
-
340
- // 3. send retain message if available.
341
- if (tag.includes('$')
342
- && serverOption.retain.isAvailable
343
- ) {
344
- let retainMessage;
345
- if (tag.includes('@')) {
346
- let cid = tag.split('@')[0]
347
- cid = cid.split(this.CID_PREFIX)[1]
348
- let retainTag = tag.split('@')[1]
349
- if (this.cid2remote.has(cid)) {
350
- retainMessage = this.cid2remote.get(cid).retain_signal.get(retainTag)
351
- } else {
352
- // console.log('cid subscribe is rejected. not online:', cid)
353
- return;
354
- }
355
-
356
- } else if (this.retain_messages.has(tag)) {
357
- retainMessage = this.retain_messages.get(tag)
358
- }
359
- // console.log('send retainMessage buffer',retainMessage)
360
- if(retainMessage) remote.send_enc_mode(retainMessage)
361
-
362
- }
363
-
364
- })
365
- return remote.channels.size;
366
- }
367
-
368
- unsubscribe(chArr, remote) {
369
- //unsubscribe all channels of the remote.
370
- if (chArr.length == 1 && chArr[0] == "") {
371
-
372
- remote.channels.forEach(ch => {
373
- if (this.channel_map.has(ch)) {
374
- let remotes = this.channel_map.get(ch)
375
- remotes.delete(remote)
376
- if (remotes.size == 0) this.channel_map.delete(ch)
377
- }
378
- })
379
- remote.channels.clear();
380
-
381
- } else {
382
- // unsubscribe each channels.
383
- chArr.forEach(ch => {
384
- // substitution home_channel
385
- if (ch.indexOf('#') === 0) {
386
- ch = remote.HOME_CHANNEL + ch;
387
- } else if( ch.includes('@')){
388
- ch = this.CID_PREFIX + ch;
389
- } else {
390
- ch = this.CHANNEL_PREFIX + ch;
391
- }
392
- // console.log('-- Manager::unsubscribe:', ch )
393
-
394
- // delete manager map.
395
- if (this.channel_map.has(ch)) {
396
- let remotes = this.channel_map.get(ch)
397
- remotes.delete(remote)
398
- if (remotes.size == 0) this.channel_map.delete(ch)
399
- } else {
400
- // console.log('##no such a ch', ch )
401
- }
402
- remote.channels.delete(ch)
403
- })
404
- }
405
-
406
- // console.log( '-- result unsub:', remote.channels )
407
-
408
- }
409
-
410
-
411
- monitor() {
412
- if (process.stdout.isTTY) {
413
- process.stdout.cursorTo(0, 0)
414
- process.stdout.clearScreenDown()
415
- }
416
-
417
- if (serverOption.showChannel > 0) {
418
- this.metric.channels(serverOption.showChannel);
419
- }
420
- let mode = parseInt(serverOption.showMetric)
421
- console.log('monitor metric type:', mode)
422
- switch (mode) {
423
- case 1:
424
- this.metric.oneline(true);
425
- break;
426
- case 2:
427
- // this.metric.getCIdList(true);
428
- this.metric.getRemotes(true);
429
- break;
430
- case 3:
431
- this.metric.getChannelList(true);
432
- break;
433
- default:
434
- }
435
-
436
- }
437
-
438
-
439
- closeRemoteByCId(cid) {
440
- if (this.cid2remote.has(cid)) {
441
- this.cid2remote.get(cid).close()
442
- return 1
443
- } else {
444
- return 0
445
- }
446
- }
447
-
448
-
449
- }
1
+ import { MBP } from 'meta-buffer-pack'
2
+ import { Remote } from './Remote.js'
3
+ import { serverOption } from './serverOption.js'
4
+ import { IOMsg, CLIENT_STATE } from '../common/constants.js'
5
+ import { getSignalPack } from '../common/payload.js';
6
+ import { FileLogger } from './FileLogger.js'
7
+ import { Metric } from './Metric.js'
8
+
9
+ const encoder = new TextEncoder()
10
+ const decoder = new TextDecoder()
11
+
12
+ export class Manager {
13
+
14
+ constructor(server, authManager) {
15
+ this.server = server;
16
+ this.txBytes = 0;
17
+ this.rxBytes = 0;
18
+
19
+ this.authManager = authManager;
20
+
21
+ this.connectionLogger;
22
+ if (serverOption.fileLogger.connection.use) {
23
+ this.connectionLogger = new FileLogger(serverOption.fileLogger.connection.path)
24
+ // console.log('Manager: begin file logger [connection]')
25
+ }
26
+
27
+ this.attackLogger;
28
+ if (serverOption.fileLogger.attack.use) {
29
+ this.attackLogger = new FileLogger(serverOption.fileLogger.attack.path)
30
+ // console.log('Manager: begin file logger.[attack]')
31
+ }
32
+
33
+ this.remotes = new Set(); // total remotes
34
+ this.cid2remote = new Map(); // key: cid -> value: remote
35
+ this.retain_messages = new Map() // key: tag, data: signalMessage
36
+ this.channel_map = new Map() // key: channelName -> value: < remote : Set >
37
+ this.CHANNEL_PREFIX = 'CH:'
38
+ this.CID_PREFIX = 'CID:'
39
+ this.metric = new Metric(this)
40
+ this.lastSSID = 0;
41
+
42
+ this.pingIntervalID = setInterval(e => {
43
+ this.remotes.forEach(function each(remo) {
44
+ let socket = remo.socket;
45
+ if (socket.isAlive === false) {
46
+ console.log('## timeout. cid:', remo.cid)
47
+ remo.close();
48
+ }
49
+
50
+ socket.txCounter++;
51
+ socket.isAlive = false;
52
+ if (remo.socketType === 'websocket') {
53
+ socket.ping();
54
+ } else {
55
+ remo.ping()
56
+ }
57
+
58
+ });
59
+ }, serverOption.timeout);
60
+
61
+ if (serverOption.showMetric) {
62
+ this.monitIntervalID = setInterval((e) => {
63
+ this.monitor()
64
+ }, serverOption.monitorPeriod);
65
+ }
66
+
67
+
68
+ }
69
+
70
+
71
+ addRemote(socket, req) {
72
+ socket.isAlive = true;
73
+ let remote = new Remote(socket, req, this)
74
+ this.remotes.add(remote)
75
+ remote.send(Buffer.from([IOMsg.SERVER_READY]))
76
+ remote.setState(CLIENT_STATE.SENT_SERVER_READY)
77
+ this.lastSSID = remote.ssid;
78
+ let connectionInfo = `+ IP:${remote.ip} #${remote.ssid} ${socket.socketType === 'websocket' ? "WS" : "CS"} `;
79
+ // console.log( connectionInfo)
80
+ if (this.connectionLogger) this.connectionLogger.log(connectionInfo)
81
+ };
82
+
83
+
84
+ removeRemote(remote) {
85
+
86
+ let remoteInfo = `- IP:${remote.ip} #${remote.ssid} cid:${remote.cid} ${remote?.socket.socketType === 'websocket' ? "WS" : "CS"} `
87
+ if (this.connectionLogger) this.connectionLogger.log(remoteInfo)
88
+ this.deligateSignal(remote, '@state', 'close')
89
+ this.deligateSignal(remote, '@$state', 'close')
90
+
91
+ // remove subscriber
92
+ for (let ch of remote.channels.keys()) {
93
+ if (this.channel_map.has(ch)) {
94
+ const remotes = this.channel_map.get(ch)
95
+ // console.log(`-- channel [ ${ch} ] removes subscriber id: ${remote.cid} `)
96
+ remotes.delete(remote)
97
+ if (remotes.size == 0) this.channel_map.delete(ch)
98
+ } else {
99
+ // console.log('no such a ch: ', ch )
100
+ }
101
+ }
102
+
103
+ // retain message policy
104
+ // type 1. ( isAuthorizedUser) ? keep : delete.
105
+ if (remote.boho.isAuthorized) {
106
+ //keep retain signal.
107
+ } else {
108
+ // anonymouse: remove the cid publish channel
109
+ for (let ch of remote.retain_signal.keys()) {
110
+ // console.log('cid retain_signal.keys:', ch )
111
+ ch = this.CID_PREFIX + remote.cid + '@' + ch
112
+ if (this.channel_map.has(ch)) {
113
+ this.channel_map.delete(ch)
114
+ // console.log(`-- deleted cid pub ch [ ${ch} ]. cid publisher id: ${remote.cid} `)
115
+ } else {
116
+ // console.log('no such a retain_signal tag: ', ch )
117
+ }
118
+ }
119
+ }
120
+
121
+
122
+ if (remote.uid && this.server.apiNames.has('account')) {
123
+ let req = { topic: 'detachUserRemote' }
124
+ req.args = ['caller:manager.removeRemote']
125
+ this.server.emit('account', remote, req)
126
+ }
127
+
128
+ this.remotes.delete(remote)
129
+ this.cid2remote.delete(remote.cid)
130
+ remote = null
131
+
132
+ }
133
+
134
+
135
+ // it's like unix wall command.
136
+ serverSignal(obj) {
137
+ let sigPack = MBP.pack(
138
+ MBP.MB('#MsgType', '8', IOMsg.SERVER_SIGNAL),
139
+ MBP.MB('#signalObject', obj)
140
+ )
141
+
142
+ this.remotes.forEach(remote => {
143
+ remote.send_enc_mode(sigPack)
144
+ })
145
+ }
146
+
147
+ getSignalTag(buffer) {
148
+ let tagLen = buffer.readUint8(1)
149
+ let tagBuf = buffer.subarray(2, 2 + tagLen)
150
+ let tag = decoder.decode(tagBuf)
151
+ return tag
152
+ }
153
+
154
+ getNewSignalTagMessage(buffer, newTag) {
155
+ let msgType = buffer[0]
156
+ let tagLen = buffer.readUint8(1)
157
+ let newTagBuf = encoder.encode(newTag)
158
+ let newTagLen = newTagBuf.byteLength;
159
+ let payloadCunk = buffer.subarray(2 + tagLen)
160
+ let newBuffer = Buffer.concat([Buffer.from([msgType, newTagLen]), newTagBuf, payloadCunk])
161
+ return newBuffer
162
+ }
163
+
164
+
165
+ // signaling to the cid subscribers.
166
+ // example. sending cid close signal.
167
+ deligateSignal(remote, tag, ...args) {
168
+ let sigPack = getSignalPack(tag, ...args)
169
+ this.sender(tag, remote, sigPack)
170
+ }
171
+
172
+ adminSignal(cid, message) {
173
+ if (this.cid2remote.has(cid)) {
174
+ this.cid2remote.get(cid).send_enc_mode(message)
175
+ return
176
+ } else {
177
+ return "no cid"
178
+ }
179
+ }
180
+
181
+ serverSignalTo(tag, ...args) {
182
+ let cid = tag.split('@')[0]
183
+ let topic = tag.split('@')[1]
184
+ let sigPack = getSignalPack('@' + topic, ...args)
185
+ if (cid && this.cid2remote.has(cid)) {
186
+ let targetRemote = this.cid2remote.get(cid)
187
+ console.log('target', targetRemote.state, targetRemote.cid)
188
+ targetRemote.send_enc_mode(sigPack)
189
+ return
190
+ } else {
191
+ return "no cid"
192
+ }
193
+ }
194
+
195
+ sender(tag, remote, message) {
196
+ if (serverOption.memberOnly && !remote.boho.isAuthorized) {
197
+ // console.log("## MemberOnly, reject unAuthorized remote." )
198
+ remote.send(Buffer.from([IOMsg.SERVER_CLEAR_AUTH]))
199
+ remote.close()
200
+ return ['err', 'not autrized']
201
+ }
202
+
203
+ if (tag.indexOf('$') == 0) return ['err', 'prefix $ is reserved for userSet tag.']
204
+
205
+ let cidIndex = tag.indexOf('@');
206
+ if (cidIndex === 0) {
207
+ // ** CID_PUB multi-cast
208
+ // [cid_pub] @topic , @$retainTopic
209
+ // modify signalpack with cid_appneded tag.
210
+ tag = remote.cid + tag;
211
+ message = this.getNewSignalTagMessage(message, tag)
212
+
213
+ } else if (cidIndex > 0) {
214
+ // uni-cast.
215
+ let targetCId = tag.split('@')[0];
216
+ if (this.cid2remote.has(targetCId)) {
217
+ //rm cid from tag.
218
+ let ommitCIdTag = '@' + tag.split('@')[1]
219
+ // console.log('uni-cast tag change from', tag, 'to' , ommitCIdTag )
220
+ message = this.getNewSignalTagMessage(message, ommitCIdTag)
221
+ // console.log(`origin tag: ${tag} omitTag: ${this.getSignalTag(message)}`)
222
+ this.cid2remote.get(targetCId).send_enc_mode(message)
223
+ return ['ok', 1]
224
+ }
225
+ return ['err', 'Invalid cid']
226
+ } else {
227
+ // console.log('ch_pub tag:', tag)
228
+ //else channel publish message
229
+ }
230
+
231
+
232
+ /**
233
+ * multi-cast:
234
+ * channel_publish
235
+ * or cid_publish
236
+ */
237
+
238
+ // HOME_CHANNEL substitution.
239
+ // (blank)#topic -> home_channel#topic.
240
+ if (tag.indexOf('#') === 0) {
241
+ tag = remote.HOME_CHANNEL + tag;
242
+ } else if (tag.includes('@')) {
243
+ tag = this.CID_PREFIX + tag;
244
+ } else {
245
+ tag = this.CHANNEL_PREFIX + tag;
246
+ }
247
+
248
+ // retain signal message
249
+ if (tag.includes('$')
250
+ // && remote.boho.isAuthorized
251
+ && serverOption.retain.isAvailable
252
+ && serverOption.retain.limitCounter > this.retain_messages.size
253
+ && serverOption.retain.limitSize > message.byteLength
254
+ ) {
255
+ // cid retain signal
256
+ if (tag.includes('@')) {
257
+ // cid retain signal stored inside remote.
258
+ let retainTag = tag.split('@')[1]
259
+ remote.retain_signal.set(retainTag, message)
260
+ } else {
261
+ // channel retain signal stored inside manager.
262
+ this.retain_messages.set(tag, message)
263
+ }
264
+
265
+ } else {
266
+ // console.log('no retain. serverOption.retain:', serverOption.retain )
267
+ }
268
+
269
+ let sentCounter = 0;
270
+ if (this.channel_map.has(tag)) {
271
+ // console.log('raw channel_map matched tag:', tag)
272
+ let remotes = this.channel_map.get(tag);
273
+ if (remotes.size >= 1) {
274
+ let limit;
275
+ if (serverOption.useQuota.publishCounter) {
276
+ limit = Math.min(remote.quota.publishCounter, remotes.size)
277
+ } else {
278
+ limit = remotes.size;
279
+ }
280
+ let peers = remotes.values();
281
+ while (sentCounter < limit) {
282
+ let c = peers.next().value
283
+ if (!c) {
284
+ // console.log('## null remote' )
285
+ break;
286
+ }
287
+ if (c !== remote) {
288
+ c.send_enc_mode(message);
289
+ sentCounter++;
290
+ }
291
+ }
292
+
293
+ if (serverOption.useQuota.publishCounter) {
294
+ // console.log(`pub >> [${tag}] sent: ${sentCounter} [use quota limit: ${remote.quota.publishCounter }] total: ${remotes.size} subscribers. ` )
295
+ } else {
296
+ // console.log(`pub >> [${tag}] sent: ${sentCounter} [no quota limit] total: ${remotes.size} subscribers. ` )
297
+ }
298
+ return ['ok', sentCounter]
299
+ }
300
+ }
301
+ // console.log('-- No subscriber. ch: ' , tag )
302
+ return ['err', 'No subscriber.']
303
+ }
304
+
305
+
306
+
307
+ subscribe(chArr, remote) {
308
+ chArr.forEach(tag => {
309
+ if (tag.indexOf('#') === 0) {
310
+ tag = remote.HOME_CHANNEL + tag
311
+ } else if (tag.includes('@')) {
312
+ tag = this.CID_PREFIX + tag;
313
+ } else {
314
+ tag = this.CHANNEL_PREFIX + tag
315
+ }
316
+
317
+ // 1. set channel map
318
+ if (this.channel_map.has(tag)) {
319
+ this.channel_map.get(tag).add(remote)
320
+ } else {
321
+ this.channel_map.set(tag, new Set([remote]))
322
+ }
323
+
324
+ // 2.add to remote channels.
325
+ remote.channels.add(tag)
326
+
327
+ // 3. send retain message if available.
328
+ if (tag.includes('$')
329
+ && serverOption.retain.isAvailable
330
+ ) {
331
+ let retainMessage;
332
+ if (tag.includes('@')) {
333
+ let cid = tag.split('@')[0]
334
+ cid = cid.split(this.CID_PREFIX)[1]
335
+ let retainTag = tag.split('@')[1]
336
+ if (this.cid2remote.has(cid)) {
337
+ retainMessage = this.cid2remote.get(cid).retain_signal.get(retainTag)
338
+ } else {
339
+ // console.log('cid subscribe is rejected. not online:', cid)
340
+ return;
341
+ }
342
+
343
+ } else if (this.retain_messages.has(tag)) {
344
+ retainMessage = this.retain_messages.get(tag)
345
+ }
346
+ if (retainMessage) remote.send_enc_mode(retainMessage)
347
+
348
+ }
349
+
350
+ })
351
+ return remote.channels.size;
352
+ }
353
+
354
+ unsubscribe(chArr, remote) {
355
+ //unsubscribe all channels of the remote.
356
+ if (chArr.length == 1 && chArr[0] == "") {
357
+
358
+ remote.channels.forEach(ch => {
359
+ if (this.channel_map.has(ch)) {
360
+ let remotes = this.channel_map.get(ch)
361
+ remotes.delete(remote)
362
+ if (remotes.size == 0) this.channel_map.delete(ch)
363
+ }
364
+ })
365
+ remote.channels.clear();
366
+
367
+ } else {
368
+ // unsubscribe each channels.
369
+ chArr.forEach(ch => {
370
+ // substitution home_channel
371
+ if (ch.indexOf('#') === 0) {
372
+ ch = remote.HOME_CHANNEL + ch;
373
+ } else if (ch.includes('@')) {
374
+ ch = this.CID_PREFIX + ch;
375
+ } else {
376
+ ch = this.CHANNEL_PREFIX + ch;
377
+ }
378
+ // console.log('-- Manager::unsubscribe:', ch )
379
+
380
+ // delete manager map.
381
+ if (this.channel_map.has(ch)) {
382
+ let remotes = this.channel_map.get(ch)
383
+ remotes.delete(remote)
384
+ if (remotes.size == 0) this.channel_map.delete(ch)
385
+ } else {
386
+ // console.log('##no such a ch', ch )
387
+ }
388
+ remote.channels.delete(ch)
389
+ })
390
+ }
391
+
392
+ // console.log( '-- result unsub:', remote.channels )
393
+
394
+ }
395
+
396
+
397
+ monitor() {
398
+ if (process.stdout.isTTY) {
399
+ process.stdout.cursorTo(0, 0)
400
+ process.stdout.clearScreenDown()
401
+ }
402
+
403
+ if (serverOption.showChannel > 0) {
404
+ this.metric.channels(serverOption.showChannel);
405
+ }
406
+ let mode = parseInt(serverOption.showMetric)
407
+ switch (mode) {
408
+ case 1:
409
+ this.metric.oneline(true);
410
+ break;
411
+ case 2:
412
+ this.metric.getRemotes(true);
413
+ break;
414
+ case 3:
415
+ this.metric.getChannelList(true);
416
+ break;
417
+ default:
418
+ }
419
+
420
+ }
421
+
422
+
423
+ closeRemoteByCId(cid) {
424
+ if (this.cid2remote.has(cid)) {
425
+ this.cid2remote.get(cid).close()
426
+ return 1
427
+ } else {
428
+ return 0
429
+ }
430
+ }
431
+
432
+
433
+ }