vrpc 3.7.0 → 3.8.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 +113 -37
- package/browser/vrpc.js +2 -2
- package/package.json +5 -5
- package/vrpc/VrpcAgent.js +17 -9
- package/vrpc/VrpcBrowserAgent.js +16 -8
- package/vrpc/VrpcClient.js +54 -16
- package/vrpc/VrpcPersistor.js +20 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vrpc",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.8.0",
|
|
4
4
|
"description": "Variadic Remote Procedure Calls",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"directories": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"test": "npm run test:adapter && npm run test:agent && npm run test:client && npm run test:persistor && npm run test:production",
|
|
16
16
|
"test:adapter": "./node_modules/.bin/mocha tests/adapter/*.js --exit",
|
|
17
17
|
"test:native": "./node_modules/.bin/mocha tests/native/*.js --exit",
|
|
18
|
-
"test:agent": "tests/agent/test.sh"
|
|
18
|
+
"test:agent": "tests/agent/test.sh",
|
|
19
19
|
"test:client": "tests/client/test.sh",
|
|
20
20
|
"test:performance": "tests/performance/test.sh",
|
|
21
21
|
"test:persistor": "./node_modules/.bin/mocha tests/persistor/*.js --timeout 30000 --exit",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
},
|
|
48
48
|
"homepage": "https://docs.heisenware.com/developers/vrpc",
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@heisenware/storage": "^
|
|
50
|
+
"@heisenware/storage": "^3.0.2",
|
|
51
51
|
"chai": "^4.3.10",
|
|
52
52
|
"crypto-browserify": "^3.12.1",
|
|
53
53
|
"dockerode": "^4.0.9",
|
|
@@ -78,11 +78,11 @@
|
|
|
78
78
|
"process": "^0.11.10"
|
|
79
79
|
},
|
|
80
80
|
"peerDependencies": {
|
|
81
|
-
"@heisenware/storage": "^1.2.2"
|
|
81
|
+
"@heisenware/storage": "^1.2.2 || ^2.0.0 || ^3.0.0"
|
|
82
82
|
},
|
|
83
83
|
"peerDependenciesMeta": {
|
|
84
84
|
"@heisenware/storage": {
|
|
85
85
|
"optional": true
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
|
-
}
|
|
88
|
+
}
|
package/vrpc/VrpcAgent.js
CHANGED
|
@@ -23,7 +23,7 @@ class VrpcAgent extends EventEmitter {
|
|
|
23
23
|
* @param {String} defaults.agent This agent's name
|
|
24
24
|
* @param {String} defaults.username MQTT username (if no token is used)
|
|
25
25
|
* @param {String} defaults.password MQTT password (if no token is provided)
|
|
26
|
-
* @param {String} defaults.token Access token
|
|
26
|
+
* @param {String} defaults.token Access token
|
|
27
27
|
* @param {String} defaults.broker Broker url in form: `<scheme>://<host>:<port>`
|
|
28
28
|
* @param {String} defaults.version The (user-defined) version of this agent
|
|
29
29
|
* @returns {Agent} Agent instance
|
|
@@ -641,11 +641,13 @@ class VrpcAgent extends EventEmitter {
|
|
|
641
641
|
}
|
|
642
642
|
|
|
643
643
|
_handleClientInfoMessage (topic, json) {
|
|
644
|
-
//
|
|
645
|
-
|
|
644
|
+
// A client connection went offline. The topic carries the connection
|
|
645
|
+
// id (unique per VrpcClient instance) - the key of all bookkeeping -
|
|
646
|
+
// so ending one connection never touches others sharing its identity.
|
|
647
|
+
const connectionId = topic.slice(0, -15) // /__clientInfo__ = 15
|
|
646
648
|
if (json.status === 'offline') {
|
|
647
|
-
VrpcAdapter._unregisterClient(
|
|
648
|
-
const entry = this._isolatedInstances.get(
|
|
649
|
+
VrpcAdapter._unregisterClient(connectionId)
|
|
650
|
+
const entry = this._isolatedInstances.get(connectionId)
|
|
649
651
|
if (entry) {
|
|
650
652
|
entry.forEach(instanceId => {
|
|
651
653
|
const json = { f: '__delete__', a: [instanceId], r: null }
|
|
@@ -655,8 +657,10 @@ class VrpcAgent extends EventEmitter {
|
|
|
655
657
|
}
|
|
656
658
|
})
|
|
657
659
|
}
|
|
658
|
-
this._mqttUnsubscribe(`${
|
|
659
|
-
|
|
660
|
+
this._mqttUnsubscribe(`${connectionId}/__clientInfo__`)
|
|
661
|
+
// clientId: the identity-derived id shared by all connections of one
|
|
662
|
+
// identity (undefined for clients < 3.8.0)
|
|
663
|
+
this.emit('clientGone', connectionId, { clientId: json.clientId })
|
|
660
664
|
}
|
|
661
665
|
}
|
|
662
666
|
|
|
@@ -825,10 +829,14 @@ class VrpcAgent extends EventEmitter {
|
|
|
825
829
|
/**
|
|
826
830
|
* Event 'clientGone'
|
|
827
831
|
*
|
|
828
|
-
* Emitted when a tracked VRPC client
|
|
832
|
+
* Emitted when a tracked VRPC client connection ended. Listeners receive
|
|
833
|
+
* the connection id (unique per VrpcClient instance) and an info object
|
|
834
|
+
* whose `clientId` is the identity-derived id shared by all connections
|
|
835
|
+
* of one identity (undefined for clients < 3.8.0).
|
|
829
836
|
*
|
|
830
837
|
* @event VrpcAgent#clientGone
|
|
831
|
-
* @type {String}
|
|
838
|
+
* @type {String} connectionId
|
|
839
|
+
* @type {Object} info
|
|
832
840
|
*/
|
|
833
841
|
|
|
834
842
|
module.exports = VrpcAgent
|
package/vrpc/VrpcBrowserAgent.js
CHANGED
|
@@ -495,11 +495,13 @@ class VrpcAgent extends EventEmitter {
|
|
|
495
495
|
}
|
|
496
496
|
|
|
497
497
|
_handleClientInfoMessage (topic, json) {
|
|
498
|
-
//
|
|
499
|
-
|
|
498
|
+
// A client connection went offline. The topic carries the connection
|
|
499
|
+
// id (unique per VrpcClient instance) - the key of all bookkeeping -
|
|
500
|
+
// so ending one connection never touches others sharing its identity.
|
|
501
|
+
const connectionId = topic.slice(0, -15) // /__clientInfo__ = 15
|
|
500
502
|
if (json.status === 'offline') {
|
|
501
|
-
VrpcAdapter._unregisterClient(
|
|
502
|
-
const entry = this._isolatedInstances.get(
|
|
503
|
+
VrpcAdapter._unregisterClient(connectionId)
|
|
504
|
+
const entry = this._isolatedInstances.get(connectionId)
|
|
503
505
|
if (entry) {
|
|
504
506
|
entry.forEach(instanceId => {
|
|
505
507
|
const json = { f: '__delete__', a: [instanceId], r: null }
|
|
@@ -509,8 +511,10 @@ class VrpcAgent extends EventEmitter {
|
|
|
509
511
|
}
|
|
510
512
|
})
|
|
511
513
|
}
|
|
512
|
-
this._mqttUnsubscribe(`${
|
|
513
|
-
|
|
514
|
+
this._mqttUnsubscribe(`${connectionId}/__clientInfo__`)
|
|
515
|
+
// clientId: the identity-derived id shared by all connections of one
|
|
516
|
+
// identity (undefined for clients < 3.8.0)
|
|
517
|
+
this.emit('clientGone', connectionId, { clientId: json.clientId })
|
|
514
518
|
}
|
|
515
519
|
}
|
|
516
520
|
|
|
@@ -679,10 +683,14 @@ class VrpcAgent extends EventEmitter {
|
|
|
679
683
|
/**
|
|
680
684
|
* Event 'clientGone'
|
|
681
685
|
*
|
|
682
|
-
* Emitted when a tracked VRPC client
|
|
686
|
+
* Emitted when a tracked VRPC client connection ended. Listeners receive
|
|
687
|
+
* the connection id (unique per VrpcClient instance) and an info object
|
|
688
|
+
* whose `clientId` is the identity-derived id shared by all connections
|
|
689
|
+
* of one identity (undefined for clients < 3.8.0).
|
|
683
690
|
*
|
|
684
691
|
* @event VrpcAgent#clientGone
|
|
685
|
-
* @type {String}
|
|
692
|
+
* @type {String} connectionId
|
|
693
|
+
* @type {Object} info
|
|
686
694
|
*/
|
|
687
695
|
|
|
688
696
|
module.exports = VrpcAgent
|
package/vrpc/VrpcClient.js
CHANGED
|
@@ -74,7 +74,7 @@ class VrpcClient extends EventEmitter {
|
|
|
74
74
|
* @example
|
|
75
75
|
* const client = new VrpcClient({
|
|
76
76
|
* domain: 'vrpc',
|
|
77
|
-
* broker: '
|
|
77
|
+
* broker: 'mqtts://broker.hivemq.com:8883'
|
|
78
78
|
* })
|
|
79
79
|
*/
|
|
80
80
|
constructor ({
|
|
@@ -127,6 +127,7 @@ class VrpcClient extends EventEmitter {
|
|
|
127
127
|
this._instance = nanoid(8)
|
|
128
128
|
this._mqttClientId = mqttClientId || this._createMqttClientId()
|
|
129
129
|
this._vrpcClientId = this._createVrpcClientId()
|
|
130
|
+
this._vrpcConnectionId = this._createVrpcConnectionId()
|
|
130
131
|
this._agents = {}
|
|
131
132
|
this._eventEmitter = new EventEmitter()
|
|
132
133
|
this._invokeId = 0
|
|
@@ -147,7 +148,13 @@ class VrpcClient extends EventEmitter {
|
|
|
147
148
|
}
|
|
148
149
|
|
|
149
150
|
/**
|
|
150
|
-
* Provides
|
|
151
|
+
* Provides the identity-derived id of this client
|
|
152
|
+
*
|
|
153
|
+
* Every client constructed with the same domain, host and identity
|
|
154
|
+
* reports the same client id: it names the principal, not the
|
|
155
|
+
* connection. Agents receive it as `clientId` in the presence messages
|
|
156
|
+
* and can group the connections (e.g. browser tabs) of one identity by
|
|
157
|
+
* it. See getConnectionId() for the id that is unique per instance.
|
|
151
158
|
*
|
|
152
159
|
* @returns {String} clientId
|
|
153
160
|
*/
|
|
@@ -155,6 +162,21 @@ class VrpcClient extends EventEmitter {
|
|
|
155
162
|
return this._vrpcClientId
|
|
156
163
|
}
|
|
157
164
|
|
|
165
|
+
/**
|
|
166
|
+
* Provides the unique id of this connection
|
|
167
|
+
*
|
|
168
|
+
* Unique per VrpcClient instance: two clients sharing one identity still
|
|
169
|
+
* have distinct connection ids. Agents key all their bookkeeping by it
|
|
170
|
+
* (response routing, event listeners, isolated instances, presence), so
|
|
171
|
+
* one connection ending never disturbs the others of the same identity.
|
|
172
|
+
* Without an identity the connection id equals the client id.
|
|
173
|
+
*
|
|
174
|
+
* @returns {String} connectionId
|
|
175
|
+
*/
|
|
176
|
+
getConnectionId () {
|
|
177
|
+
return this._vrpcConnectionId
|
|
178
|
+
}
|
|
179
|
+
|
|
158
180
|
/**
|
|
159
181
|
* Actually connects to the MQTT broker.
|
|
160
182
|
*
|
|
@@ -184,8 +206,11 @@ class VrpcClient extends EventEmitter {
|
|
|
184
206
|
rejectUnauthorized: false,
|
|
185
207
|
connectTimeout: this._timeout,
|
|
186
208
|
will: {
|
|
187
|
-
topic: `${this.
|
|
188
|
-
payload: JSON.stringify({
|
|
209
|
+
topic: `${this._vrpcConnectionId}/__clientInfo__`,
|
|
210
|
+
payload: JSON.stringify({
|
|
211
|
+
status: 'offline',
|
|
212
|
+
clientId: this._vrpcClientId
|
|
213
|
+
})
|
|
189
214
|
}
|
|
190
215
|
}
|
|
191
216
|
if (username === undefined) delete options.username
|
|
@@ -236,7 +261,7 @@ class VrpcClient extends EventEmitter {
|
|
|
236
261
|
)
|
|
237
262
|
}
|
|
238
263
|
// Single wildcard catches BOTH RPC responses (length 3) AND events (length 4)
|
|
239
|
-
await this._mqttSubscribe(`${this.
|
|
264
|
+
await this._mqttSubscribe(`${this._vrpcConnectionId}/#`)
|
|
240
265
|
|
|
241
266
|
// Re-subscribe to all cached event topics
|
|
242
267
|
this._log.debug('Restoring event subscriptions after reconnect...')
|
|
@@ -409,7 +434,7 @@ class VrpcClient extends EventEmitter {
|
|
|
409
434
|
f: isIsolated ? '__createIsolated__' : '__createShared__',
|
|
410
435
|
a: [instance, ...args],
|
|
411
436
|
i: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
|
|
412
|
-
s: this.
|
|
437
|
+
s: this._vrpcConnectionId,
|
|
413
438
|
v: VRPC_PROTOCOL_VERSION
|
|
414
439
|
}
|
|
415
440
|
const proxy = await this._getProxy(agent, className, json)
|
|
@@ -462,7 +487,7 @@ class VrpcClient extends EventEmitter {
|
|
|
462
487
|
f: '__delete__',
|
|
463
488
|
a: [instance],
|
|
464
489
|
i: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
|
|
465
|
-
s: this.
|
|
490
|
+
s: this._vrpcConnectionId,
|
|
466
491
|
v: VRPC_PROTOCOL_VERSION
|
|
467
492
|
}
|
|
468
493
|
const topic = `${this._domain}/${agent}/${className}/__static__/__delete__`
|
|
@@ -502,7 +527,7 @@ class VrpcClient extends EventEmitter {
|
|
|
502
527
|
f: functionName,
|
|
503
528
|
a: wrapped,
|
|
504
529
|
i: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
|
|
505
|
-
s: this.
|
|
530
|
+
s: this._vrpcConnectionId,
|
|
506
531
|
v: VRPC_PROTOCOL_VERSION
|
|
507
532
|
}
|
|
508
533
|
const topic = `${this._domain}/${agent}/${className}/__static__/${functionName}`
|
|
@@ -545,7 +570,7 @@ class VrpcClient extends EventEmitter {
|
|
|
545
570
|
f: functionName,
|
|
546
571
|
a: [],
|
|
547
572
|
i: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
|
|
548
|
-
s: this.
|
|
573
|
+
s: this._vrpcConnectionId,
|
|
549
574
|
v: VRPC_PROTOCOL_VERSION
|
|
550
575
|
}
|
|
551
576
|
if (agent === '*') {
|
|
@@ -708,7 +733,7 @@ class VrpcClient extends EventEmitter {
|
|
|
708
733
|
/**
|
|
709
734
|
* Reconnects to the broker by using a different token
|
|
710
735
|
*
|
|
711
|
-
* @param {String} token Access token
|
|
736
|
+
* @param {String} token Access token
|
|
712
737
|
* @param {Object} [options]
|
|
713
738
|
* @param {String} options.agent Agent name. If not provided class default is used
|
|
714
739
|
* @returns {Promise} Promise that resolves once re-connected
|
|
@@ -752,8 +777,12 @@ class VrpcClient extends EventEmitter {
|
|
|
752
777
|
async end () {
|
|
753
778
|
if (!this._client) return
|
|
754
779
|
this._mqttPublish(
|
|
755
|
-
`${this.
|
|
756
|
-
JSON.stringify({
|
|
780
|
+
`${this._vrpcConnectionId}/__clientInfo__`,
|
|
781
|
+
JSON.stringify({
|
|
782
|
+
status: 'offline',
|
|
783
|
+
clientId: this._vrpcClientId,
|
|
784
|
+
v: VRPC_PROTOCOL_VERSION
|
|
785
|
+
})
|
|
757
786
|
)
|
|
758
787
|
this._eventEmitter.removeAllListeners()
|
|
759
788
|
this.removeAllListeners()
|
|
@@ -819,6 +848,14 @@ class VrpcClient extends EventEmitter {
|
|
|
819
848
|
return `${this._domain}/${os.hostname()}/${identity}`
|
|
820
849
|
}
|
|
821
850
|
|
|
851
|
+
_createVrpcConnectionId () {
|
|
852
|
+
// Without an identity the client id already is unique per instance.
|
|
853
|
+
// With one, the instance token makes every connection of that identity
|
|
854
|
+
// distinct while keeping the three-segment layout agents parse.
|
|
855
|
+
if (!this._identity) return this._vrpcClientId
|
|
856
|
+
return `${this._vrpcClientId}:${this._instance}`
|
|
857
|
+
}
|
|
858
|
+
|
|
822
859
|
_mqttPublish (topic, message, options) {
|
|
823
860
|
this._client.publish(
|
|
824
861
|
topic,
|
|
@@ -981,6 +1018,7 @@ class VrpcClient extends EventEmitter {
|
|
|
981
1018
|
const proxyId = `${this._instance}-${this._proxyId++}`
|
|
982
1019
|
const proxy = {
|
|
983
1020
|
vrpcClientId: this._vrpcClientId,
|
|
1021
|
+
vrpcConnectionId: this._vrpcConnectionId,
|
|
984
1022
|
vrpcInstanceId: instance,
|
|
985
1023
|
vrpcProxyId: proxyId
|
|
986
1024
|
}
|
|
@@ -1027,7 +1065,7 @@ class VrpcClient extends EventEmitter {
|
|
|
1027
1065
|
i: `${this._instance}-${
|
|
1028
1066
|
this._invokeId++ % Number.MAX_SAFE_INTEGER
|
|
1029
1067
|
}`,
|
|
1030
|
-
s: this.
|
|
1068
|
+
s: this._vrpcConnectionId,
|
|
1031
1069
|
v: VRPC_PROTOCOL_VERSION
|
|
1032
1070
|
}
|
|
1033
1071
|
this._mqttPublish(
|
|
@@ -1061,7 +1099,7 @@ class VrpcClient extends EventEmitter {
|
|
|
1061
1099
|
i: `${this._instance}-${
|
|
1062
1100
|
this._invokeId++ % Number.MAX_SAFE_INTEGER
|
|
1063
1101
|
}`,
|
|
1064
|
-
s: this.
|
|
1102
|
+
s: this._vrpcConnectionId,
|
|
1065
1103
|
v: VRPC_PROTOCOL_VERSION
|
|
1066
1104
|
}
|
|
1067
1105
|
this._mqttPublish(
|
|
@@ -1096,7 +1134,7 @@ class VrpcClient extends EventEmitter {
|
|
|
1096
1134
|
i: `${this._instance}-${
|
|
1097
1135
|
this._invokeId++ % Number.MAX_SAFE_INTEGER
|
|
1098
1136
|
}`,
|
|
1099
|
-
s: this.
|
|
1137
|
+
s: this._vrpcConnectionId,
|
|
1100
1138
|
v: VRPC_PROTOCOL_VERSION
|
|
1101
1139
|
}
|
|
1102
1140
|
this._mqttPublish(
|
|
@@ -1280,7 +1318,7 @@ class VrpcClient extends EventEmitter {
|
|
|
1280
1318
|
const topicId = VrpcClient._createHash(hashInput, 12)
|
|
1281
1319
|
|
|
1282
1320
|
// Route directly to the client's single event inbox wildcard
|
|
1283
|
-
const topic = `${this.
|
|
1321
|
+
const topic = `${this._vrpcConnectionId}/${topicId}`
|
|
1284
1322
|
const id = `__e__${topic}`
|
|
1285
1323
|
|
|
1286
1324
|
const handler = ({ a }) => callback.apply(null, a)
|
package/vrpc/VrpcPersistor.js
CHANGED
|
@@ -47,6 +47,9 @@ const VrpcAdapter = require('./VrpcAdapter')
|
|
|
47
47
|
* for an 'update' event on instances to persist their state after creation.
|
|
48
48
|
*
|
|
49
49
|
* @requires @heisenware/storage - This peer dependency must be installed.
|
|
50
|
+
* Storage 1.x (synchronous constructor) and >= 2.x (async `Storage.open`)
|
|
51
|
+
* are both supported; the layer is opened lazily and every operation waits
|
|
52
|
+
* for it.
|
|
50
53
|
*/
|
|
51
54
|
class VrpcPersistor {
|
|
52
55
|
/**
|
|
@@ -76,8 +79,8 @@ class VrpcPersistor {
|
|
|
76
79
|
.toLocaleLowerCase()
|
|
77
80
|
.replace(/[^a-zA-Z0-9]/g, '-')}`
|
|
78
81
|
|
|
79
|
-
this._storage =
|
|
80
|
-
this._isInitialized = this._init()
|
|
82
|
+
this._storage = null
|
|
83
|
+
this._isInitialized = this._init(Storage)
|
|
81
84
|
|
|
82
85
|
this._log.info(
|
|
83
86
|
`[VrpcPersistor] Persistence layer enabled. Storage path: ${this._dir}`
|
|
@@ -166,8 +169,12 @@ class VrpcPersistor {
|
|
|
166
169
|
* Initializes the persistor by attaching listeners to VRPC adapter events.
|
|
167
170
|
* @private
|
|
168
171
|
*/
|
|
169
|
-
async _init () {
|
|
172
|
+
async _init (Storage) {
|
|
170
173
|
try {
|
|
174
|
+
// Listeners go first and synchronously: an instance created right
|
|
175
|
+
// after construction must be caught. The operations they trigger wait
|
|
176
|
+
// for the storage layer opened below.
|
|
177
|
+
|
|
171
178
|
// Persist new instance creation
|
|
172
179
|
VrpcAdapter.on('create', async ({ instance, className, args }) => {
|
|
173
180
|
this._log.info(
|
|
@@ -211,6 +218,14 @@ class VrpcPersistor {
|
|
|
211
218
|
)
|
|
212
219
|
)
|
|
213
220
|
})
|
|
221
|
+
|
|
222
|
+
// storage >= 2 constructs through the async factory only; 1.x has a
|
|
223
|
+
// synchronous constructor. No watcher: this persistor is the sole
|
|
224
|
+
// writer of its directory.
|
|
225
|
+
this._storage =
|
|
226
|
+
typeof Storage.open === 'function'
|
|
227
|
+
? await Storage.open({ dir: this._dir, log: this._log, watch: false })
|
|
228
|
+
: new Storage({ dir: this._dir, log: this._log })
|
|
214
229
|
} catch (err) {
|
|
215
230
|
this._log.error(
|
|
216
231
|
`[VrpcPersistor] Could not initialize persistence layer: ${err.message}`
|
|
@@ -225,6 +240,7 @@ class VrpcPersistor {
|
|
|
225
240
|
* @private
|
|
226
241
|
*/
|
|
227
242
|
async _persist (id, className, args) {
|
|
243
|
+
await this._isInitialized
|
|
228
244
|
await this._storage.setItem(id, { className, args }, { folder: className })
|
|
229
245
|
}
|
|
230
246
|
|
|
@@ -233,6 +249,7 @@ class VrpcPersistor {
|
|
|
233
249
|
* @private
|
|
234
250
|
*/
|
|
235
251
|
async _delete (id) {
|
|
252
|
+
await this._isInitialized
|
|
236
253
|
await this._storage.removeItem(id)
|
|
237
254
|
}
|
|
238
255
|
}
|