iosignal 3.1.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.ko.md +1 -1
  2. package/README.md +1 -225
  3. package/dist/io.d.ts +5 -3
  4. package/dist/io.js +4 -4
  5. package/dist/io.js.map +1 -1
  6. package/dist/io.min.js +4 -4
  7. package/dist/io.min.js.map +1 -1
  8. package/dist/iosignal.js +285 -182
  9. package/dist/types/client/IOCore.d.ts +5 -3
  10. package/examples/react-chat-js/dist/assets/index-e2KOTYW8.js +65 -0
  11. package/examples/react-chat-js/dist/index.html +1 -1
  12. package/examples/react-chat-js/package-lock.json +1 -1
  13. package/examples/server/index.js +62 -9
  14. package/examples/server/package-lock.json +833 -2
  15. package/examples/server/package.json +1 -0
  16. package/examples/server/public/index.html +26 -0
  17. package/examples/svelte-chat-js/package-lock.json +11 -95
  18. package/examples/svelte-chat-js/package.json +1 -0
  19. package/examples/svelte-chat-js/src/app.html +1 -0
  20. package/examples/svelte-chat-js/src/routes/+layout.svelte +0 -4
  21. package/examples/svelte-chat-js/src/routes/+page.svelte +5 -0
  22. package/examples/svelte-chat-js/svelte.config.js +10 -2
  23. package/index.js +2 -1
  24. package/package.json +1 -1
  25. package/src/api/RedisAPI.js +19 -31
  26. package/src/api/api_reply.js +14 -6
  27. package/src/api/api_sudo.js +2 -8
  28. package/src/auth/Auth_Redis.js +8 -3
  29. package/src/auth/BohoAuth.js +12 -12
  30. package/src/client/CongPacket.js +2 -0
  31. package/src/client/IOCore.js +28 -12
  32. package/src/server/Manager.js +18 -3
  33. package/src/server/Metric.js +1 -2
  34. package/src/server/Remote.js +5 -0
  35. package/src/server/RemoteCore.js +27 -34
  36. package/src/server/Server.js +139 -66
  37. package/src/server/serverOption.js +5 -4
  38. package/test-nodejs/client-playToggle.js +19 -0
@@ -27,15 +27,13 @@ export class RemoteCore {
27
27
  this.ssid = RemoteCore.ssid++; // ordered index number
28
28
  this.manager = manager;
29
29
 
30
- this.cid; // Communication Id
31
- this.did; // Device Id
32
- this.uid; // User Id
33
- this.nick = ""
30
+ this.cid = ''; // Communication Id
31
+ this.did = ''; // Device Id
32
+ this.uid = ''; // User Id
33
+ this.nick = ''
34
34
 
35
-
36
- this.lastEchoMessage = "N"
37
35
  this.privateNode = false
38
- this.HOME_CHANNEL = ""
36
+ this.HOME_CHANNEL = ''
39
37
  this.level = serverOption.defaultQuotaIndex;
40
38
  this.quota = quotaTable[this.level];
41
39
  this.isAdmin = false;
@@ -51,7 +49,8 @@ export class RemoteCore {
51
49
  setState(state) {
52
50
  this.state = state
53
51
  if (serverOption.debug.showAuthInfo) {
54
- this.stateLog.push(state)
52
+ // let s = state + ":" + CLIENT_STATE[ state]
53
+ this.stateLog.push( state )
55
54
  console.log(this.stateLog.join('>'))
56
55
  }
57
56
  }
@@ -66,7 +65,7 @@ export class RemoteCore {
66
65
 
67
66
 
68
67
  showMessageLog(message, isBinary) {
69
- let from = this.boho.isAuthorized ? `did: ${this.did} ${this.cid}@` : `${this.cid}@`
68
+ let from = this.boho.isAuthorized ? `[FROM]did: ${this.did} cid:${this.cid}` : `[FROM]cid:${this.cid}`
70
69
  if (isBinary) {
71
70
  let msgTypeName = IOMsg[message[0]]
72
71
  if (!msgTypeName) msgTypeName = Boho.BohoMsg[message[0]]
@@ -165,6 +164,7 @@ export class RemoteCore {
165
164
  case IOMsg.CID_REQ:
166
165
  if (this.state < CLIENT_STATE.SENT_SERVER_READY) {
167
166
  // Protocol violation: CID_REQ was sent before receiving the SERVER_READY signal.
167
+ console.log('CID_REQ before SERVER_READY')
168
168
  this.close()
169
169
  }
170
170
 
@@ -182,22 +182,16 @@ export class RemoteCore {
182
182
  this.setState(CLIENT_STATE.CID_READY)
183
183
  break;
184
184
 
185
-
186
- case IOMsg.ECHO: // TEXT only
187
- try {
188
- let msg = decoder.decode(message.subarray(1))
189
- this.lastEchoMessage = msg
190
- } catch (error) {
191
- // console.log('ECHO message is not a text')
185
+ case IOMsg.ECHO:
186
+ if( message.byteLength < 30) {
187
+ this.send(message, isBinary)
192
188
  }
193
- this.send(message, isBinary)
194
189
  break;
195
190
 
196
191
  case IOMsg.IAM:
197
- if (message.byteLength > 1) {
198
- let iamInfo = message.subarray(1)
199
- this.nick = decoder.decode(iamInfo)
200
- console.log('iam nick reset', this.nick)
192
+ if (message.byteLength > 1 && message.byteLength < 30) {
193
+ // if iam request has a text message then reset the nick property.
194
+ this.nick = decoder.decode(message.subarray(1))
201
195
  }
202
196
  this.iamResponse()
203
197
  break;
@@ -218,7 +212,7 @@ export class RemoteCore {
218
212
 
219
213
  case IOMsg.UNSUBSCRIBE:
220
214
  if (message.byteLength == 2) {
221
- this.manager.unsubscribe([""], this)
215
+ this.manager.unsubscribe([''], this)
222
216
  } else if (message.byteLength >= 3) {
223
217
  let tagLen = message.readUInt8(1)
224
218
 
@@ -292,7 +286,7 @@ export class RemoteCore {
292
286
  case Boho.BohoMsg.AUTH_REQ:
293
287
  if (!this.manager.authManager) return
294
288
  if (this.state < CLIENT_STATE.SENT_SERVER_READY) {
295
- // console.log('protocol error. auth_req before server_ready')
289
+ console.log('protocol error. auth_req before server_ready')
296
290
  this.close();
297
291
  }
298
292
  this.setState(CLIENT_STATE.RECV_AUTH_REQ)
@@ -305,22 +299,23 @@ export class RemoteCore {
305
299
  case Boho.BohoMsg.AUTH_HMAC:
306
300
  if (!this.manager.authManager) return
307
301
  if (this.state < CLIENT_STATE.SENT_SERVER_NONCE) {
308
- // console.log('protocol error. auth_hmac before server_nonce')
302
+ console.log('protocol error. auth_hmac before server_nonce')
309
303
  this.close();
310
304
  }
311
305
  this.setState(CLIENT_STATE.RECV_AUTH_HMAC)
312
306
  //async
313
307
  this.manager.authManager.verify_auth_hmac(message, this)
314
308
  .then(authInfo => {
315
- if (authInfo) {
316
- this.manager.deligateSignal(remote, '@$name', authInfo.name)
309
+ if (authInfo?.name) {
310
+ this.manager.deligateSignal(this, '@$name', authInfo.name)
317
311
  // console.log('device login success authInfo', authInfo)
318
312
  // this.manager.sender('@$name', authInfo.name )
319
313
  }
320
314
 
315
+ // console.log('[VERIFY AUTH_HMAC] SubClass of BohoAuth return authInfo:', authInfo )
321
316
  })
322
317
  .catch(e => {
323
-
318
+ console.log('authInfo return fail.', e )
324
319
  })
325
320
 
326
321
  return;
@@ -348,23 +343,19 @@ export class RemoteCore {
348
343
  response(msgId, statusCode, body) {
349
344
  // console.log('response body', body)
350
345
  if (body) {
351
-
352
346
  this.send_enc_mode(MBP.pack(
353
347
  MB('#type', '8', IOMsg.RESPONSE_MBP),
354
348
  MB('status', '8', statusCode),
355
349
  MB('mid', '16', msgId),
356
350
  MB('body', body)
357
351
  ))
358
-
359
352
  } else {
360
353
  this.send_enc_mode(MBP.pack(
361
354
  MB('#type', '8', IOMsg.RESPONSE_MBP),
362
355
  MB('status', '8', statusCode),
363
356
  MB('mid', '16', msgId)
364
357
  ))
365
-
366
358
  }
367
-
368
359
  }
369
360
 
370
361
 
@@ -407,9 +398,9 @@ export class RemoteCore {
407
398
 
408
399
 
409
400
 
410
- iamResponse(info = "") {
401
+ iamResponse(info = '') {
411
402
 
412
- if (info == "") {
403
+ if (info == '') {
413
404
  let channels = []
414
405
  for (let tag of this.channels.keys()) {
415
406
  channels.push(tag)
@@ -417,15 +408,17 @@ export class RemoteCore {
417
408
 
418
409
  info = {
419
410
  "ssid": this.ssid,
420
- "uid": this.uid,
421
411
  "cid": this.cid,
422
412
  "did": this.did,
413
+ "uid": this.uid,
414
+ "level": this.level,
423
415
  "nick": this.nick,
424
416
  "ip": this.ip
425
417
  , 'tag': channels
426
418
  }
427
419
  }
428
420
 
421
+ // console.log( 'iam res info', info )
429
422
  let pack = MBP.pack(
430
423
  MB('#MsgType', '8', IOMsg.IAM_RES),
431
424
  MB('#info', info)
@@ -11,58 +11,67 @@ export class Server extends EventEmitter {
11
11
  super();
12
12
  this.apiNames = new Set()
13
13
  this.wss = {};
14
+ this.port = null;
15
+ this.congPort = null;
14
16
 
15
17
  if (options.timeout) {
16
18
  let pingT = parseInt(options.timeout)
17
19
  if (pingT && pingT >= 1000) serverOption.timeout = pingT
18
20
  }
19
-
20
21
  if (options.monitorPeriod) {
21
22
  let monitorT = parseInt(options.monitorPeriod)
22
23
  if (monitorT && monitorT >= 1000) serverOption.monitorPeriod = monitorT
23
24
  }
24
-
25
-
26
25
  if (options.showMessage) {
27
26
  serverOption.showMessage = options.showMessage
28
27
  }
29
-
30
28
  if (options.showMetric) {
31
29
  serverOption.showMetric = options.showMetric
32
30
  }
33
-
34
- if (options.timeout) {
35
- serverOption.timeout = options.timeout
31
+ if (options.membersOnly) {
32
+ serverOption.membersOnly = options.membersOnly
36
33
  }
37
34
 
38
- if (options.port) {
39
- serverOption.port = parseInt(options.port)
35
+
36
+ if (options.port || options.port == 0) {
37
+ this.port = parseInt(options.port)
40
38
  }
41
39
 
42
40
  if (options.httpServer) {
43
- serverOption.httpServer = options.httpServer
41
+ this.httpServer = options.httpServer
44
42
  }
45
43
 
46
- if (options.congPort) {
47
- serverOption.congPort = parseInt(options.congPort)
44
+ if (options.wsPath) {
45
+ this.wsPath = options.wsPath
46
+ }
47
+
48
+ if (options.congPort || options.congPort == 0) {
49
+ this.congPort = parseInt(options.congPort)
48
50
  }
49
51
 
50
52
  this.manager = new Manager(this, authManager)
51
53
 
52
- if (serverOption.port || serverOption.httpServer) this.startWSServer(serverOption)
53
- if (serverOption.congPort) this.startCongServer(serverOption.congPort)
54
+ this.serverCountToListen = 0;
55
+ this.listeningServerCount = 0;
56
+ if (this.port || this.port == 0 || this.httpServer) this.serverCountToListen++;
57
+ if (this.congPort || this.congPort == 0) this.serverCountToListen++;
54
58
 
55
- }
59
+ if (this.serverCountToListen === 0) {
60
+ throw new Error('Cannot create Server: At least one of "port", "httpServer", or "congPort" must be specified.');
61
+ } else {
62
+ if (this.port || this.port == 0 || this.httpServer) this.startWSServer();
63
+ if (this.congPort || this.congPort == 0) this.startCongServer();
64
+ }
56
65
 
66
+ }
57
67
 
58
- startWSServer(serverOption) {
59
68
 
60
- if (serverOption.port) {
61
- console.log('opening WebSocket Server port:', serverOption.port)
62
- this.wss = new WebSocketServer({ port: serverOption.port })
63
- } else if (serverOption.httpServer) {
64
- console.log('opening WebSocket Server external httpServer:')
65
- this.wss = new WebSocketServer({ server: serverOption.httpServer })
69
+ startWSServer() {
70
+ // console.log('optoins', options )
71
+ if (this.port || this.port == 0) {
72
+ this.wss = new WebSocketServer({ port: this.port , path: this.wsPath })
73
+ } else if (this.httpServer ) {
74
+ this.wss = new WebSocketServer({ server: this.httpServer, path: this.wsPath })
66
75
  } else {
67
76
  throw new TypeError(
68
77
  'One and only one of the "port", "httpServer"' +
@@ -70,9 +79,16 @@ export class Server extends EventEmitter {
70
79
  );
71
80
  }
72
81
 
82
+ this.wss.once('listening', () => {
83
+ this.port = this.wss.address().port;
84
+ console.log('wss server bound port:', this.port );
85
+ this.listeningServerCount++;
86
+ if (this.listeningServerCount === this.serverCountToListen) {
87
+ this.emit('ready');
88
+ }
89
+ });
73
90
 
74
91
  // this.wss.setMaxListeners(0)
75
-
76
92
  this.wss.on('error', (err) => {
77
93
  console.error('### ws server error:', err.message)
78
94
  if (err.code == 'EADDRINUSE') {
@@ -80,8 +96,8 @@ export class Server extends EventEmitter {
80
96
  }
81
97
  })
82
98
 
83
- this.wss.on('close', (err) => {
84
- console.log('### WS server closed.', err)
99
+ this.wss.on('close', () => {
100
+ console.log('### WS server is closed.')
85
101
  })
86
102
 
87
103
  this.wss.on('connection', (ws, req) => {
@@ -92,9 +108,7 @@ export class Server extends EventEmitter {
92
108
 
93
109
  }
94
110
 
95
- startCongServer(congPort) {
96
- console.log('opening CongSocket Server:', congPort)
97
-
111
+ startCongServer() {
98
112
  this.congServer = net.createServer((socket) => {
99
113
  this.manager.addRemote(socket)
100
114
  })
@@ -103,61 +117,120 @@ export class Server extends EventEmitter {
103
117
  if (err.code == 'EADDRINUSE') {
104
118
  process.exit()
105
119
  }
106
- }).listen(congPort, () => {
107
- // console.log('congsocket server bound' );
120
+ }).on('close', () => {
121
+ console.log('### congsocket server is closed.')
122
+ }).listen(this.congPort, () => {
123
+ this.congPort = this.congServer.address().port;
124
+ console.log('congsocket server bound port:', this.congPort );
125
+ this.listeningServerCount++;
126
+ if (this.listeningServerCount === this.serverCountToListen) {
127
+ this.emit('ready');
128
+ }
108
129
  });
109
130
  }
110
131
 
111
-
112
-
132
+ /**
133
+ * API register.
134
+ * target: api_name <string>
135
+ * api: api_module <function module|class instance>
136
+ * return this
137
+ */
113
138
  api(target, api) {
114
139
 
115
- this.apiNames.add(target)
116
-
140
+ if( typeof target !== 'string'){
141
+ throw new Error(`api( target ,api ) target name is not a string.`)
142
+ }
117
143
  if (!api.checkPermission || typeof api.checkPermission != 'function') {
118
- throw new Error('wrong api interface. no checkPermission function.')
144
+ throw new Error(`API ${target} : no checkPermission or not a function.`)
119
145
  }
120
-
121
-
122
- if (typeof api.request == 'function' && Array.isArray(api.commands)) {
123
- console.log(`API TYPE1. A Class with one request function. target: ${target} list: ${api.commands}`)
124
-
125
- this.on(target, (remote, req) => {
126
- if (api.checkPermission(remote, req)) {
127
- api.request(remote, req)
128
- } else {
129
- remote.response(req.mid, STATUS.ERROR, "NO_PERMISSION.")
146
+ if(!api.commands || !Array.isArray(api.commands) ){
147
+ throw new Error(`API ${target} : no commands or !Array.`)
148
+ }
149
+
150
+ // API TYPE 1. single request() function.
151
+ if ( api.request && typeof api.request == 'function' ) {
152
+ this.on(target, async (remote, req) => {
153
+ try {
154
+ if (!api.checkPermission(remote, req)) {
155
+ remote.response(req.mid, STATUS.ERROR, "NO_PERMISSION.")
156
+ return
157
+ }
158
+ if( api.commands.includes( req.topic )){
159
+ console.log('server api req', req )
160
+ await api.request(remote, req)
161
+ }else{
162
+ remote.response(req.mid, STATUS.ERROR, "UNKNOWN_COMMAND");
163
+ }
164
+ } catch (error) {
165
+ console.error(`Unhandled API Error in target [${target}]:`, error);
166
+ remote.response(req.mid, STATUS.ERROR, "INTERNAL_SERVER_ERROR");
130
167
  }
131
168
  })
132
-
133
169
  } else {
134
- let apiList = []
135
- Object.keys(api).forEach(v => {
136
- if (typeof api[v] === 'function') apiList.push(v)
137
- console.log('api list', typeof api[v], v)
138
- })
139
-
140
- console.log(`API TYPE2. Multiple function list. target: ${target} list:${apiList}`)
141
-
142
- this.on(target, (remote, req) => {
143
- let r;
144
- if (!api.checkPermission(remote, req)) {
145
- r = "NO_PERMISSION."
146
- } else {
147
- if (api[req.topic]) {
148
- console.log(`API TYPE2. request target: ${target} has topic:${req.topic}`)
149
- api[req.topic](remote, req)
170
+ // API TYPE 2. multiple functions.
171
+ this.on(target, async (remote, req) => {
172
+ try {
173
+ if (!api.checkPermission(remote, req)) {
174
+ remote.response(req.mid, STATUS.ERROR, "NO_PERMISSION.")
150
175
  return
151
- } else {
152
- r = `target: ${req.target} has not topic name: ${req.topic}`
153
176
  }
177
+ if( api.commands.includes( req.topic )){
178
+ await api[req.topic](remote, req)
179
+ }else{
180
+ remote.response(req.mid, STATUS.ERROR, "UNKNOWN_COMMAND");
181
+ }
182
+ } catch (error) {
183
+ console.error(`Unhandled API Error in target [${target}]:`, error);
184
+ remote.response(req.mid, STATUS.ERROR, "INTERNAL_SERVER_ERROR");
154
185
  }
186
+ })
187
+ }
188
+ this.apiNames.add(target)
189
+ console.log(`[API registed: ${target} ] accept commands: ${api.commands}`)
190
+ return this
191
+ }
155
192
 
156
- remote.response(req.mid, STATUS.ERROR, r)
193
+ close(callback) {
194
+ console.log('closing iosignal server...')
195
+ if (this.manager) {
196
+ this.manager.close()
197
+ }
198
+
199
+ this.apiNames.forEach(v => this.removeAllListeners(v))
200
+ this.apiNames.clear()
201
+
202
+ let serverCount = 0;
203
+ if (this.wss) serverCount++;
204
+ if (this.congServer) serverCount++;
205
+
206
+ if (serverCount === 0) {
207
+ if (callback) process.nextTick(callback);
208
+ return;
209
+ }
210
+
211
+ let closedCount = 0;
212
+ const onClosed = () => {
213
+ closedCount++;
214
+ if (closedCount === serverCount) {
215
+ console.log('IOSignal server is closed.')
216
+ if (callback) callback();
217
+ }
218
+ }
219
+
220
+ if (this.wss) {
221
+ this.wss.close(() => {
222
+ // console.log('wss server closed.')
223
+ onClosed()
157
224
  })
158
225
  }
159
226
 
160
- return this
227
+ if (this.congServer) {
228
+ this.congServer.close((err) => {
229
+ if (err) console.error('congServer close error', err)
230
+ // console.log('cong server closed.')
231
+ onClosed()
232
+ })
233
+ }
161
234
  }
162
235
 
163
236
  }
@@ -2,9 +2,10 @@
2
2
 
3
3
  export let serverOption = {
4
4
  clientTracking: false,
5
- port: 0, //wsPort
5
+ port: null, // WebSocket
6
+ congPort: null, // CongSocket
6
7
  httpServer: null,
7
- congPort: 0,
8
+ wsPath: null,
8
9
  timeout: 50000,
9
10
  showMessage: 'none',
10
11
  showMetric: 0,
@@ -46,10 +47,10 @@ export let serverOption = {
46
47
  },
47
48
 
48
49
  auth: {
49
- delay_auth_fail: 1000,
50
+ delay_auth_fail: 600,
50
51
  },
51
52
 
52
- memberOnly: false
53
+ membersOnly: false
53
54
 
54
55
  }
55
56
 
@@ -0,0 +1,19 @@
1
+
2
+ import { IO } from "iosignal"
3
+
4
+ const io = new IO('wss://io.iosignal.net/ws')
5
+
6
+ // io.auth('id','key')
7
+
8
+ io.on('ready', () => {
9
+ console.log('ready cid:', io.cid)
10
+ io.signal('#screen', 'playToggle')
11
+ });
12
+
13
+ io.listen('#notify', (...args) => {
14
+ console.log(args)
15
+ })
16
+
17
+ io.on('error', err => {
18
+ console.log('err', err)
19
+ })