vrpc 3.4.0 → 3.5.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.
- package/browser/vrpc.js +7 -1
- package/browser/vrpc.js.LICENSE.txt +1 -5
- package/package.json +15 -15
- package/vrpc/VrpcAdapter.js +3 -22
- package/vrpc/VrpcAgent.js +26 -16
- package/vrpc/VrpcBrowserAdapter.js +0 -5
- package/vrpc/VrpcBrowserAgent.js +23 -10
- package/vrpc/VrpcClient.js +220 -83
- package/vrpc/vrpc-agent-js +1 -1
package/vrpc/VrpcClient.js
CHANGED
|
@@ -38,7 +38,6 @@ SOFTWARE.
|
|
|
38
38
|
*/
|
|
39
39
|
|
|
40
40
|
const os = require('os')
|
|
41
|
-
const crypto = require('crypto')
|
|
42
41
|
const { nanoid } = require('nanoid')
|
|
43
42
|
const mqtt = require('mqtt')
|
|
44
43
|
const EventEmitter = require('events')
|
|
@@ -64,7 +63,7 @@ class VrpcClient extends EventEmitter {
|
|
|
64
63
|
* @param {String} [options.token] Access token
|
|
65
64
|
* @param {String} options.domain Sets the domain
|
|
66
65
|
* @param {String} [options.agent="*"] Sets default agent
|
|
67
|
-
* @param {String} [options.broker="mqtts://
|
|
66
|
+
* @param {String} [options.broker="mqtts://broker.hivemq.com:8883"] Broker url in form: `<scheme>://<host>:<port>`
|
|
68
67
|
* @param {Number} [options.timeout=12000] Maximum time in ms to wait for a RPC answer
|
|
69
68
|
* @param {Object} [options.log=console] Log object (must support debug, info, warn, and error level)
|
|
70
69
|
* @param {Boolean} [options.bestEffort=true] If true, message will be sent with best effort, i.e. no caching if offline
|
|
@@ -84,7 +83,7 @@ class VrpcClient extends EventEmitter {
|
|
|
84
83
|
username,
|
|
85
84
|
domain,
|
|
86
85
|
agent = '*',
|
|
87
|
-
broker = 'mqtts://
|
|
86
|
+
broker = 'mqtts://broker.hivemq.com:8883',
|
|
88
87
|
timeout = 12 * 1000,
|
|
89
88
|
log = 'console',
|
|
90
89
|
bestEffort = true,
|
|
@@ -135,6 +134,8 @@ class VrpcClient extends EventEmitter {
|
|
|
135
134
|
this._client = null
|
|
136
135
|
this._cachedSubscriptions = {}
|
|
137
136
|
this._proxies = {}
|
|
137
|
+
this._callbackIds = new WeakMap()
|
|
138
|
+
this._emitterListener = new WeakMap()
|
|
138
139
|
if (log === 'console') {
|
|
139
140
|
this._log = console
|
|
140
141
|
this._log.debug = () => {}
|
|
@@ -189,6 +190,19 @@ class VrpcClient extends EventEmitter {
|
|
|
189
190
|
if (password === undefined) delete options.password
|
|
190
191
|
this._client = mqtt.connect(this._broker, options)
|
|
191
192
|
|
|
193
|
+
// Persistent lifecycle handlers
|
|
194
|
+
this._client.on('offline', () => {
|
|
195
|
+
this._log.warn('MQTT client went offline.')
|
|
196
|
+
// Clear the cache to ensure a fresh state upon reconnection
|
|
197
|
+
this._agents = {}
|
|
198
|
+
this.emit('offline')
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
this._client.on('reconnect', () => {
|
|
202
|
+
this._log.info('MQTT client is attempting to reconnect...')
|
|
203
|
+
this.emit('reconnect')
|
|
204
|
+
})
|
|
205
|
+
|
|
192
206
|
this._client.on('error', err => {
|
|
193
207
|
this.emit('error', err)
|
|
194
208
|
})
|
|
@@ -214,75 +228,105 @@ class VrpcClient extends EventEmitter {
|
|
|
214
228
|
}
|
|
215
229
|
// RPC responses
|
|
216
230
|
this._mqttSubscribe(this._vrpcClientId)
|
|
231
|
+
|
|
232
|
+
// Re-subscribe to all cached event topics
|
|
233
|
+
this._log.info('Restoring event subscriptions after reconnect...')
|
|
234
|
+
for (const topic of Object.keys(this._cachedSubscriptions)) {
|
|
235
|
+
this._mqttSubscribe(topic)
|
|
236
|
+
}
|
|
237
|
+
|
|
217
238
|
this.emit('connect')
|
|
218
239
|
})
|
|
219
240
|
|
|
220
241
|
this._client.on('message', (topic, message) => {
|
|
221
242
|
if (message.length === 0) return
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
// AgentInfo message
|
|
229
|
-
if (klass === '__agentInfo__') {
|
|
230
|
-
const { status, hostname, version } = JSON.parse(message.toString())
|
|
231
|
-
this._createIfNotExist(agent)
|
|
232
|
-
this._agents[agent].status = status
|
|
233
|
-
this._agents[agent].hostname = hostname
|
|
234
|
-
this._agents[agent].version = version
|
|
235
|
-
if (status === 'offline') {
|
|
236
|
-
this._clearCachedSubscriptions({ lostAgent: agent })
|
|
243
|
+
try {
|
|
244
|
+
const tokens = topic.split('/')
|
|
245
|
+
const [domain, agent, klass, instance] = tokens
|
|
246
|
+
if (domain !== this._domain) {
|
|
247
|
+
this._log.warn(`Received message from foreign domain: ${domain}`)
|
|
248
|
+
return
|
|
237
249
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
this.
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
this.
|
|
250
|
+
// AgentInfo message
|
|
251
|
+
if (klass === '__agentInfo__') {
|
|
252
|
+
const { status, hostname, version } = JSON.parse(message.toString())
|
|
253
|
+
this._createIfNotExist(agent)
|
|
254
|
+
this._agents[agent].status = status
|
|
255
|
+
this._agents[agent].hostname = hostname
|
|
256
|
+
this._agents[agent].version = version
|
|
257
|
+
if (status === 'offline') {
|
|
258
|
+
this._clearCachedSubscriptions({ lostAgent: agent })
|
|
259
|
+
}
|
|
260
|
+
this.emit('agent', { domain, agent, status, hostname, version })
|
|
261
|
+
// ClassInfo message
|
|
262
|
+
} else if (
|
|
263
|
+
instance === '__classInfo__' ||
|
|
264
|
+
instance === '__classInfoConcise__'
|
|
265
|
+
) {
|
|
266
|
+
// Json properties: { className, instances, memberFunctions, staticFunctions }
|
|
267
|
+
const json = JSON.parse(message.toString())
|
|
268
|
+
this._createIfNotExist(agent)
|
|
269
|
+
const oldClassInfo = this._agents[agent].classes[klass]
|
|
270
|
+
const newInstances = json.instances || []
|
|
271
|
+
const oldInstances = oldClassInfo ? oldClassInfo.instances : []
|
|
272
|
+
const removed = oldInstances.filter(x => !newInstances.includes(x))
|
|
273
|
+
const added = newInstances.filter(x => !oldInstances.includes(x))
|
|
274
|
+
this._agents[agent].classes[klass] = json
|
|
275
|
+
const {
|
|
276
|
+
className,
|
|
277
|
+
instances,
|
|
278
|
+
memberFunctions,
|
|
279
|
+
staticFunctions,
|
|
280
|
+
meta
|
|
281
|
+
} = json
|
|
282
|
+
if (removed.length !== 0) {
|
|
283
|
+
this.emit('instanceGone', removed, { domain, agent, className })
|
|
284
|
+
removed.forEach(lostInstance =>
|
|
285
|
+
this._clearCachedSubscriptions({ lostInstance })
|
|
286
|
+
)
|
|
287
|
+
}
|
|
288
|
+
if (added.length !== 0) {
|
|
289
|
+
this.emit('instanceNew', added, { domain, agent, className })
|
|
290
|
+
}
|
|
291
|
+
this.emit('class', {
|
|
292
|
+
domain,
|
|
293
|
+
agent,
|
|
294
|
+
className,
|
|
295
|
+
instances,
|
|
296
|
+
memberFunctions,
|
|
297
|
+
staticFunctions,
|
|
298
|
+
meta: meta || {}
|
|
299
|
+
})
|
|
300
|
+
// RPC message
|
|
301
|
+
} else {
|
|
302
|
+
const { i, a, e, r } = JSON.parse(message.toString())
|
|
303
|
+
this._eventEmitter.emit(i, { a, e, r })
|
|
263
304
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
instances,
|
|
269
|
-
memberFunctions,
|
|
270
|
-
staticFunctions,
|
|
271
|
-
meta: meta || {}
|
|
272
|
-
})
|
|
273
|
-
// RPC message
|
|
274
|
-
} else {
|
|
275
|
-
const { i, a, e, r } = JSON.parse(message.toString())
|
|
276
|
-
this._eventEmitter.emit(i, { a, e, r })
|
|
305
|
+
} catch (err) {
|
|
306
|
+
this._log.error(
|
|
307
|
+
`Could not parse MQTT message on topic "${topic}": ${err.message}`
|
|
308
|
+
)
|
|
277
309
|
}
|
|
278
310
|
})
|
|
311
|
+
|
|
279
312
|
return new Promise((resolve, reject) => {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
313
|
+
const timeout = setTimeout(() => {
|
|
314
|
+
// This will run only if the 'connect' event isn't fired in time.
|
|
315
|
+
// We explicitly end the client to stop it from retrying in the background.
|
|
316
|
+
this._client.end(true, () => {
|
|
317
|
+
reject(
|
|
318
|
+
new Error(`Connection trial timed out (> ${this._timeout} ms)`)
|
|
319
|
+
)
|
|
320
|
+
})
|
|
321
|
+
}, this._timeout)
|
|
322
|
+
|
|
323
|
+
this._client.once('connect', () => {
|
|
324
|
+
// If we connect successfully, clear the timeout and resolve the promise.
|
|
325
|
+
clearTimeout(timeout)
|
|
326
|
+
resolve()
|
|
283
327
|
})
|
|
284
|
-
this._client.once('connect', resolve)
|
|
285
328
|
})
|
|
329
|
+
// --------------------------------------------------------------------------------
|
|
286
330
|
}
|
|
287
331
|
|
|
288
332
|
/**
|
|
@@ -343,6 +387,9 @@ class VrpcClient extends EventEmitter {
|
|
|
343
387
|
cacheProxy = false,
|
|
344
388
|
isIsolated = false
|
|
345
389
|
} = {}) {
|
|
390
|
+
if (!this._isConnected()) {
|
|
391
|
+
throw new Error('Client is not connected')
|
|
392
|
+
}
|
|
346
393
|
if (agent === '*') throw new Error('Agent must be specified')
|
|
347
394
|
const json = {
|
|
348
395
|
c: className,
|
|
@@ -372,6 +419,9 @@ class VrpcClient extends EventEmitter {
|
|
|
372
419
|
* @returns {Promise<Proxy>} Proxy object reflecting the remotely existing instance
|
|
373
420
|
*/
|
|
374
421
|
async getInstance (instance, options = {}) {
|
|
422
|
+
if (!this._isConnected()) {
|
|
423
|
+
throw new Error('Client is not connected')
|
|
424
|
+
}
|
|
375
425
|
if (this._proxies[instance]) return this._proxies[instance]
|
|
376
426
|
const { agent, className } = await this._getInstanceData(instance, options)
|
|
377
427
|
return this._createProxy(agent, className, instance)
|
|
@@ -390,6 +440,9 @@ class VrpcClient extends EventEmitter {
|
|
|
390
440
|
* @returns {Promise<Boolean>} true if successful, false otherwise
|
|
391
441
|
*/
|
|
392
442
|
async delete (instance, options = {}) {
|
|
443
|
+
if (!this._isConnected()) {
|
|
444
|
+
throw new Error('Client is not connected')
|
|
445
|
+
}
|
|
393
446
|
const { agent, className } = await this._getInstanceData(instance, options)
|
|
394
447
|
const json = {
|
|
395
448
|
c: className,
|
|
@@ -402,7 +455,25 @@ class VrpcClient extends EventEmitter {
|
|
|
402
455
|
const topic = `${this._domain}/${agent}/${className}/__static__/__delete__`
|
|
403
456
|
this._mqttPublish(topic, JSON.stringify(json))
|
|
404
457
|
if (this._proxies[instance]) delete this._proxies[instance]
|
|
405
|
-
|
|
458
|
+
const result = await this._handleAgentAnswer(json, agent)
|
|
459
|
+
// Now that deletion is confirmed, proactively update all local caches
|
|
460
|
+
if (result) {
|
|
461
|
+
// 1. Remove from the proxy cache
|
|
462
|
+
if (this._proxies[instance]) {
|
|
463
|
+
delete this._proxies[instance]
|
|
464
|
+
}
|
|
465
|
+
// 2. Remove from the main agent/instance cache
|
|
466
|
+
if (this._agents[agent] && this._agents[agent].classes) {
|
|
467
|
+
const classInfo = this._agents[agent].classes[className]
|
|
468
|
+
if (classInfo && classInfo.instances) {
|
|
469
|
+
const index = classInfo.instances.indexOf(instance)
|
|
470
|
+
if (index > -1) {
|
|
471
|
+
classInfo.instances.splice(index, 1)
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return result
|
|
406
477
|
}
|
|
407
478
|
|
|
408
479
|
/**
|
|
@@ -421,6 +492,9 @@ class VrpcClient extends EventEmitter {
|
|
|
421
492
|
args = [],
|
|
422
493
|
agent = this._agent
|
|
423
494
|
} = {}) {
|
|
495
|
+
if (!this._isConnected()) {
|
|
496
|
+
throw new Error('Client is not connected')
|
|
497
|
+
}
|
|
424
498
|
const wrapped = this._wrapArguments({
|
|
425
499
|
agent,
|
|
426
500
|
className,
|
|
@@ -468,6 +542,9 @@ class VrpcClient extends EventEmitter {
|
|
|
468
542
|
args = [],
|
|
469
543
|
agent = this._agent
|
|
470
544
|
} = {}) {
|
|
545
|
+
if (!this._isConnected()) {
|
|
546
|
+
throw new Error('Client is not connected')
|
|
547
|
+
}
|
|
471
548
|
const json = {
|
|
472
549
|
c: className,
|
|
473
550
|
f: functionName,
|
|
@@ -644,10 +721,10 @@ class VrpcClient extends EventEmitter {
|
|
|
644
721
|
async reconnectWithToken (token, { agent = this._agent } = {}) {
|
|
645
722
|
this._token = token
|
|
646
723
|
this._agent = agent
|
|
647
|
-
this._client
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
724
|
+
if (this._client) {
|
|
725
|
+
await new Promise(resolve => this._client.end(false, {}, resolve))
|
|
726
|
+
}
|
|
727
|
+
return this.connect()
|
|
651
728
|
}
|
|
652
729
|
|
|
653
730
|
/**
|
|
@@ -678,11 +755,49 @@ class VrpcClient extends EventEmitter {
|
|
|
678
755
|
* @returns {Promise} Resolves when ended
|
|
679
756
|
*/
|
|
680
757
|
async end () {
|
|
758
|
+
if (!this._client) return
|
|
681
759
|
this._mqttPublish(
|
|
682
760
|
`${this._vrpcClientId}/__clientInfo__`,
|
|
683
761
|
JSON.stringify({ status: 'offline', v: VRPC_PROTOCOL_VERSION })
|
|
684
762
|
)
|
|
685
|
-
|
|
763
|
+
this._eventEmitter.removeAllListeners()
|
|
764
|
+
this.removeAllListeners()
|
|
765
|
+
const client = this._client
|
|
766
|
+
this._client = null // Prevent race conditions during shutdown
|
|
767
|
+
return new Promise(resolve => client.end(false, {}, resolve))
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// Private functions
|
|
771
|
+
|
|
772
|
+
static _createHash (str, length = 20) {
|
|
773
|
+
// Extended DJB2 hash with two accumulators
|
|
774
|
+
let hash1 = 5381
|
|
775
|
+
let hash2 = 52711 // different seed
|
|
776
|
+
|
|
777
|
+
for (let i = 0; i < str.length; i++) {
|
|
778
|
+
const char = str.charCodeAt(i)
|
|
779
|
+
hash1 = (hash1 * 33) ^ char
|
|
780
|
+
hash2 = (hash2 * 33) ^ char
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// Combine into longer hex string
|
|
784
|
+
const token =
|
|
785
|
+
(hash1 >>> 0).toString(16).padStart(8, '0') +
|
|
786
|
+
(hash2 >>> 0).toString(16).padStart(8, '0')
|
|
787
|
+
|
|
788
|
+
return token.slice(0, length)
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
_isConnected () {
|
|
792
|
+
return this._client && this._client.connected
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
_getCallbackId (callback, { readOnly = false } = {}) {
|
|
796
|
+
if (!this._callbackIds.has(callback)) {
|
|
797
|
+
if (readOnly) return null
|
|
798
|
+
this._callbackIds.set(callback, nanoid(8))
|
|
799
|
+
}
|
|
800
|
+
return this._callbackIds.get(callback)
|
|
686
801
|
}
|
|
687
802
|
|
|
688
803
|
_generateUserName () {
|
|
@@ -700,12 +815,8 @@ class VrpcClient extends EventEmitter {
|
|
|
700
815
|
os.totalmem() +
|
|
701
816
|
os.type()
|
|
702
817
|
// console.log('ClientInfo:', clientInfo)
|
|
703
|
-
const
|
|
704
|
-
|
|
705
|
-
.update(clientInfo)
|
|
706
|
-
.digest('hex')
|
|
707
|
-
.substring(0, 12)
|
|
708
|
-
return `vc3${this._instance}${md5}` // 3 + 8 + 12 = 23 (max clientId)
|
|
818
|
+
const hash = VrpcClient._createHash(clientInfo, 12)
|
|
819
|
+
return `vc3${this._instance}${hash}` // 3 + 8 + 12 = 23 (max clientId)
|
|
709
820
|
}
|
|
710
821
|
|
|
711
822
|
_createVrpcClientId () {
|
|
@@ -845,7 +956,7 @@ class VrpcClient extends EventEmitter {
|
|
|
845
956
|
this.removeListener('class', checkClass)
|
|
846
957
|
reject(
|
|
847
958
|
new Error(
|
|
848
|
-
`
|
|
959
|
+
`Connection to class "${className}" on agent "${agent}" and domain "${this._domain}" timed out (> ${this._timeout} ms)`
|
|
849
960
|
)
|
|
850
961
|
)
|
|
851
962
|
}, this._timeout)
|
|
@@ -1112,12 +1223,24 @@ class VrpcClient extends EventEmitter {
|
|
|
1112
1223
|
// special case of an EventEmitter provided as argument
|
|
1113
1224
|
if (this._isEmitter(x)) {
|
|
1114
1225
|
const { emitter, event } = x
|
|
1226
|
+
let callback = (...args) => emitter.emit(event, ...args)
|
|
1227
|
+
if (this._emitterListener.has(emitter)) {
|
|
1228
|
+
const eventMap = this._emitterListener.get(emitter)
|
|
1229
|
+
if (eventMap.has(event)) {
|
|
1230
|
+
callback = eventMap.get(event)
|
|
1231
|
+
} else {
|
|
1232
|
+
eventMap.set(event, callback)
|
|
1233
|
+
}
|
|
1234
|
+
} else {
|
|
1235
|
+
this._emitterListener.set(emitter, new Map([[event, callback]]))
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1115
1238
|
const id = this._onRemoteEvent({
|
|
1116
1239
|
agent,
|
|
1117
1240
|
className,
|
|
1118
1241
|
instance,
|
|
1119
1242
|
event,
|
|
1120
|
-
callback
|
|
1243
|
+
callback
|
|
1121
1244
|
})
|
|
1122
1245
|
wrapped.push(id)
|
|
1123
1246
|
continue
|
|
@@ -1129,16 +1252,24 @@ class VrpcClient extends EventEmitter {
|
|
|
1129
1252
|
}
|
|
1130
1253
|
|
|
1131
1254
|
_onRemoteEvent ({ agent, className, instance, event, callback }) {
|
|
1255
|
+
const callbackId = this._getCallbackId(callback)
|
|
1256
|
+
const eventCallback = `${event}-${callbackId}`
|
|
1257
|
+
|
|
1132
1258
|
// the topic on which any remote event will be published to
|
|
1133
1259
|
const topic = instance
|
|
1134
|
-
? `${this._domain}/${agent}/${className}/${instance}:${
|
|
1135
|
-
: `${this._domain}/${agent}/${className}/${
|
|
1260
|
+
? `${this._domain}/${agent}/${className}/${instance}:${eventCallback}`
|
|
1261
|
+
: `${this._domain}/${agent}/${className}/${eventCallback}`
|
|
1262
|
+
|
|
1136
1263
|
// prepare a special id for the agent to know that events should be
|
|
1137
|
-
// published to the provided topic after the prefix
|
|
1264
|
+
// published to the provided topic after the prefix and before the suffix
|
|
1265
|
+
|
|
1138
1266
|
const id = `__e__${topic}`
|
|
1267
|
+
|
|
1139
1268
|
// call our proxy callback when remote events were received
|
|
1140
1269
|
const handler = ({ a }) => callback.apply(null, a)
|
|
1141
|
-
this._eventEmitter.
|
|
1270
|
+
if (this._eventEmitter.listenerCount(id) === 0) {
|
|
1271
|
+
this._eventEmitter.on(id, handler)
|
|
1272
|
+
}
|
|
1142
1273
|
if (!this._cachedSubscriptions[topic]) {
|
|
1143
1274
|
// when not yet subscribed to this topic do it now
|
|
1144
1275
|
this._mqttSubscribe(topic)
|
|
@@ -1151,10 +1282,16 @@ class VrpcClient extends EventEmitter {
|
|
|
1151
1282
|
}
|
|
1152
1283
|
|
|
1153
1284
|
_offRemoteEvent ({ agent, className, instance, event, callback }) {
|
|
1285
|
+
const callbackId = this._getCallbackId(callback, {
|
|
1286
|
+
readOnly: true
|
|
1287
|
+
})
|
|
1288
|
+
const eventCallback = `${event}-${callbackId}`
|
|
1154
1289
|
const topic = instance
|
|
1155
|
-
? `${this._domain}/${agent}/${className}/${instance}:${
|
|
1156
|
-
: `${this._domain}/${agent}/${className}/${
|
|
1290
|
+
? `${this._domain}/${agent}/${className}/${instance}:${eventCallback}`
|
|
1291
|
+
: `${this._domain}/${agent}/${className}/${eventCallback}`
|
|
1292
|
+
|
|
1157
1293
|
const id = `__e__${topic}`
|
|
1294
|
+
|
|
1158
1295
|
if (this._cachedSubscriptions[topic]) {
|
|
1159
1296
|
const index = this._cachedSubscriptions[topic].findIndex(
|
|
1160
1297
|
x => x.callback === callback
|
|
@@ -1164,8 +1301,8 @@ class VrpcClient extends EventEmitter {
|
|
|
1164
1301
|
return null
|
|
1165
1302
|
}
|
|
1166
1303
|
const [{ handler }] = this._cachedSubscriptions[topic].splice(index, 1)
|
|
1167
|
-
this._eventEmitter.removeListener(id, handler)
|
|
1168
1304
|
if (this._cachedSubscriptions[topic].length === 0) {
|
|
1305
|
+
this._eventEmitter.removeListener(id, handler)
|
|
1169
1306
|
this._mqttUnsubscribe(topic)
|
|
1170
1307
|
delete this._cachedSubscriptions[topic]
|
|
1171
1308
|
return id
|
|
@@ -1241,14 +1378,14 @@ class VrpcClient extends EventEmitter {
|
|
|
1241
1378
|
// when no agent is specified, but an explicit class default exists give
|
|
1242
1379
|
// such instances priority in being found
|
|
1243
1380
|
if (!options.agent && this._agent !== '*') {
|
|
1244
|
-
const { classes, status } = this._agents[this._agent]
|
|
1381
|
+
const { classes, status } = this._agents[this._agent] || {}
|
|
1245
1382
|
if (!classes || status === 'offline') {
|
|
1246
1383
|
for (const className in classes) {
|
|
1247
1384
|
if (options.className && className !== options.className) continue
|
|
1248
1385
|
const { instances } = classes[className]
|
|
1249
1386
|
if (!instances) continue
|
|
1250
1387
|
if (instances.includes(instance)) {
|
|
1251
|
-
return { agent, className, instance }
|
|
1388
|
+
return { agent: this._agent, className, instance }
|
|
1252
1389
|
}
|
|
1253
1390
|
}
|
|
1254
1391
|
}
|