vrpc 3.0.2 → 3.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.
@@ -59,15 +59,17 @@ class VrpcClient extends EventEmitter {
59
59
  *
60
60
  * @constructor
61
61
  * @param {Object} options
62
- * @param {String} options.token Access token as generated by: https://app.vrpc.io
63
- * @param {String} options.username MQTT username (if no token is used)
64
- * @param {String} options.password MQTT password (if no token is provided)
62
+ * @param {String} [options.username] MQTT username
63
+ * @param {String} [options.password] MQTT password (if no token is provided)
64
+ * @param {String} [options.token] Access token
65
65
  * @param {String} options.domain Sets the domain
66
66
  * @param {String} [options.agent="*"] Sets default agent
67
67
  * @param {String} [options.broker="mqtts://vrpc.io:8883"] Broker url in form: `<scheme>://<host>:<port>`
68
68
  * @param {Number} [options.timeout=6000] Maximum time in ms to wait for a RPC answer
69
69
  * @param {Object} [options.log=console] Log object (must support debug, info, warn, and error level)
70
70
  * @param {Boolean} [options.bestEffort=false] If true, message will be sent with best effort, i.e. no caching if offline
71
+ * @param {String} [options.mqttClientId='<generated()>'] Explicitly sets the mqtt client id
72
+ * @param {String} [options.identity] Explicitly sets a vrpc client identity
71
73
  * @example
72
74
  * const client = new VrpcClient({
73
75
  * domain: 'vrpc',
@@ -76,14 +78,16 @@ class VrpcClient extends EventEmitter {
76
78
  */
77
79
  constructor ({
78
80
  token,
79
- username,
80
81
  password,
82
+ username,
81
83
  domain,
82
84
  agent = '*',
83
85
  broker = 'mqtts://vrpc.io:8883',
84
86
  timeout = 6 * 1000,
85
87
  log = 'console',
86
- bestEffort = false
88
+ bestEffort = false,
89
+ mqttClientId = null,
90
+ identity = null
87
91
  } = {}) {
88
92
  super()
89
93
  // domain sanity check
@@ -99,6 +103,12 @@ class VrpcClient extends EventEmitter {
99
103
  'The agent must NOT contain any of those characters: "+", "/", "#"'
100
104
  )
101
105
  }
106
+ // identity sanity check
107
+ if (identity && identity.match(/[+/#$]/)) {
108
+ throw new Error(
109
+ 'The identity must NOT contain any of those characters: "+", "/", "#", "$"'
110
+ )
111
+ }
102
112
  this._token = token
103
113
  this._username = username
104
114
  this._password = password
@@ -106,10 +116,11 @@ class VrpcClient extends EventEmitter {
106
116
  this._domain = domain
107
117
  this._broker = broker
108
118
  this._timeout = timeout
119
+ this._identity = identity
109
120
  this._qos = this._qos = bestEffort ? 0 : 1
110
121
 
111
122
  this._instance = nanoid(8)
112
- this._mqttClientId = this._createMqttClientId()
123
+ this._mqttClientId = mqttClientId || this._createMqttClientId()
113
124
  this._vrpcClientId = this._createVrpcClientId()
114
125
  this._agents = {}
115
126
  this._eventEmitter = new EventEmitter()
@@ -152,6 +163,7 @@ class VrpcClient extends EventEmitter {
152
163
  let username = this._username
153
164
  let password = this._password
154
165
  if (this._token) {
166
+ username = username || this._generateUserName()
155
167
  password = this._token
156
168
  }
157
169
  const options = {
@@ -323,7 +335,7 @@ class VrpcClient extends EventEmitter {
323
335
  f: isIsolated ? '__createIsolated__' : '__createShared__',
324
336
  a: [instance, ...args],
325
337
  i: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
326
- s: `${this._domain}/${os.hostname()}/${this._instance}`,
338
+ s: this._vrpcClientId,
327
339
  v: VRPC_PROTOCOL_VERSION
328
340
  }
329
341
  const proxy = await this._getProxy(agent, className, json)
@@ -370,7 +382,7 @@ class VrpcClient extends EventEmitter {
370
382
  f: '__delete__',
371
383
  a: [instance],
372
384
  i: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
373
- s: `${this._domain}/${os.hostname()}/${this._instance}`,
385
+ s: this._vrpcClientId,
374
386
  v: VRPC_PROTOCOL_VERSION
375
387
  }
376
388
  const topic = `${this._domain}/${agent}/${className}/__static__/__delete__`
@@ -659,10 +671,13 @@ class VrpcClient extends EventEmitter {
659
671
  return new Promise(resolve => this._client.end(false, {}, resolve))
660
672
  }
661
673
 
674
+ _generateUserName () {
675
+ return `${this._domain}:client@${os.hostname()}-${os.platform()}-js`
676
+ }
677
+
662
678
  _createMqttClientId () {
663
679
  const clientInfo =
664
680
  os.arch() +
665
- JSON.stringify(os.cpus()) +
666
681
  os.homedir() +
667
682
  os.hostname() +
668
683
  JSON.stringify(os.networkInterfaces()) +
@@ -680,7 +695,8 @@ class VrpcClient extends EventEmitter {
680
695
  }
681
696
 
682
697
  _createVrpcClientId () {
683
- return `${this._domain}/${os.hostname()}/${this._instance}`
698
+ const identity = this._identity || this._instance
699
+ return `${this._domain}/${os.hostname()}/${identity}`
684
700
  }
685
701
 
686
702
  _mqttPublish (topic, message, options) {
@@ -693,6 +709,12 @@ class VrpcClient extends EventEmitter {
693
709
  this._log.warn(
694
710
  `Could not publish MQTT message because: ${err.message}`
695
711
  )
712
+ if (err.message === 'client disconnecting') {
713
+ // Workaround for an already reported bug in MQTT.js (#1284)
714
+ this._log.info('Forcing reconnect now...')
715
+ this._client.disconnecting = false
716
+ this._client.end(true, {}, () => this._client.reconnect())
717
+ }
696
718
  }
697
719
  }
698
720
  )
@@ -705,9 +727,44 @@ class VrpcClient extends EventEmitter {
705
727
  (err, granted) => {
706
728
  if (err) {
707
729
  this._log.warn(
708
- `Could not subscribe to topic: ${topic} because: ${err.message}`
730
+ `Could not subscribe to topic(s): '${topic}', because: ${err.message}`
709
731
  )
732
+ if (err.message === 'client disconnecting') {
733
+ // Workaround for an already reported bug in MQTT.js (#1284)
734
+ this._log.info('Forcing reconnect now...')
735
+ this._client.disconnecting = false
736
+ this._client.end(true, {}, () => this._client.reconnect())
737
+ }
710
738
  } else {
739
+ const topicArray = Array.isArray(topic) ? topic : [topic]
740
+ const erroneousGranted = granted
741
+ .filter(x => x.qos === 128)
742
+ .map(x => x.topic)
743
+ if (erroneousGranted.length > 0) {
744
+ err = new Error(
745
+ `Could not subscribe all ${topicArray.length} topic(s) but got error qos=128 on following ${erroneousGranted.length} topic(s): ${erroneousGranted}`
746
+ )
747
+ err.code = 'SUBSCRIBE_FAILED'
748
+ err.subscribeOptions = options
749
+ this._log.error(err)
750
+ this.emit('error', err)
751
+ }
752
+ const reducedQos = granted.filter(x => x.qos < this._qos)
753
+ if (reducedQos.length > 0) {
754
+ err = new Error(
755
+ `Could not subscribe all ${
756
+ topicArray.length
757
+ } topic(s) at desired qos=${
758
+ this._qos
759
+ } but got reduced qos on following ${
760
+ reducedQos.length
761
+ } topic(s): ${JSON.stringify(reducedQos)}`
762
+ )
763
+ err.code = 'SUBSCRIBE_REDUCED_QOS'
764
+ err.subscribeOptions = options
765
+ this._log.warn(err)
766
+ this.emit('error', err)
767
+ }
711
768
  if (granted.length === 0) {
712
769
  this._log.debug(`Already subscribed to: ${topic}`)
713
770
  }
@@ -932,7 +989,8 @@ class VrpcClient extends EventEmitter {
932
989
  if (typeof ret === 'string' && ret.substring(0, 5) === '__p__') {
933
990
  this._eventEmitter.once(ret, promiseData => {
934
991
  // TODO improve error message here, like above
935
- if (promiseData.e) reject(new Error(`[vrpc ${agent}-${c}-${f}]: ${promiseData.e}`))
992
+ if (promiseData.e)
993
+ reject(new Error(`[vrpc ${agent}-${c}-${f}]: ${promiseData.e}`))
936
994
  else resolve(promiseData.r)
937
995
  })
938
996
  } else {
@@ -1067,7 +1125,9 @@ class VrpcClient extends EventEmitter {
1067
1125
  : `${this._domain}/${agent}/${className}/${event}`
1068
1126
  const id = `__e__${topic}`
1069
1127
  if (this._cachedSubscriptions[topic]) {
1070
- const index = this._cachedSubscriptions[topic].findIndex(x => x.callback === callback)
1128
+ const index = this._cachedSubscriptions[topic].findIndex(
1129
+ x => x.callback === callback
1130
+ )
1071
1131
  if (index === -1) {
1072
1132
  this._log.warn(`Failed removing event listener ${id}`)
1073
1133
  return null
@@ -106,12 +106,28 @@ class VrpcNative {
106
106
  if (functionName.startsWith('vrpcOn')) {
107
107
  const id = `__f__${context}-${functionName}`
108
108
  wrapped.push(id)
109
- eventEmitter.on(id, a => x.apply(null, a))
109
+ eventEmitter.on(id, a => {
110
+ try {
111
+ x.apply(null, a)
112
+ } catch (err) {
113
+ console.error(
114
+ `[VrpcNative ${context}-${functionName}]: Failed to execute callback, because: ${err.message}`
115
+ )
116
+ }
117
+ })
110
118
  } else {
111
119
  const id = `__f__${context}-${functionName}-${i}-${invokeId++ %
112
120
  Number.MAX_SAFE_INTEGER}`
113
121
  wrapped.push(id)
114
- eventEmitter.once(id, a => x.apply(null, a))
122
+ eventEmitter.once(id, a => {
123
+ try {
124
+ x.apply(null, a)
125
+ } catch (err) {
126
+ console.error(
127
+ `[VrpcNative ${context}-${functionName}]: Failed to execute callback, because: ${err.message}`
128
+ )
129
+ }
130
+ })
115
131
  }
116
132
  } else if (VrpcNative._isEmitter(x)) {
117
133
  const { emitter, event } = x
@@ -273,12 +289,28 @@ class VrpcNative {
273
289
  if (functionName.startsWith('vrpcOn')) {
274
290
  const id = `__f__${className}-${functionName}:${args[0]}`
275
291
  wrapped.push(id)
276
- this._eventEmitter.on(id, a => x.apply(null, a))
292
+ this._eventEmitter.on(id, a => {
293
+ try {
294
+ x.apply(null, a)
295
+ } catch (err) {
296
+ console.error(
297
+ `[VrpcNative ${className}-${functionName}]: Failed to execute callback, because: ${err.message}`
298
+ )
299
+ }
300
+ })
277
301
  } else {
278
302
  const id = `__f__${className}-${functionName}-${i}-${invokeId++ %
279
303
  Number.MAX_SAFE_INTEGER}`
280
304
  wrapped.push(id)
281
- this._eventEmitter.once(id, a => x.apply(null, a))
305
+ this._eventEmitter.once(id, a => {
306
+ try {
307
+ x.apply(null, a)
308
+ } catch (err) {
309
+ console.error(
310
+ `[VrpcNative ${className}-${functionName}]: Failed to execute callback, because: ${err.message}`
311
+ )
312
+ }
313
+ })
282
314
  }
283
315
  } else if (VrpcNative._isEmitter(x)) {
284
316
  const { emitter, event } = x