iosignal 5.2.0 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +163 -0
  2. package/dist/browser/esm/io.d.ts +17 -12
  3. package/dist/browser/esm/io.js +5 -5
  4. package/dist/browser/esm/io.js.map +1 -1
  5. package/dist/browser/iife/io.js +5 -5
  6. package/dist/browser/iife/io.js.map +1 -1
  7. package/dist/node/iosignal.cjs +195 -550
  8. package/dist/node/iosignal.js +193 -548
  9. package/dist/types/client/IOCore.d.ts +11 -5
  10. package/dist/types/client/browser/IOWebSocket.d.ts +4 -4
  11. package/dist/types/common/payload.d.ts +2 -2
  12. package/package.json +22 -4
  13. package/.vscode/settings.json +0 -3
  14. package/index.js +0 -26
  15. package/rollup.config.js +0 -59
  16. package/src/auth/BohoAuth.js +0 -196
  17. package/src/auth/key_providers/FileKeyProvider.js +0 -59
  18. package/src/auth/key_providers/RedisKeyProvider.js +0 -46
  19. package/src/auth/key_providers/StringKeyProvider.js +0 -54
  20. package/src/client/CongPacket.js +0 -143
  21. package/src/client/IOCongSocket.js +0 -92
  22. package/src/client/IOCore.js +0 -1202
  23. package/src/client/IOWS.js +0 -78
  24. package/src/client/browser/IOWebSocket.js +0 -202
  25. package/src/common/constants.js +0 -210
  26. package/src/common/payload.js +0 -82
  27. package/src/common/quotaTable.js +0 -68
  28. package/src/common/util.js +0 -64
  29. package/src/server/FileLogger.js +0 -30
  30. package/src/server/Manager.js +0 -450
  31. package/src/server/Metrics.js +0 -155
  32. package/src/server/Remote.js +0 -216
  33. package/src/server/RemoteCore.js +0 -427
  34. package/src/server/Server.js +0 -236
  35. package/src/server/serverOption.js +0 -56
  36. package/src/services/RedisService.js +0 -53
  37. package/src/services/constant.js +0 -7
  38. package/src/services/replyService.js +0 -39
  39. package/src/services/sudoService.js +0 -85
  40. package/tsconfig.json +0 -25
@@ -77,7 +77,7 @@ export class IOCore {
77
77
  * @type {Boho}
78
78
  */
79
79
  boho: Boho;
80
- serverTimeNonce: any;
80
+ serverTimeNonce: import("buffer").Buffer;
81
81
  /**
82
82
  * Indicates if the connection is TLS (wss).
83
83
  * @type {boolean}
@@ -319,8 +319,8 @@ export class IOCore {
319
319
  */
320
320
  subscribe(tag: string): void;
321
321
  /**
322
- * Subscribes stored channels.
323
- * called client state become 'ready'
322
+ * Sends subscriptions stored by listen()/link() on each CID-ready transition,
323
+ * including reconnection. This automates subscription setup for simple clients.
324
324
  */
325
325
  subscribe_channels(): void;
326
326
  /**
@@ -330,7 +330,13 @@ export class IOCore {
330
330
  */
331
331
  unsubscribe(tag?: string): void;
332
332
  /**
333
- * Listens for signals on a specific tag.
333
+ * Convenience API for simple clients (for example, CLI tools): register a
334
+ * tag handler once and remember its subscription in channels. Register before
335
+ * connection readiness; the CID-ready flow subscribes on initial connection
336
+ * and again after reconnect, without application-level ready/subscribe code.
337
+ * This does not send a subscription immediately, even if already ready.
338
+ * For precise subscription/send ordering or dynamic subscriptions, use
339
+ * on() with subscribe() in a ready handler instead. Direct signals use on('@').
334
340
  * @param {string} tag - The tag to listen on.
335
341
  * @param {Function} handler - The callback function to handle the signal.
336
342
  * @throws {TypeError} If tag is not a string, handler is not a function, or tag length is invalid.
@@ -408,5 +414,5 @@ export type SIZE_LIMIT = import("../common/constants.js").SIZE_LIMIT;
408
414
  export type ENC_MODE = import("../common/constants.js").ENC_MODE;
409
415
  export type STATE = import("../common/constants.js").STATE;
410
416
  export type quotaTable = any;
411
- export type Boho = import("boho").Boho;
412
417
  export type Buffer = import("boho").Buffer;
418
+ import Boho from "boho";
@@ -15,9 +15,9 @@ export default class IO extends IOCore {
15
15
  static binaryType: string;
16
16
  /**
17
17
  * The Boho library instance.
18
- * @type {Boho}
18
+ * @type {typeof Boho}
19
19
  */
20
- static Boho: Boho;
20
+ static Boho: typeof Boho;
21
21
  /**
22
22
  * The MBP (MessagePack-Boho) instance.
23
23
  * @type {MBP}
@@ -73,7 +73,7 @@ export default class IO extends IOCore {
73
73
  */
74
74
  socket_send(data: BufferSource): void;
75
75
  }
76
- export type Boho = import("boho").Boho;
77
- export type MBP = typeof import("meta-buffer-pack");
76
+ export type MBP = import("boho").MBP;
78
77
  export type Buffer = import("boho").Buffer;
79
78
  import { IOCore } from "../IOCore.js";
79
+ import Boho from 'boho';
@@ -1,6 +1,6 @@
1
- export function getSignalPack(tag: any, ...args: any[]): Buffer<ArrayBufferLike>;
1
+ export function getSignalPack(tag: any, ...args: any[]): import("buffer").Buffer;
2
2
  export function parsePayload(args: any): {
3
3
  type: number;
4
- buffer: Uint8Array<ArrayBuffer> | Buffer<ArrayBufferLike> | null | undefined;
4
+ buffer: Uint8Array<ArrayBuffer> | import("buffer").Buffer | null | undefined;
5
5
  };
6
6
  export function getPayloadFromSignalPack(signalPack: any): any;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iosignal",
3
- "version": "5.2.0",
3
+ "version": "6.1.0",
4
4
  "description": "iosignal",
5
5
  "type": "module",
6
6
  "exports": {
@@ -17,7 +17,11 @@
17
17
  "unpkg": "./dist/browser/iife/io.js",
18
18
  "scripts": {
19
19
  "build": "rollup -c && npm run build:types",
20
- "build:types": "tsc --project tsconfig.json"
20
+ "build:types": "tsc --project tsconfig.json",
21
+ "test": "node --test --test-timeout=15000 checks/*.test.mjs test/integration/*.test.mjs",
22
+ "verify": "node scripts/verify.mjs",
23
+ "prepack": "npm run verify",
24
+ "test:integration": "node --test --test-timeout=15000 test/integration/*.test.mjs"
21
25
  },
22
26
  "author": {
23
27
  "name": "Taeo Lee",
@@ -36,7 +40,7 @@
36
40
  "typescript": "^6.0.3"
37
41
  },
38
42
  "dependencies": {
39
- "boho": "^2.1.3",
43
+ "boho": "^2.2.0",
40
44
  "eventemitter3": "^5.0.4",
41
45
  "meta-buffer-pack": "^2.1.3",
42
46
  "ws": "^8.21.3"
@@ -47,7 +51,7 @@
47
51
  },
48
52
  "repository": {
49
53
  "type": "git",
50
- "url": "https://github.com/remocons/iosignal.git",
54
+ "url": "git+https://github.com/remocons/iosignal.git",
51
55
  "directory": "packages/iosignal"
52
56
  },
53
57
  "keywords": [
@@ -63,5 +67,19 @@
63
67
  "iot",
64
68
  "arduino",
65
69
  "auth"
70
+ ],
71
+ "files": [
72
+ "dist/browser/esm/io.d.ts",
73
+ "dist/browser/esm/io.js",
74
+ "dist/browser/esm/io.js.map",
75
+ "dist/browser/iife/io.js",
76
+ "dist/browser/iife/io.js.map",
77
+ "dist/node/iosignal.cjs",
78
+ "dist/node/iosignal.js",
79
+ "dist/types/client/IOCore.d.ts",
80
+ "dist/types/client/browser/IOWebSocket.d.ts",
81
+ "dist/types/common/constants.d.ts",
82
+ "dist/types/common/payload.d.ts",
83
+ "dist/types/common/quotaTable.d.ts"
66
84
  ]
67
85
  }
@@ -1,3 +0,0 @@
1
- {
2
- "liveServer.settings.port": 5501
3
- }
package/index.js DELETED
@@ -1,26 +0,0 @@
1
- import pkg from './package.json' with { type: 'json' };
2
- export const version = pkg.version;
3
- export { IOCongSocket } from './src/client/IOCongSocket.js'
4
- export { IOWS as IO } from './src/client/IOWS.js'
5
- export { pack, CongRx } from './src/client/CongPacket.js'
6
- export { Server } from './src/server/Server.js'
7
- export { serverOption } from './src/server/serverOption.js'
8
- export { FileLogger } from './src/server/FileLogger.js'
9
- export { getSignalPack, parsePayload, getPayloadFromSignalPack } from './src/common/payload.js'
10
- export * from './src/common/constants.js'
11
- export * from './src/common/util.js'
12
-
13
- // MBP
14
- export { default as MBP } from 'meta-buffer-pack'
15
-
16
- // boho auth
17
- export { default as Boho} from 'boho'
18
- export { BohoAuth } from './src/auth/BohoAuth.js'
19
- export { FileKeyProvider } from './src/auth/key_providers/FileKeyProvider.js'
20
- export { RedisKeyProvider } from './src/auth/key_providers/RedisKeyProvider.js'
21
- export { StringKeyProvider } from './src/auth/key_providers/StringKeyProvider.js'
22
-
23
- // services
24
- export * as replyService from './src/services/replyService.js'
25
- export * as sudoService from './src/services/sudoService.js'
26
- export { RedisService } from './src/services/RedisService.js'
package/rollup.config.js DELETED
@@ -1,59 +0,0 @@
1
- import resolve from '@rollup/plugin-node-resolve'
2
- import commonjs from '@rollup/plugin-commonjs'
3
- import json from '@rollup/plugin-json'
4
- import terser from '@rollup/plugin-terser'
5
- import dts from 'rollup-plugin-dts'
6
-
7
- export default [
8
- {
9
- input: './src/client/browser/IOWebSocket.js',
10
- output: [
11
- {
12
- file: './dist/browser/esm/io.js',
13
- format: 'es',
14
- sourcemap: true,
15
- },
16
- {
17
- file: './dist/browser/iife/io.js',
18
- format: 'iife',
19
- name: 'IO',
20
- sourcemap: true,
21
- }
22
- ],
23
- plugins: [
24
- resolve({
25
- preferBuiltins: false
26
- }),
27
- commonjs(),
28
- json(),
29
- terser()
30
- ]
31
- },
32
- {
33
- input: './src/client/browser/IOWebSocket.js',
34
- output: { file: './dist/browser/esm/io.d.ts', format: 'es' },
35
- plugins: [dts()]
36
- }
37
-
38
- ,{
39
- input: './index.js',
40
- output: [
41
- {
42
- file: './dist/node/iosignal.js',
43
- format: 'es'
44
- },
45
- {
46
- file: "./dist/node/iosignal.cjs",
47
- format: 'cjs'
48
- }
49
- ],
50
- plugins: [
51
- resolve({
52
- preferBuiltins: true
53
- }),
54
- json(),
55
- commonjs()
56
- ]
57
- }
58
-
59
- ]
@@ -1,196 +0,0 @@
1
- import MBP from 'meta-buffer-pack'
2
- import Boho from 'boho'
3
- import { quotaTable } from '../common/quotaTable.js'
4
- import { serverOption } from '../server/serverOption.js'
5
- import { IOMsg, STATE } from '../common/constants.js'
6
- import { FileLogger } from '../server/FileLogger.js'
7
-
8
- const decoder = new TextDecoder()
9
- export class BohoAuth {
10
- constructor(keyProvider) {
11
- if (!keyProvider) {
12
- throw new Error('BohoAuth must be initialized with a keyProvider.');
13
- }
14
- this.keyProvider = keyProvider;
15
- this.keepOldConnection = true;
16
- this.authLogger;
17
- if (serverOption.fileLogger.auth.use) {
18
- this.authLogger = new FileLogger(serverOption.fileLogger.auth.path)
19
- console.log('Auth: begin file logger.[auth]')
20
- }
21
- }
22
-
23
- send_auth_fail(peer, reason) {
24
- if (this.authLogger) {
25
- let peerInfo = `FAIL #${peer.ssid} reason:${reason} `
26
- this.authLogger.log(peerInfo)
27
- }
28
- // console.log('##### AUTH_FAIL reason: ', reason)
29
- peer.setState(STATE.AUTH_FAIL)
30
- // add some delay time.
31
- setTimeout(e => {
32
- peer.send(Buffer.from([Boho.BohoMsg.AUTH_FAIL]))
33
- }, serverOption.auth.delay_auth_fail)
34
- }
35
-
36
-
37
- async verify_auth_req(auth_req, peer) {
38
- try {
39
- //1. unpack
40
- let infoPack = MBP.unpack(auth_req, Boho.Meta.AUTH_REQ)
41
- if (!infoPack) {
42
- this.send_auth_fail(peer, 'unpack auth_pack fail');
43
- return
44
- }
45
-
46
- let id = ""
47
- if (infoPack.id8.includes(0)) {
48
- id = decoder.decode(infoPack.id8.subarray(0, infoPack.id8.indexOf(0)));
49
- } else {
50
- id = decoder.decode(infoPack.id8);
51
- }
52
-
53
- //2. get key of id from DB
54
- let authInfo = await this.keyProvider.getAuth(id)
55
- if (!authInfo) {
56
- this.send_auth_fail(peer, 'NO ID:' + id);
57
- return
58
- }
59
-
60
- if (serverOption.debug.showAuthInfo) {
61
- console.log('[debug]showAuthInfo', authInfo)
62
- }
63
-
64
- peer.boho.copy_id8(infoPack.id8)
65
- // type of key
66
- let authKey;
67
- const SHA256_HASH_BASE64_LEN = 44
68
- if (authInfo.key.length == SHA256_HASH_BASE64_LEN) {
69
- // hashed key
70
- authKey = Buffer.from(authInfo.key, 'base64')
71
- peer.boho.copy_key(authKey)
72
- } else {
73
- // plain key
74
- peer.boho.set_key(authInfo.key)
75
- }
76
-
77
- //3. check hmac
78
- let auth_res = peer.boho.verify_auth_req(infoPack)
79
- if (!auth_res) {
80
- this.send_auth_fail(peer, 'hmac dismatched');
81
- return
82
- }
83
-
84
- //4. get info
85
-
86
- //5. check duplicate login.
87
- if (peer.manager.cid2remote.has(authInfo.cid)) {
88
- let old = peer.manager.cid2remote.get(authInfo.cid)
89
- console.log('[WARN]DUPLICATE_LOGIN detected.', old.cid)
90
- old.ping(); // check the connection.
91
- if (old == peer) {
92
- console.log('## trying to RELOGIN after login.')
93
- return
94
- }
95
-
96
- let authClearSignal = MBP.pack(
97
- MBP.MB('#MsgType', '8', IOMsg.AUTH_CLEAR),
98
- MBP.MB('#reason', 'duplicate login.')
99
- )
100
- if (this.keepOldConnection) { // default true
101
- // keep old connection. reject new connection by sending auth_fail signal.
102
- // peer.send(authClearSignal)
103
- if( peer.socketType == 'websocket'){
104
- this.send_auth_fail(peer, 'duplicate login')
105
- }else{
106
- peer.close(); //arduino
107
- }
108
- } else {
109
- // accept new connection. stop the old connection by sending auth_clear signal.
110
- old.send(authClearSignal)
111
- peer.close()
112
- }
113
- return
114
- }
115
-
116
- //6. delete current (or anonymouse)cid if exist.
117
- if (peer.cid) {
118
- // console.log('if peer has cid', peer.cid)
119
- peer.manager.cid2remote.delete(peer.cid)
120
- }
121
-
122
- //7. setting info.
123
- peer.did = id
124
- peer.cid = authInfo.cid
125
- peer.nick = authInfo.cid // temporary: nick as cid
126
- if (authInfo.uid) peer.uid = authInfo.uid
127
-
128
- //8. setting quota level.
129
- let quotaLevel = serverOption.defaultQuotaIndex;
130
- if (authInfo.level) quotaLevel = authInfo.level;
131
- quotaLevel = parseInt(quotaLevel)
132
- let newQuota = quotaTable[quotaLevel];
133
- if (!newQuota) {
134
- let err = 'no index quotaTable for auth.level: ' + quotaLevel
135
- this.send_auth_fail(peer, err);
136
- return
137
- } else {
138
- peer.level = quotaLevel;
139
- peer.quota = newQuota;
140
- }
141
-
142
- if (peer.level === serverOption.adminLevel) {
143
- console.log('## LOGIN ADMIN CID:', peer.cid)
144
- peer.isAdmin = true;
145
- }
146
- // send quota.level
147
- peer.send(Buffer.from([IOMsg.QUOTA_LEVEL, quotaLevel]))
148
-
149
- //9. set cid2remote
150
- peer.manager.cid2remote.set(peer.cid, peer)
151
- //10. send ack.
152
- peer.send(auth_res)
153
- peer.setState(STATE.AUTH_RES)
154
- if (this.authLogger) {
155
- let peerInfo = `OK #${peer.ssid} cid: ${peer.cid} did:${peer.did}`
156
- if (peer.isAdmin) peerInfo = "#ADMIN# " + peerInfo
157
- this.authLogger.log(peerInfo)
158
- }
159
- return authInfo
160
- } catch (error) {
161
- console.log('verify_auth_req error', error)
162
- this.send_auth_fail(peer, '[BohoAuth]caught: unknown error:' + error);
163
- }
164
- }
165
-
166
- async getAuth(id) {
167
- return this.keyProvider.getAuth(id);
168
- }
169
-
170
- async getAuthIdList() {
171
- if (typeof this.keyProvider.getAuthIdList === 'function') {
172
- return this.keyProvider.getAuthIdList();
173
- }
174
- }
175
-
176
- async addAuth(id, keyStr, cid, level = 0) {
177
- if (typeof this.keyProvider.addAuth === 'function') {
178
- return this.keyProvider.addAuth(id, keyStr, cid, level);
179
- }
180
- }
181
-
182
- async delAuth(id) {
183
- if (typeof this.keyProvider.delAuth === 'function') {
184
- return this.keyProvider.delAuth(id);
185
- }
186
- }
187
-
188
- async save() {
189
- if (typeof this.keyProvider.save === 'function') {
190
- return this.keyProvider.save();
191
- }
192
- }
193
-
194
-
195
- }
196
-
@@ -1,59 +0,0 @@
1
- import { readFileSync } from 'fs'
2
- import Boho from 'boho'
3
- import path from 'path'
4
-
5
- export class FileKeyProvider {
6
- constructor(_path) {
7
- this.AUTH = new Map();
8
- let pathObj = path.parse(_path)
9
- this.path = path.resolve(_path)
10
- console.log('auth from file path:', this.path)
11
- let ext = pathObj.ext;
12
- if (ext.toLowerCase() == '.js') {
13
- // Read auth data from file
14
- console.log("#JS path:", this.path);
15
- this.loadAuthInfoFile_JS(this.path)
16
- } else if (ext.toLowerCase() == '.json') {
17
- console.log("#JSON path:", this.path);
18
- this.loadAuthInfoFile_JSON(this.path)
19
- } else {
20
- console.log('no authinfofile path.')
21
- }
22
- }
23
-
24
- //loaded when server start.
25
- loadAuthInfoFile_JS(path) {
26
- import(path).then((file) => {
27
- console.log(file.authInfo);
28
- file.authInfo.forEach(item => {
29
- this.addAuth(...item)
30
- })
31
- console.log('total AUTH INFO size: ', this.AUTH.size)
32
- }).catch(e => {
33
- console.log(e)
34
- })
35
- }
36
-
37
- loadAuthInfoFile_JSON(path) {
38
- let file = readFileSync(path)
39
- file = new TextDecoder().decode(file)
40
- let list = JSON.parse(file)
41
- list.forEach(item => {
42
- this.addAuth(...item)
43
- })
44
- console.log('total AUTH INFO size: ', this.AUTH.size)
45
- }
46
-
47
- async getAuth(id) {
48
- return this.AUTH.get(id)
49
- }
50
-
51
- async getAuthIdList() {
52
- return Array.from(this.AUTH.keys())
53
- }
54
-
55
- addAuth(id, keyStr, cid, level = 0) {
56
- let Base64hashKey = Buffer.from(Boho.sha256.hash(keyStr)).toString('base64')
57
- this.AUTH.set(id, { key: Base64hashKey, cid: cid, level: level })
58
- }
59
- }
@@ -1,46 +0,0 @@
1
- import Boho from 'boho'
2
- const DEVICE_PREFIX = "device:"
3
-
4
- export class RedisKeyProvider {
5
- constructor(redisClient) {
6
- if (!redisClient) {
7
- throw new Error("RedisKeyProvider constructor: no redisClient")
8
- }
9
- this.redis = redisClient
10
- }
11
-
12
- // get device key from DB. (for Boho auth.)
13
- async getAuth(id) {
14
- try {
15
- let result = await this.redis.hGetAll(DEVICE_PREFIX + id)
16
- if (result && result.key) return result
17
- else return null;
18
- } catch (error) {
19
- console.log('getAuth', error)
20
- }
21
- }
22
-
23
- //sudoService call
24
- async getAuthIdList() {
25
- let result = await this.redis.keys(DEVICE_PREFIX + '*')
26
- result = result.map(v => {
27
- return v.split(':')[1]
28
- })
29
- if (result) return result
30
- }
31
-
32
- // add device auth info
33
- async addAuth(id, keyStr, cid = '', level = 0) {
34
- let Base64hashKey = Buffer.from(Boho.sha256.hash(keyStr)).toString('base64')
35
- return this.redis.hSet(DEVICE_PREFIX + id, { 'key': Base64hashKey, 'cid': cid, 'level': level })
36
- }
37
-
38
- //sudoService call
39
- async delAuth(id) {
40
- return this.redis.del(DEVICE_PREFIX + id)
41
- }
42
-
43
- async save(id) {
44
- return this.redis.save()
45
- }
46
- }
@@ -1,54 +0,0 @@
1
- /**
2
- * StringKeyProvider.js
3
- *
4
- * authInfo <String>
5
- * id: max 8chars
6
- * key: ~44 base64 chars recomended
7
- * cid:
8
- * level: range: 0~255
9
- * separator: ',' for multiple devices.
10
- *
11
- * example => authInfo = 'id1.key1.cid1.255,id2.key2.cid2.200'
12
- */
13
-
14
- import Boho from 'boho'
15
-
16
- export class StringKeyProvider {
17
- constructor(authInfo) {
18
-
19
- if (!authInfo) {
20
- throw TypeError("StringKeyProvider constructor() missing authInfo.")
21
- }
22
-
23
- let id_keys = authInfo.split(',')
24
-
25
- this.AUTH = new Map();
26
-
27
- if (id_keys.length >= 1) {
28
- id_keys.forEach(v => {
29
- let [ did, key, cid, level ] = v.split('.')
30
- level = parseInt(level)
31
- if (did && key && cid && typeof level == 'number') {
32
- this.addAuth(did, key, cid, level)
33
- } else {
34
- throw TypeError("StringKeyProvider invalid authInfo.")
35
- }
36
- })
37
- } else {
38
- throw TypeError("StringKeyProvider invalid authInfo.")
39
- }
40
-
41
- }
42
-
43
- async getAuth(id) {
44
- return this.AUTH.get(id)
45
- }
46
- async getAuthIdList() {
47
- return Array.from(this.AUTH.keys())
48
- }
49
-
50
- addAuth(id, keyStr, cid, level = 0) {
51
- let Base64hashKey = Buffer.from(Boho.sha256.hash(keyStr)).toString('base64')
52
- this.AUTH.set(id, { key: Base64hashKey, cid: cid, level: level })
53
- }
54
- }