vrpc 3.3.2 → 3.5.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/browser/vrpc.js +7 -1
- package/browser/vrpc.js.LICENSE.txt +1 -14
- package/package.json +15 -15
- package/vrpc/VrpcAdapter.js +5 -11
- package/vrpc/VrpcAgent.js +26 -16
- package/vrpc/VrpcBrowserAdapter.js +0 -5
- package/vrpc/VrpcBrowserAgent.js +23 -10
- package/vrpc/VrpcClient.js +210 -93
- 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,7 @@ class VrpcClient extends EventEmitter {
|
|
|
135
134
|
this._client = null
|
|
136
135
|
this._cachedSubscriptions = {}
|
|
137
136
|
this._proxies = {}
|
|
137
|
+
this._callbackIds = new WeakMap()
|
|
138
138
|
if (log === 'console') {
|
|
139
139
|
this._log = console
|
|
140
140
|
this._log.debug = () => {}
|
|
@@ -189,6 +189,19 @@ class VrpcClient extends EventEmitter {
|
|
|
189
189
|
if (password === undefined) delete options.password
|
|
190
190
|
this._client = mqtt.connect(this._broker, options)
|
|
191
191
|
|
|
192
|
+
// Persistent lifecycle handlers
|
|
193
|
+
this._client.on('offline', () => {
|
|
194
|
+
this._log.warn('MQTT client went offline.')
|
|
195
|
+
// Clear the cache to ensure a fresh state upon reconnection
|
|
196
|
+
this._agents = {}
|
|
197
|
+
this.emit('offline')
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
this._client.on('reconnect', () => {
|
|
201
|
+
this._log.info('MQTT client is attempting to reconnect...')
|
|
202
|
+
this.emit('reconnect')
|
|
203
|
+
})
|
|
204
|
+
|
|
192
205
|
this._client.on('error', err => {
|
|
193
206
|
this.emit('error', err)
|
|
194
207
|
})
|
|
@@ -214,75 +227,105 @@ class VrpcClient extends EventEmitter {
|
|
|
214
227
|
}
|
|
215
228
|
// RPC responses
|
|
216
229
|
this._mqttSubscribe(this._vrpcClientId)
|
|
230
|
+
|
|
231
|
+
// Re-subscribe to all cached event topics
|
|
232
|
+
this._log.info('Restoring event subscriptions after reconnect...')
|
|
233
|
+
for (const topic of Object.keys(this._cachedSubscriptions)) {
|
|
234
|
+
this._mqttSubscribe(topic)
|
|
235
|
+
}
|
|
236
|
+
|
|
217
237
|
this.emit('connect')
|
|
218
238
|
})
|
|
219
239
|
|
|
220
240
|
this._client.on('message', (topic, message) => {
|
|
221
241
|
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 })
|
|
242
|
+
try {
|
|
243
|
+
const tokens = topic.split('/')
|
|
244
|
+
const [domain, agent, klass, instance] = tokens
|
|
245
|
+
if (domain !== this._domain) {
|
|
246
|
+
this._log.warn(`Received message from foreign domain: ${domain}`)
|
|
247
|
+
return
|
|
237
248
|
}
|
|
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.
|
|
249
|
+
// AgentInfo message
|
|
250
|
+
if (klass === '__agentInfo__') {
|
|
251
|
+
const { status, hostname, version } = JSON.parse(message.toString())
|
|
252
|
+
this._createIfNotExist(agent)
|
|
253
|
+
this._agents[agent].status = status
|
|
254
|
+
this._agents[agent].hostname = hostname
|
|
255
|
+
this._agents[agent].version = version
|
|
256
|
+
if (status === 'offline') {
|
|
257
|
+
this._clearCachedSubscriptions({ lostAgent: agent })
|
|
258
|
+
}
|
|
259
|
+
this.emit('agent', { domain, agent, status, hostname, version })
|
|
260
|
+
// ClassInfo message
|
|
261
|
+
} else if (
|
|
262
|
+
instance === '__classInfo__' ||
|
|
263
|
+
instance === '__classInfoConcise__'
|
|
264
|
+
) {
|
|
265
|
+
// Json properties: { className, instances, memberFunctions, staticFunctions }
|
|
266
|
+
const json = JSON.parse(message.toString())
|
|
267
|
+
this._createIfNotExist(agent)
|
|
268
|
+
const oldClassInfo = this._agents[agent].classes[klass]
|
|
269
|
+
const newInstances = json.instances || []
|
|
270
|
+
const oldInstances = oldClassInfo ? oldClassInfo.instances : []
|
|
271
|
+
const removed = oldInstances.filter(x => !newInstances.includes(x))
|
|
272
|
+
const added = newInstances.filter(x => !oldInstances.includes(x))
|
|
273
|
+
this._agents[agent].classes[klass] = json
|
|
274
|
+
const {
|
|
275
|
+
className,
|
|
276
|
+
instances,
|
|
277
|
+
memberFunctions,
|
|
278
|
+
staticFunctions,
|
|
279
|
+
meta
|
|
280
|
+
} = json
|
|
281
|
+
if (removed.length !== 0) {
|
|
282
|
+
this.emit('instanceGone', removed, { domain, agent, className })
|
|
283
|
+
removed.forEach(lostInstance =>
|
|
284
|
+
this._clearCachedSubscriptions({ lostInstance })
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
if (added.length !== 0) {
|
|
288
|
+
this.emit('instanceNew', added, { domain, agent, className })
|
|
289
|
+
}
|
|
290
|
+
this.emit('class', {
|
|
291
|
+
domain,
|
|
292
|
+
agent,
|
|
293
|
+
className,
|
|
294
|
+
instances,
|
|
295
|
+
memberFunctions,
|
|
296
|
+
staticFunctions,
|
|
297
|
+
meta: meta || {}
|
|
298
|
+
})
|
|
299
|
+
// RPC message
|
|
300
|
+
} else {
|
|
301
|
+
const { i, a, e, r } = JSON.parse(message.toString())
|
|
302
|
+
this._eventEmitter.emit(i, { a, e, r })
|
|
263
303
|
}
|
|
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 })
|
|
304
|
+
} catch (err) {
|
|
305
|
+
this._log.error(
|
|
306
|
+
`Could not parse MQTT message on topic "${topic}": ${err.message}`
|
|
307
|
+
)
|
|
277
308
|
}
|
|
278
309
|
})
|
|
310
|
+
|
|
279
311
|
return new Promise((resolve, reject) => {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
312
|
+
const timeout = setTimeout(() => {
|
|
313
|
+
// This will run only if the 'connect' event isn't fired in time.
|
|
314
|
+
// We explicitly end the client to stop it from retrying in the background.
|
|
315
|
+
this._client.end(true, () => {
|
|
316
|
+
reject(
|
|
317
|
+
new Error(`Connection trial timed out (> ${this._timeout} ms)`)
|
|
318
|
+
)
|
|
319
|
+
})
|
|
320
|
+
}, this._timeout)
|
|
321
|
+
|
|
322
|
+
this._client.once('connect', () => {
|
|
323
|
+
// If we connect successfully, clear the timeout and resolve the promise.
|
|
324
|
+
clearTimeout(timeout)
|
|
325
|
+
resolve()
|
|
283
326
|
})
|
|
284
|
-
this._client.once('connect', resolve)
|
|
285
327
|
})
|
|
328
|
+
// --------------------------------------------------------------------------------
|
|
286
329
|
}
|
|
287
330
|
|
|
288
331
|
/**
|
|
@@ -343,6 +386,9 @@ class VrpcClient extends EventEmitter {
|
|
|
343
386
|
cacheProxy = false,
|
|
344
387
|
isIsolated = false
|
|
345
388
|
} = {}) {
|
|
389
|
+
if (!this._isConnected()) {
|
|
390
|
+
throw new Error('Client is not connected')
|
|
391
|
+
}
|
|
346
392
|
if (agent === '*') throw new Error('Agent must be specified')
|
|
347
393
|
const json = {
|
|
348
394
|
c: className,
|
|
@@ -372,6 +418,9 @@ class VrpcClient extends EventEmitter {
|
|
|
372
418
|
* @returns {Promise<Proxy>} Proxy object reflecting the remotely existing instance
|
|
373
419
|
*/
|
|
374
420
|
async getInstance (instance, options = {}) {
|
|
421
|
+
if (!this._isConnected()) {
|
|
422
|
+
throw new Error('Client is not connected')
|
|
423
|
+
}
|
|
375
424
|
if (this._proxies[instance]) return this._proxies[instance]
|
|
376
425
|
const { agent, className } = await this._getInstanceData(instance, options)
|
|
377
426
|
return this._createProxy(agent, className, instance)
|
|
@@ -390,6 +439,9 @@ class VrpcClient extends EventEmitter {
|
|
|
390
439
|
* @returns {Promise<Boolean>} true if successful, false otherwise
|
|
391
440
|
*/
|
|
392
441
|
async delete (instance, options = {}) {
|
|
442
|
+
if (!this._isConnected()) {
|
|
443
|
+
throw new Error('Client is not connected')
|
|
444
|
+
}
|
|
393
445
|
const { agent, className } = await this._getInstanceData(instance, options)
|
|
394
446
|
const json = {
|
|
395
447
|
c: className,
|
|
@@ -402,7 +454,25 @@ class VrpcClient extends EventEmitter {
|
|
|
402
454
|
const topic = `${this._domain}/${agent}/${className}/__static__/__delete__`
|
|
403
455
|
this._mqttPublish(topic, JSON.stringify(json))
|
|
404
456
|
if (this._proxies[instance]) delete this._proxies[instance]
|
|
405
|
-
|
|
457
|
+
const result = await this._handleAgentAnswer(json, agent)
|
|
458
|
+
// Now that deletion is confirmed, proactively update all local caches
|
|
459
|
+
if (result) {
|
|
460
|
+
// 1. Remove from the proxy cache
|
|
461
|
+
if (this._proxies[instance]) {
|
|
462
|
+
delete this._proxies[instance]
|
|
463
|
+
}
|
|
464
|
+
// 2. Remove from the main agent/instance cache
|
|
465
|
+
if (this._agents[agent] && this._agents[agent].classes) {
|
|
466
|
+
const classInfo = this._agents[agent].classes[className]
|
|
467
|
+
if (classInfo && classInfo.instances) {
|
|
468
|
+
const index = classInfo.instances.indexOf(instance)
|
|
469
|
+
if (index > -1) {
|
|
470
|
+
classInfo.instances.splice(index, 1)
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return result
|
|
406
476
|
}
|
|
407
477
|
|
|
408
478
|
/**
|
|
@@ -421,6 +491,9 @@ class VrpcClient extends EventEmitter {
|
|
|
421
491
|
args = [],
|
|
422
492
|
agent = this._agent
|
|
423
493
|
} = {}) {
|
|
494
|
+
if (!this._isConnected()) {
|
|
495
|
+
throw new Error('Client is not connected')
|
|
496
|
+
}
|
|
424
497
|
const wrapped = this._wrapArguments({
|
|
425
498
|
agent,
|
|
426
499
|
className,
|
|
@@ -468,6 +541,9 @@ class VrpcClient extends EventEmitter {
|
|
|
468
541
|
args = [],
|
|
469
542
|
agent = this._agent
|
|
470
543
|
} = {}) {
|
|
544
|
+
if (!this._isConnected()) {
|
|
545
|
+
throw new Error('Client is not connected')
|
|
546
|
+
}
|
|
471
547
|
const json = {
|
|
472
548
|
c: className,
|
|
473
549
|
f: functionName,
|
|
@@ -644,10 +720,10 @@ class VrpcClient extends EventEmitter {
|
|
|
644
720
|
async reconnectWithToken (token, { agent = this._agent } = {}) {
|
|
645
721
|
this._token = token
|
|
646
722
|
this._agent = agent
|
|
647
|
-
this._client
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
723
|
+
if (this._client) {
|
|
724
|
+
await new Promise(resolve => this._client.end(false, {}, resolve))
|
|
725
|
+
}
|
|
726
|
+
return this.connect()
|
|
651
727
|
}
|
|
652
728
|
|
|
653
729
|
/**
|
|
@@ -678,11 +754,49 @@ class VrpcClient extends EventEmitter {
|
|
|
678
754
|
* @returns {Promise} Resolves when ended
|
|
679
755
|
*/
|
|
680
756
|
async end () {
|
|
757
|
+
if (!this._client) return
|
|
681
758
|
this._mqttPublish(
|
|
682
759
|
`${this._vrpcClientId}/__clientInfo__`,
|
|
683
760
|
JSON.stringify({ status: 'offline', v: VRPC_PROTOCOL_VERSION })
|
|
684
761
|
)
|
|
685
|
-
|
|
762
|
+
this._eventEmitter.removeAllListeners()
|
|
763
|
+
this.removeAllListeners()
|
|
764
|
+
const client = this._client
|
|
765
|
+
this._client = null // Prevent race conditions during shutdown
|
|
766
|
+
return new Promise(resolve => client.end(false, {}, resolve))
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// Private functions
|
|
770
|
+
|
|
771
|
+
static _createHash (str, length = 20) {
|
|
772
|
+
// Extended DJB2 hash with two accumulators
|
|
773
|
+
let hash1 = 5381
|
|
774
|
+
let hash2 = 52711 // different seed
|
|
775
|
+
|
|
776
|
+
for (let i = 0; i < str.length; i++) {
|
|
777
|
+
const char = str.charCodeAt(i)
|
|
778
|
+
hash1 = (hash1 * 33) ^ char
|
|
779
|
+
hash2 = (hash2 * 33) ^ char
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// Combine into longer hex string
|
|
783
|
+
const token =
|
|
784
|
+
(hash1 >>> 0).toString(16).padStart(8, '0') +
|
|
785
|
+
(hash2 >>> 0).toString(16).padStart(8, '0')
|
|
786
|
+
|
|
787
|
+
return token.slice(0, length)
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
_isConnected () {
|
|
791
|
+
return this._client && this._client.connected
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
_getCallbackId (callback, { readOnly = false } = {}) {
|
|
795
|
+
if (!this._callbackIds.has(callback)) {
|
|
796
|
+
if (readOnly) return null
|
|
797
|
+
this._callbackIds.set(callback, nanoid(8))
|
|
798
|
+
}
|
|
799
|
+
return this._callbackIds.get(callback)
|
|
686
800
|
}
|
|
687
801
|
|
|
688
802
|
_generateUserName () {
|
|
@@ -700,12 +814,8 @@ class VrpcClient extends EventEmitter {
|
|
|
700
814
|
os.totalmem() +
|
|
701
815
|
os.type()
|
|
702
816
|
// 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)
|
|
817
|
+
const hash = VrpcClient._createHash(clientInfo, 12)
|
|
818
|
+
return `vc3${this._instance}${hash}` // 3 + 8 + 12 = 23 (max clientId)
|
|
709
819
|
}
|
|
710
820
|
|
|
711
821
|
_createVrpcClientId () {
|
|
@@ -845,7 +955,7 @@ class VrpcClient extends EventEmitter {
|
|
|
845
955
|
this.removeListener('class', checkClass)
|
|
846
956
|
reject(
|
|
847
957
|
new Error(
|
|
848
|
-
`
|
|
958
|
+
`Connection to class "${className}" on agent "${agent}" and domain "${this._domain}" timed out (> ${this._timeout} ms)`
|
|
849
959
|
)
|
|
850
960
|
)
|
|
851
961
|
}, this._timeout)
|
|
@@ -1081,8 +1191,6 @@ class VrpcClient extends EventEmitter {
|
|
|
1081
1191
|
event,
|
|
1082
1192
|
callback
|
|
1083
1193
|
})
|
|
1084
|
-
// indicate that no network call is required
|
|
1085
|
-
if (id === null) return null
|
|
1086
1194
|
wrapped.push(id)
|
|
1087
1195
|
continue
|
|
1088
1196
|
} else if (
|
|
@@ -1098,8 +1206,6 @@ class VrpcClient extends EventEmitter {
|
|
|
1098
1206
|
event,
|
|
1099
1207
|
callback
|
|
1100
1208
|
})
|
|
1101
|
-
// indicate that no network call is required
|
|
1102
|
-
if (id === null) return null
|
|
1103
1209
|
wrapped.push(id)
|
|
1104
1210
|
continue
|
|
1105
1211
|
} else {
|
|
@@ -1123,8 +1229,6 @@ class VrpcClient extends EventEmitter {
|
|
|
1123
1229
|
event,
|
|
1124
1230
|
callback: (...args) => emitter.emit(event, ...args)
|
|
1125
1231
|
})
|
|
1126
|
-
// indicate that no network call is required
|
|
1127
|
-
if (id === null) return null
|
|
1128
1232
|
wrapped.push(id)
|
|
1129
1233
|
continue
|
|
1130
1234
|
}
|
|
@@ -1135,16 +1239,24 @@ class VrpcClient extends EventEmitter {
|
|
|
1135
1239
|
}
|
|
1136
1240
|
|
|
1137
1241
|
_onRemoteEvent ({ agent, className, instance, event, callback }) {
|
|
1242
|
+
const callbackId = this._getCallbackId(callback)
|
|
1243
|
+
const eventCallback = `${event}-${callbackId}`
|
|
1244
|
+
|
|
1138
1245
|
// the topic on which any remote event will be published to
|
|
1139
1246
|
const topic = instance
|
|
1140
|
-
? `${this._domain}/${agent}/${className}/${instance}:${
|
|
1141
|
-
: `${this._domain}/${agent}/${className}/${
|
|
1247
|
+
? `${this._domain}/${agent}/${className}/${instance}:${eventCallback}`
|
|
1248
|
+
: `${this._domain}/${agent}/${className}/${eventCallback}`
|
|
1249
|
+
|
|
1142
1250
|
// prepare a special id for the agent to know that events should be
|
|
1143
|
-
// published to the provided topic after the prefix
|
|
1251
|
+
// published to the provided topic after the prefix and before the suffix
|
|
1252
|
+
|
|
1144
1253
|
const id = `__e__${topic}`
|
|
1254
|
+
|
|
1145
1255
|
// call our proxy callback when remote events were received
|
|
1146
1256
|
const handler = ({ a }) => callback.apply(null, a)
|
|
1147
|
-
this._eventEmitter.
|
|
1257
|
+
if (this._eventEmitter.listenerCount(id) === 0) {
|
|
1258
|
+
this._eventEmitter.on(id, handler)
|
|
1259
|
+
}
|
|
1148
1260
|
if (!this._cachedSubscriptions[topic]) {
|
|
1149
1261
|
// when not yet subscribed to this topic do it now
|
|
1150
1262
|
this._mqttSubscribe(topic)
|
|
@@ -1153,15 +1265,20 @@ class VrpcClient extends EventEmitter {
|
|
|
1153
1265
|
}
|
|
1154
1266
|
// otherwise just add the new callback-handler pair
|
|
1155
1267
|
this._cachedSubscriptions[topic].push({ callback, handler })
|
|
1156
|
-
|
|
1157
|
-
return null
|
|
1268
|
+
return id
|
|
1158
1269
|
}
|
|
1159
1270
|
|
|
1160
1271
|
_offRemoteEvent ({ agent, className, instance, event, callback }) {
|
|
1272
|
+
const callbackId = this._getCallbackId(callback, {
|
|
1273
|
+
readOnly: true
|
|
1274
|
+
})
|
|
1275
|
+
const eventCallback = `${event}-${callbackId}`
|
|
1161
1276
|
const topic = instance
|
|
1162
|
-
? `${this._domain}/${agent}/${className}/${instance}:${
|
|
1163
|
-
: `${this._domain}/${agent}/${className}/${
|
|
1277
|
+
? `${this._domain}/${agent}/${className}/${instance}:${eventCallback}`
|
|
1278
|
+
: `${this._domain}/${agent}/${className}/${eventCallback}`
|
|
1279
|
+
|
|
1164
1280
|
const id = `__e__${topic}`
|
|
1281
|
+
|
|
1165
1282
|
if (this._cachedSubscriptions[topic]) {
|
|
1166
1283
|
const index = this._cachedSubscriptions[topic].findIndex(
|
|
1167
1284
|
x => x.callback === callback
|
|
@@ -1171,14 +1288,13 @@ class VrpcClient extends EventEmitter {
|
|
|
1171
1288
|
return null
|
|
1172
1289
|
}
|
|
1173
1290
|
const [{ handler }] = this._cachedSubscriptions[topic].splice(index, 1)
|
|
1174
|
-
this._eventEmitter.removeListener(id, handler)
|
|
1175
1291
|
if (this._cachedSubscriptions[topic].length === 0) {
|
|
1292
|
+
this._eventEmitter.removeListener(id, handler)
|
|
1176
1293
|
this._mqttUnsubscribe(topic)
|
|
1177
1294
|
delete this._cachedSubscriptions[topic]
|
|
1178
1295
|
return id
|
|
1179
1296
|
}
|
|
1180
|
-
|
|
1181
|
-
return null
|
|
1297
|
+
return id
|
|
1182
1298
|
} else {
|
|
1183
1299
|
this._log.warn(`Can not unsubscribe from non-existing event: ${event}`)
|
|
1184
1300
|
}
|
|
@@ -1225,7 +1341,8 @@ class VrpcClient extends EventEmitter {
|
|
|
1225
1341
|
|
|
1226
1342
|
_isEmitter (variable) {
|
|
1227
1343
|
return (
|
|
1228
|
-
variable &&
|
|
1344
|
+
variable &&
|
|
1345
|
+
typeof variable === 'object' &&
|
|
1229
1346
|
variable.hasOwnProperty('emitter') &&
|
|
1230
1347
|
variable.hasOwnProperty('event') &&
|
|
1231
1348
|
typeof variable.emitter === 'object' &&
|
|
@@ -1248,14 +1365,14 @@ class VrpcClient extends EventEmitter {
|
|
|
1248
1365
|
// when no agent is specified, but an explicit class default exists give
|
|
1249
1366
|
// such instances priority in being found
|
|
1250
1367
|
if (!options.agent && this._agent !== '*') {
|
|
1251
|
-
const { classes, status } = this._agents[this._agent]
|
|
1368
|
+
const { classes, status } = this._agents[this._agent] || {}
|
|
1252
1369
|
if (!classes || status === 'offline') {
|
|
1253
1370
|
for (const className in classes) {
|
|
1254
1371
|
if (options.className && className !== options.className) continue
|
|
1255
1372
|
const { instances } = classes[className]
|
|
1256
1373
|
if (!instances) continue
|
|
1257
1374
|
if (instances.includes(instance)) {
|
|
1258
|
-
return { agent, className, instance }
|
|
1375
|
+
return { agent: this._agent, className, instance }
|
|
1259
1376
|
}
|
|
1260
1377
|
}
|
|
1261
1378
|
}
|