vrpc 3.0.3 → 3.0.4

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.3",
3
+ "version": "3.0.4",
4
4
  "description": "Variadic Remote Procedure Calls",
5
5
  "main": "index.js",
6
6
  "directories": {
@@ -390,7 +390,7 @@ class VrpcAdapter {
390
390
  }
391
391
 
392
392
  static _call (json) {
393
- const before = Object.keys(VrpcAdapter._listeners).length
393
+ this._mustTrackClient = false // reset client tracking flag
394
394
  switch (json.f) {
395
395
  case '__createIsolated__':
396
396
  VrpcAdapter._handleCreateIsolated(json)
@@ -408,10 +408,9 @@ class VrpcAdapter {
408
408
  // this call may have the side-effect of taking a listener on board
409
409
  VrpcAdapter._handleCall(json)
410
410
  }
411
- const after = Object.keys(VrpcAdapter._listeners).length
412
411
  // if we took a listener on board we must tell our caller such that
413
412
  // things can be cleaned up later.
414
- return after > before
413
+ return this._mustTrackClient
415
414
  }
416
415
 
417
416
  static _handleCreateIsolated (json) {
@@ -593,13 +592,7 @@ class VrpcAdapter {
593
592
  if (VrpcAdapter._isFunction(entry.removeAllListeners)) {
594
593
  entry.removeAllListeners()
595
594
  }
596
- for (const [key, { clients, instance }] of Object.entries(
597
- VrpcAdapter._listeners
598
- )) {
599
- if (instance === instanceId) {
600
- VrpcAdapter._listeners[key] = { clients }
601
- }
602
- }
595
+ delete VrpcAdapter._listeners[instanceId]
603
596
  VrpcAdapter._instances.delete(instanceId)
604
597
  return true
605
598
  }
@@ -607,18 +600,18 @@ class VrpcAdapter {
607
600
  }
608
601
 
609
602
  static _unregisterClient (clientId) {
610
- for (const [key, value] of Object.entries(VrpcAdapter._listeners)) {
611
- const { instances, event, listener, clients } = value
612
- clients.delete(clientId)
613
- if (clients.size === 0) {
614
- if (event) {
615
- instances.forEach(instance => {
616
- VrpcAdapter.getInstance(instance).removeListener(event, listener)
617
- })
603
+ Object.entries(VrpcAdapter._listeners).forEach(([ik, iv]) => {
604
+ Object.entries(iv).forEach(([ek, ev]) => {
605
+ const { clients, listener, event } = ev
606
+ if (clients.has(clientId)) {
607
+ clients.delete(clientId)
608
+ if (clients.size === 0) {
609
+ VrpcAdapter.getInstance(ik).removeListener(event, listener)
610
+ delete VrpcAdapter._listeners[ik][ek]
611
+ }
618
612
  }
619
- delete VrpcAdapter._listeners[key]
620
- }
621
- }
613
+ })
614
+ })
622
615
  }
623
616
 
624
617
  static _validate (schema, params) {
@@ -630,102 +623,162 @@ class VrpcAdapter {
630
623
  }
631
624
 
632
625
  static _unwrapArguments (json, instanceId) {
633
- const sender = json.s
634
- let context = json.c
635
- let func = json.f
636
- let args = json.a
637
- // things are different if we came from callAll (instanceId !== undefined)
638
- if (instanceId) {
639
- context = instanceId
640
- func = args[0]
641
- args = args.slice(1)
642
- }
643
- const unwrapped = instanceId ? [func] : []
626
+ const clientId = json.s
627
+ // if we came from a "callAll" we will have an instanceId defined
628
+ // things are different for callAll as we have to dispatch the instanceIds
629
+ const isCallAll = instanceId !== undefined
630
+ const context = isCallAll ? instanceId : json.c
631
+ const args = isCallAll ? json.a.slice(1) : json.a
632
+ const func = isCallAll ? json.a[0] : json.f
633
+ const unwrapped = isCallAll ? [func] : []
644
634
  for (const arg of args) {
645
- // find those args that actually need to be functions
646
- if (typeof arg === 'string' && arg.slice(0, 5) === '__f__') {
647
- const callback = this._generateCallback(arg, sender, instanceId)
648
- unwrapped.push(callback)
649
- } else if (typeof arg === 'string' && arg.slice(0, 5) === '__e__') {
650
- const listener = this._generateListener(arg, sender, instanceId)
651
- unwrapped.push(listener)
652
- // start tracking clients that are registering event listeners
653
- if (!VrpcAdapter._listeners[arg]) {
654
- VrpcAdapter._listeners[arg] = {
655
- listener,
656
- clients: new Set([sender]),
657
- instances: new Set(),
658
- event: null
635
+ if (typeof arg === 'string') {
636
+ if (arg.startsWith('__f__')) {
637
+ // function callback
638
+ unwrapped.push(
639
+ this._generateCallback({
640
+ clientId,
641
+ isCallAll,
642
+ instanceId: context,
643
+ eventId: arg
644
+ })
645
+ )
646
+ } else if (arg.startsWith('__e__')) {
647
+ // event registration
648
+ if (func === 'on' || func === 'addListener') {
649
+ this._mustTrackClient = true
650
+ const listener = VrpcAdapter._registerListener({
651
+ clientId,
652
+ isCallAll,
653
+ instanceId: context,
654
+ eventId: arg,
655
+ event: args[0]
656
+ })
657
+ if (
658
+ VrpcAdapter.getInstance(context)
659
+ .listeners(arg)
660
+ .includes(listener)
661
+ ) {
662
+ return null // skip call as listener is already registered
663
+ }
664
+ unwrapped.push(listener)
665
+ // event un-registration
666
+ } else if (func === 'off' || func === 'removeListener') {
667
+ const listener = VrpcAdapter._unregisterListener({
668
+ clientId,
669
+ instanceId: context,
670
+ eventId: arg
671
+ })
672
+ if (!listener) {
673
+ return null // skip call as others are still interested
674
+ }
675
+ unwrapped.push(listener)
676
+ } else {
677
+ this._mustTrackClient = true
678
+ const listener = VrpcAdapter._registerListener({
679
+ clientId,
680
+ isCallAll,
681
+ instanceId: context,
682
+ eventId: arg,
683
+ event: null
684
+ })
685
+ unwrapped.push(listener)
659
686
  }
660
687
  } else {
661
- VrpcAdapter._listeners[arg].clients.add(sender)
662
- }
663
- // see whether the requested listener is an EventEmitter registration
664
- if (func === 'on' || func === 'addListener') {
665
- // if this event is already registered on the given instance
666
- // avoid a second registration
667
- if (
668
- VrpcAdapter._listeners[arg].event &&
669
- VrpcAdapter._listeners[arg].instances.has(context)
670
- ) {
671
- return null // means that this call won't be forwarded
672
- }
673
- VrpcAdapter._listeners[arg].event = args[0]
674
- VrpcAdapter._listeners[arg].instances.add(context)
675
- }
676
- if (func === 'off' || func === 'removeListener') {
677
- const {
678
- clients,
679
- instances,
680
- listener,
681
- event
682
- } = VrpcAdapter._listeners[arg]
683
- clients.delete(sender)
684
- instances.delete(context)
685
- if (clients.size === 0) {
686
- delete VrpcAdapter._listeners[arg]
687
- VrpcAdapter.getInstance(context).off(event, listener)
688
- }
689
- return null // means that this call won't be forwarded
688
+ unwrapped.push(arg)
690
689
  }
691
690
  } else {
692
- // default behavior is to not touch
691
+ // regular argument
693
692
  unwrapped.push(arg)
694
693
  }
695
694
  }
696
695
  return unwrapped
697
696
  }
698
697
 
699
- static _removeAllListeners (event, sender, context) {
700
- for (const [key, value] of Object.entries(VrpcAdapter._listeners)) {
701
- if (value.event === event) {
702
- value.clients.delete(sender)
703
- if (value.clients.size === 0) {
704
- delete VrpcAdapter._listeners[key]
705
- VrpcAdapter.getInstance(context).off(event, value.listener)
698
+ static _generateCallback ({ eventId, clientId, instanceId, isCallAll }) {
699
+ return (...innerArgs) => {
700
+ // "callAll" event notifications expect the instanceId as first argument
701
+ const a = isCallAll ? [instanceId, ...innerArgs] : [...innerArgs]
702
+ VrpcAdapter._callback({ a, s: clientId, i: eventId })
703
+ }
704
+ }
705
+
706
+ static _registerListener ({
707
+ eventId,
708
+ clientId,
709
+ instanceId,
710
+ event,
711
+ isCallAll
712
+ }) {
713
+ if (!VrpcAdapter._listeners[instanceId]) {
714
+ VrpcAdapter._listeners[instanceId] = {
715
+ [eventId]: {
716
+ event,
717
+ clients: new Set([clientId]),
718
+ listener: VrpcAdapter._generateListener({
719
+ eventId,
720
+ instanceId,
721
+ isCallAll
722
+ })
706
723
  }
707
724
  }
725
+ } else if (!VrpcAdapter._listeners[instanceId][eventId]) {
726
+ VrpcAdapter._listeners[instanceId][eventId] = {
727
+ event,
728
+ clients: new Set([clientId]),
729
+ listener: VrpcAdapter._generateListener({
730
+ eventId,
731
+ instanceId,
732
+ isCallAll
733
+ })
734
+ }
735
+ } else {
736
+ VrpcAdapter._listeners[instanceId][eventId].clients.add(clientId)
708
737
  }
738
+ return VrpcAdapter._listeners[instanceId][eventId].listener
709
739
  }
710
740
 
711
- static _generateCallback (id, sender, instanceId) {
741
+ static _generateListener ({ eventId, instanceId, isCallAll }) {
712
742
  return (...innerArgs) => {
713
- // callAll expects the instanceId as first argument
714
- const a = instanceId ? [instanceId, ...innerArgs] : [...innerArgs]
715
- VrpcAdapter._callback({ a, s: sender, i: id })
743
+ const a = isCallAll ? [instanceId, ...innerArgs] : [...innerArgs]
744
+ VrpcAdapter._callback({ a, i: eventId })
716
745
  }
717
746
  }
718
747
 
719
- static _generateListener (id, sender, instanceId) {
720
- return (...innerArgs) => {
721
- // do not even emit for already offline clients
722
- if (!VrpcAdapter._listeners[id]) return
723
- // callAll expects the instanceId as first argument
724
- const a = instanceId ? [instanceId, ...innerArgs] : [...innerArgs]
725
- VrpcAdapter._callback({ a, s: sender, i: id })
748
+ static _unregisterListener ({ eventId, clientId, instanceId }) {
749
+ if (
750
+ VrpcAdapter._listeners[instanceId] &&
751
+ VrpcAdapter._listeners[instanceId][eventId]
752
+ ) {
753
+ const { listener, clients } = VrpcAdapter._listeners[instanceId][eventId]
754
+ clients.delete(clientId)
755
+ if (clients.size === 0) {
756
+ delete VrpcAdapter._listeners[instanceId][eventId]
757
+ if (Object.keys(VrpcAdapter._listeners[instanceId]).length === 0) {
758
+ delete VrpcAdapter._listeners[instanceId]
759
+ }
760
+ return listener
761
+ }
726
762
  }
727
763
  }
728
764
 
765
+ static _removeAllListeners (event, clientId, instanceId) {
766
+ const eventIds = VrpcAdapter._listeners[instanceId]
767
+ if (!eventIds) return
768
+ Object.entries(eventIds).forEach(([ek, ev]) => {
769
+ if (ev.event === event && ev.clients.has(clientId)) {
770
+ ev.clients.delete(clientId)
771
+ if (ev.clients.size === 0) {
772
+ VrpcAdapter.getInstance(instanceId).removeListener(
773
+ ev.event,
774
+ ev.listener
775
+ )
776
+ delete VrpcAdapter._listeners[instanceId][ek]
777
+ }
778
+ }
779
+ })
780
+ }
781
+
729
782
  static _handlePromise (json, promise) {
730
783
  try {
731
784
  const cid = VrpcAdapter._correlationId++ % Number.MAX_SAFE_INTEGER
package/vrpc/VrpcAgent.js CHANGED
@@ -92,7 +92,7 @@ class VrpcAgent extends EventEmitter {
92
92
  *
93
93
  * @constructor
94
94
  * @param {Object} obj
95
- * @param {String} [obj.username='<user>-<pathId>@<hostname>-<platform>-js'] MQTT username
95
+ * @param {String} [obj.username] MQTT username
96
96
  * @param {String} [obj.password] MQTT password (if no token is provided)
97
97
  * @param {String} [obj.token] Access token
98
98
  * @param {String} [obj.domain='vrpc'] The domain under which the agent-provided code is reachable
@@ -109,9 +109,9 @@ class VrpcAgent extends EventEmitter {
109
109
  * })
110
110
  */
111
111
  constructor ({
112
- token,
112
+ username,
113
113
  password,
114
- username = VrpcAgent._generateAgentName(),
114
+ token,
115
115
  domain = 'vrpc',
116
116
  agent = VrpcAgent._generateAgentName(),
117
117
  broker = 'mqtts://vrpc.io:8883',
@@ -130,7 +130,6 @@ class VrpcAgent extends EventEmitter {
130
130
  this._broker = broker
131
131
  this._qos = bestEffort ? 0 : 1
132
132
  this._version = version
133
- this._isReconnect = false
134
133
  if (log === 'console') {
135
134
  this._log = console
136
135
  this._log.debug = () => {}
@@ -169,12 +168,15 @@ class VrpcAgent extends EventEmitter {
169
168
  .createHash('md5')
170
169
  .update(this._domain + this._agent)
171
170
  .digest('hex')
172
- .substr(0, 20)
171
+ .substring(0, 20)
173
172
  let username = this._username
174
173
  let password = this._password
175
174
  if (this._token) {
176
- username = username || this._agent
175
+ username = `${this._domain}:${this._agent}`
177
176
  password = this._token
177
+ } else if (!this._password) {
178
+ username = `${this._domain}:${this._agent}`
179
+ password = this._generateToken()
178
180
  }
179
181
  this._options = {
180
182
  username,
@@ -194,12 +196,7 @@ class VrpcAgent extends EventEmitter {
194
196
  this._log.info(`Agent : ${this._agent}`)
195
197
  this._log.info(`Broker : ${this._broker}`)
196
198
  this._log.info('Connecting to MQTT server...')
197
- const wasEnded = await this._clearPersistedSession()
198
- if (wasEnded) return
199
- this._client = mqtt.connect(this._broker, {
200
- ...this._options,
201
- clean: false
202
- })
199
+ this._client = mqtt.connect(this._broker, this._options)
203
200
  this._client.on('connect', this._handleConnect.bind(this))
204
201
  this._client.on('reconnect', this._handleReconnect.bind(this))
205
202
  this._client.on('error', this._handleError.bind(this))
@@ -238,16 +235,11 @@ class VrpcAgent extends EventEmitter {
238
235
  }
239
236
  }
240
237
  await new Promise(resolve => this._client.end(false, {}, resolve))
241
- await this._clearPersistedSession()
242
238
  } catch (err) {
243
239
  this._log.error(err, `Problem during disconnecting agent: ${err.message}`)
244
240
  }
245
241
  }
246
242
 
247
- static _generateUserName () {
248
- return `${os.userInfo().username}@${os.hostname()}-${os.platform()}-js`
249
- }
250
-
251
243
  static _generateAgentName () {
252
244
  const { username } = os.userInfo()
253
245
  const pathId = crypto
@@ -258,6 +250,27 @@ class VrpcAgent extends EventEmitter {
258
250
  return `${username}-${pathId}@${os.hostname()}-${os.platform()}-js`
259
251
  }
260
252
 
253
+ _generateToken () {
254
+ const uid =
255
+ this._domain +
256
+ this._agent +
257
+ os.userInfo().username +
258
+ path.resolve() +
259
+ os.arch() +
260
+ os.homedir() +
261
+ os.hostname() +
262
+ os.platform() +
263
+ os.release() +
264
+ os.totalmem() +
265
+ os.type() +
266
+ JSON.stringify(os.networkInterfaces()) +
267
+ JSON.stringify(os.cpus().map(({ model }) => model))
268
+ return crypto
269
+ .createHash('md5')
270
+ .update(uid)
271
+ .digest('hex')
272
+ }
273
+
261
274
  _validateDomain (domain) {
262
275
  if (!domain) throw new Error('The domain must be specified')
263
276
  if (domain.match(/[+/#*]/)) {
@@ -285,27 +298,6 @@ class VrpcAgent extends EventEmitter {
285
298
  })
286
299
  }
287
300
 
288
- /**
289
- * @return {Boolean} true if `agent.end()` was explicitly called
290
- */
291
- async _clearPersistedSession () {
292
- // Clear potentially existing persisted sessions
293
- const client = mqtt.connect(this._broker, { ...this._options, clean: true })
294
- client.on('error', err => this.emit('error', err))
295
- client.on('offline', () => this.emit('offline'))
296
- client.on('reconnect', () => this.emit('reconnect'))
297
- return new Promise(resolve => {
298
- client.on('connect', () => {
299
- client.end(false, {}, () => resolve(false))
300
- })
301
- // Will be triggered if the user called 'agent.end()'
302
- this.once('end', () => {
303
- client.end()
304
- resolve(true)
305
- })
306
- })
307
- }
308
-
309
301
  _mqttPublish (topic, message, options) {
310
302
  this._client.publish(
311
303
  topic,
@@ -375,6 +367,8 @@ class VrpcAgent extends EventEmitter {
375
367
  resolve()
376
368
  } else {
377
369
  this._client.once('connect', resolve)
370
+ // Will be triggered if the user called 'agent.end()'
371
+ this.once('end', resolve)
378
372
  }
379
373
  })
380
374
  }
@@ -413,16 +407,6 @@ class VrpcAgent extends EventEmitter {
413
407
 
414
408
  _handleConnect () {
415
409
  this._log.info('[OK]')
416
- if (this._isReconnect) {
417
- this._publishAgentInfoMessage()
418
- const classes = this._getClasses()
419
- classes.forEach(className => {
420
- this._publishClassInfoMessage(className)
421
- })
422
- this.emit('connect')
423
- this._isReconnect = false
424
- return
425
- }
426
410
  try {
427
411
  const topics = this._generateTopics()
428
412
  if (topics.length > 0) this._mqttSubscribe(topics)
@@ -670,7 +654,6 @@ class VrpcAgent extends EventEmitter {
670
654
  }
671
655
 
672
656
  _handleReconnect () {
673
- this._isReconnect = true
674
657
  this._log.warn(`Reconnecting to ${this._broker}`)
675
658
  this.emit('reconnect')
676
659
  }
@@ -59,7 +59,7 @@ class VrpcClient extends EventEmitter {
59
59
  *
60
60
  * @constructor
61
61
  * @param {Object} options
62
- * @param {String} [options.username='<user>@<hostname>-<platform>-js'] MQTT username
62
+ * @param {String} [options.username] MQTT username
63
63
  * @param {String} [options.password] MQTT password (if no token is provided)
64
64
  * @param {String} [options.token] Access token
65
65
  * @param {String} options.domain Sets the domain
@@ -77,7 +77,7 @@ class VrpcClient extends EventEmitter {
77
77
  constructor ({
78
78
  token,
79
79
  password,
80
- username = VrpcClient._generateUserName(),
80
+ username,
81
81
  domain,
82
82
  agent = '*',
83
83
  broker = 'mqtts://vrpc.io:8883',
@@ -152,7 +152,7 @@ class VrpcClient extends EventEmitter {
152
152
  let username = this._username
153
153
  let password = this._password
154
154
  if (this._token) {
155
- username = username || VrpcClient._generateUserName()
155
+ username = username || this._generateUserName()
156
156
  password = this._token
157
157
  }
158
158
  const options = {
@@ -660,14 +660,13 @@ class VrpcClient extends EventEmitter {
660
660
  return new Promise(resolve => this._client.end(false, {}, resolve))
661
661
  }
662
662
 
663
- static _generateUserName () {
664
- return `${os.userInfo().username}@${os.hostname()}-${os.platform()}-js`
663
+ _generateUserName () {
664
+ return `${this._domain}:client@${os.hostname()}-${os.platform()}-js`
665
665
  }
666
666
 
667
667
  _createMqttClientId () {
668
668
  const clientInfo =
669
669
  os.arch() +
670
- JSON.stringify(os.cpus()) +
671
670
  os.homedir() +
672
671
  os.hostname() +
673
672
  JSON.stringify(os.networkInterfaces()) +
@@ -698,6 +697,12 @@ class VrpcClient extends EventEmitter {
698
697
  this._log.warn(
699
698
  `Could not publish MQTT message because: ${err.message}`
700
699
  )
700
+ if (err.message === 'client disconnecting') {
701
+ // Workaround for an already reported bug in MQTT.js (#1284)
702
+ this._log.info('Forcing reconnect now...')
703
+ this._client.disconnecting = false
704
+ this._client.end(true, {}, () => this._client.reconnect())
705
+ }
701
706
  }
702
707
  }
703
708
  )
@@ -712,6 +717,12 @@ class VrpcClient extends EventEmitter {
712
717
  this._log.warn(
713
718
  `Could not subscribe to topic: ${topic} because: ${err.message}`
714
719
  )
720
+ if (err.message === 'client disconnecting') {
721
+ // Workaround for an already reported bug in MQTT.js (#1284)
722
+ this._log.info('Forcing reconnect now...')
723
+ this._client.disconnecting = false
724
+ this._client.end(true, {}, () => this._client.reconnect())
725
+ }
715
726
  } else {
716
727
  if (granted.length === 0) {
717
728
  this._log.debug(`Already subscribed to: ${topic}`)