vrpc 3.0.4 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vrpc",
3
- "version": "3.0.4",
3
+ "version": "3.1.0",
4
4
  "description": "Variadic Remote Procedure Calls",
5
5
  "main": "index.js",
6
6
  "directories": {
@@ -499,7 +499,7 @@ class VrpcAdapter {
499
499
  }
500
500
 
501
501
  static sanitizeEventListenerReturnValues (ret) {
502
- return typeof ret === 'object' &&
502
+ return ret && typeof ret === 'object' &&
503
503
  ret._events !== undefined &&
504
504
  ret._eventsCount !== undefined
505
505
  ? true
@@ -606,7 +606,10 @@ class VrpcAdapter {
606
606
  if (clients.has(clientId)) {
607
607
  clients.delete(clientId)
608
608
  if (clients.size === 0) {
609
- VrpcAdapter.getInstance(ik).removeListener(event, listener)
609
+ const instance = VrpcAdapter.getInstance(ik)
610
+ if (instance && instance.removeListener) {
611
+ instance.removeListener(event, listener)
612
+ }
610
613
  delete VrpcAdapter._listeners[ik][ek]
611
614
  }
612
615
  }
package/vrpc/VrpcAgent.js CHANGED
@@ -101,6 +101,7 @@ class VrpcAgent extends EventEmitter {
101
101
  * @param {Object} [obj.log=console] Log object (must support debug, info, warn, and error level)
102
102
  * @param {String} [obj.bestEffort=false] If true, message will be sent with best effort, i.e. no caching if offline
103
103
  * @param {String} [obj.version=''] The (user-defined) version of this agent
104
+ * @param {String} [obj.mqttClientId='<generated()>'] Explicitly set the mqtt client id.
104
105
  *
105
106
  * @example
106
107
  * const agent = new Agent({
@@ -117,7 +118,8 @@ class VrpcAgent extends EventEmitter {
117
118
  broker = 'mqtts://vrpc.io:8883',
118
119
  log = 'console',
119
120
  bestEffort = false,
120
- version = ''
121
+ version = '',
122
+ mqttClientId = null
121
123
  } = {}) {
122
124
  super()
123
125
  this._validateDomain(domain)
@@ -130,6 +132,13 @@ class VrpcAgent extends EventEmitter {
130
132
  this._broker = broker
131
133
  this._qos = bestEffort ? 0 : 1
132
134
  this._version = version
135
+ this._mqttClientId =
136
+ mqttClientId ||
137
+ `va3${crypto
138
+ .createHash('md5')
139
+ .update(this._domain + this._agent)
140
+ .digest('hex')
141
+ .substring(0, 20)}`
133
142
  if (log === 'console') {
134
143
  this._log = console
135
144
  this._log.debug = () => {}
@@ -164,18 +173,13 @@ class VrpcAgent extends EventEmitter {
164
173
  * rejects
165
174
  */
166
175
  async serve () {
167
- const md5 = crypto
168
- .createHash('md5')
169
- .update(this._domain + this._agent)
170
- .digest('hex')
171
- .substring(0, 20)
172
176
  let username = this._username
173
177
  let password = this._password
174
178
  if (this._token) {
175
- username = `${this._domain}:${this._agent}`
179
+ username = `${this._domain}/${this._agent}`
176
180
  password = this._token
177
181
  } else if (!this._password) {
178
- username = `${this._domain}:${this._agent}`
182
+ username = `${this._domain}/${this._agent}`
179
183
  password = this._generateToken()
180
184
  }
181
185
  this._options = {
@@ -183,7 +187,7 @@ class VrpcAgent extends EventEmitter {
183
187
  password,
184
188
  keepalive: 30,
185
189
  connectTimeout: 10 * 1000,
186
- clientId: `va3${md5}`,
190
+ clientId: this._mqttClientId,
187
191
  rejectUnauthorized: false,
188
192
  will: {
189
193
  topic: `${this._baseTopic}/__agentInfo__`,
@@ -320,9 +324,38 @@ class VrpcAgent extends EventEmitter {
320
324
  (err, granted) => {
321
325
  if (err) {
322
326
  this._log.warn(
323
- `Could not subscribe to topic '${topic}', because: ${err.message}`
327
+ `Could not subscribe to topic(s) '${topic}', because: ${err.message}`
324
328
  )
325
329
  } else {
330
+ const topicArray = Array.isArray(topic) ? topic : [topic]
331
+ const erroneousGranted = granted
332
+ .filter(x => x.qos === 128)
333
+ .map(x => x.topic)
334
+ if (erroneousGranted.length > 0) {
335
+ err = new Error(
336
+ `Could not subscribe all ${topicArray.length} topic(s) but got error qos=128 on following ${erroneousGranted.length} topic(s): ${erroneousGranted}`
337
+ )
338
+ err.code = 'SUBSCRIBE_FAILED'
339
+ err.subscribeOptions = options
340
+ this._log.error(err)
341
+ this.emit('error', err)
342
+ }
343
+ const reducedQos = granted.filter(x => x.qos < this._qos)
344
+ if (reducedQos.length > 0) {
345
+ err = new Error(
346
+ `Could not subscribe all ${
347
+ topicArray.length
348
+ } topic(s) at desired qos=${
349
+ this._qos
350
+ } but got reduced qos on following ${
351
+ reducedQos.length
352
+ } topic(s): ${JSON.stringify(reducedQos)}`
353
+ )
354
+ err.code = 'SUBSCRIBE_REDUCED_QOS'
355
+ err.subscribeOptions = options
356
+ this._log.warn(err)
357
+ this.emit('error', err)
358
+ }
326
359
  if (granted.length === 0) {
327
360
  this._log.debug(`Already subscribed to topic '${topic}'`)
328
361
  }
@@ -68,6 +68,8 @@ class VrpcClient extends EventEmitter {
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',
@@ -83,7 +85,9 @@ class VrpcClient extends EventEmitter {
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()
@@ -324,7 +335,7 @@ class VrpcClient extends EventEmitter {
324
335
  f: isIsolated ? '__createIsolated__' : '__createShared__',
325
336
  a: [instance, ...args],
326
337
  i: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
327
- s: `${this._domain}/${os.hostname()}/${this._instance}`,
338
+ s: this._vrpcClientId,
328
339
  v: VRPC_PROTOCOL_VERSION
329
340
  }
330
341
  const proxy = await this._getProxy(agent, className, json)
@@ -371,7 +382,7 @@ class VrpcClient extends EventEmitter {
371
382
  f: '__delete__',
372
383
  a: [instance],
373
384
  i: `${this._instance}-${this._invokeId++ % Number.MAX_SAFE_INTEGER}`,
374
- s: `${this._domain}/${os.hostname()}/${this._instance}`,
385
+ s: this._vrpcClientId,
375
386
  v: VRPC_PROTOCOL_VERSION
376
387
  }
377
388
  const topic = `${this._domain}/${agent}/${className}/__static__/__delete__`
@@ -684,7 +695,8 @@ class VrpcClient extends EventEmitter {
684
695
  }
685
696
 
686
697
  _createVrpcClientId () {
687
- return `${this._domain}/${os.hostname()}/${this._instance}`
698
+ const identity = this._identity || this._instance
699
+ return `${this._domain}/${os.hostname()}/${identity}`
688
700
  }
689
701
 
690
702
  _mqttPublish (topic, message, options) {
@@ -715,7 +727,7 @@ class VrpcClient extends EventEmitter {
715
727
  (err, granted) => {
716
728
  if (err) {
717
729
  this._log.warn(
718
- `Could not subscribe to topic: ${topic} because: ${err.message}`
730
+ `Could not subscribe to topic(s): '${topic}', because: ${err.message}`
719
731
  )
720
732
  if (err.message === 'client disconnecting') {
721
733
  // Workaround for an already reported bug in MQTT.js (#1284)
@@ -724,6 +736,35 @@ class VrpcClient extends EventEmitter {
724
736
  this._client.end(true, {}, () => this._client.reconnect())
725
737
  }
726
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
+ }
727
768
  if (granted.length === 0) {
728
769
  this._log.debug(`Already subscribed to: ${topic}`)
729
770
  }
@@ -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