bt-sensors-plugin-sk 1.1.1 → 1.2.0-beta.0.0.10

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.
Files changed (56) hide show
  1. package/BTSensor.js +304 -108
  2. package/Queue.js +48 -0
  3. package/README.md +36 -22
  4. package/classLoader.js +38 -0
  5. package/definitions.json +941 -0
  6. package/index.js +425 -375
  7. package/package.json +52 -6
  8. package/plugin_defaults.json +146 -0
  9. package/public/893.js +1 -1
  10. package/public/main.js +1 -100
  11. package/public/remoteEntry.js +1 -129
  12. package/sensor_classes/ATC.js +34 -22
  13. package/sensor_classes/Aranet/AranetSensor.js +4 -0
  14. package/sensor_classes/Aranet2.js +16 -15
  15. package/sensor_classes/Aranet4.js +23 -17
  16. package/sensor_classes/BlackListedDevice.js +4 -0
  17. package/sensor_classes/DEVELOPMENT.md +1 -6
  18. package/sensor_classes/GoveeH50xx.js +12 -12
  19. package/sensor_classes/GoveeH510x.js +7 -8
  20. package/sensor_classes/IBeacon.js +6 -10
  21. package/sensor_classes/Inkbird.js +8 -6
  22. package/sensor_classes/JBDBMS.js +33 -25
  23. package/sensor_classes/KilovaultHLXPlus.js +33 -16
  24. package/sensor_classes/LancolVoltageMeter.js +4 -3
  25. package/sensor_classes/MopekaTankSensor.js +31 -15
  26. package/sensor_classes/Renogy/RenogySensor.js +15 -6
  27. package/sensor_classes/RenogyBattery.js +15 -13
  28. package/sensor_classes/RenogyRoverClient.js +2 -3
  29. package/sensor_classes/RuuviTag.js +36 -27
  30. package/sensor_classes/ShellySBHT003C.js +19 -22
  31. package/sensor_classes/SwitchBotMeterPlus.js +10 -12
  32. package/sensor_classes/SwitchBotTH.js +13 -16
  33. package/sensor_classes/UNKNOWN.js +8 -4
  34. package/sensor_classes/UltrasonicWindMeter.js +13 -5
  35. package/sensor_classes/Victron/VictronSensor.js +6 -2
  36. package/sensor_classes/VictronACCharger.js +19 -8
  37. package/sensor_classes/VictronBatteryMonitor.js +49 -37
  38. package/sensor_classes/VictronDCDCConverter.js +14 -5
  39. package/sensor_classes/VictronDCEnergyMeter.js +27 -15
  40. package/sensor_classes/VictronGXDevice.js +2 -5
  41. package/sensor_classes/VictronInverter.js +19 -15
  42. package/sensor_classes/VictronInverterRS.js +19 -8
  43. package/sensor_classes/VictronLynxSmartBMS.js +15 -13
  44. package/sensor_classes/VictronOrionXS.js +16 -3
  45. package/sensor_classes/VictronSmartBatteryProtect.js +5 -6
  46. package/sensor_classes/VictronSmartLithium.js +18 -18
  47. package/sensor_classes/VictronSolarCharger.js +31 -18
  48. package/sensor_classes/VictronVEBus.js +2 -5
  49. package/sensor_classes/XiaomiMiBeacon.js +31 -21
  50. package/spec/electrical.json +688 -0
  51. package/spec/environment.json +401 -0
  52. package/spec/sensors.json +39 -0
  53. package/spec/tanks.json +115 -0
  54. package/src/components/PluginConfigurationPanel.js +393 -0
  55. package/src/index.js +0 -0
  56. package/webpack.config.js +71 -0
package/BTSensor.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const { Variant } = require('dbus-next');
2
2
  const { log } = require('node:console');
3
3
  const EventEmitter = require('node:events');
4
+ const AutoQueue = require("./Queue.js")
4
5
 
5
6
  /**
6
7
  * @author Andrew Gerngross <oh.that.andy@gmail.com>
@@ -10,7 +11,9 @@ const EventEmitter = require('node:events');
10
11
  * {@link module:node-ble}
11
12
  */
12
13
 
13
- const BTCompanies = require('./bt_co.json')
14
+ const BTCompanies = require('./bt_co.json');
15
+ const connectQueue = new AutoQueue()
16
+
14
17
  /**
15
18
  * @global A map of company names keyed by their Bluetooth ID
16
19
  * {@link ./sensor_classes/bt_co.json} file derived from bluetooth-sig source:
@@ -51,6 +54,38 @@ function signalQualityPercentQuad(rssi, perfect_rssi=-20, worst_rssi=-85) {
51
54
  }
52
55
  return Math.ceil(signal_quality);
53
56
  }
57
+ function preparePath(obj, str) {
58
+ const regex = /\{([^}]+)\}/g;
59
+ let match;
60
+ let resultString = "";
61
+ let lastIndex = 0;
62
+
63
+ while ((match = regex.exec(str)) !== null) {
64
+ const fullMatch = match[0];
65
+ const keyToAccess = match[1].trim();
66
+
67
+ // Append the text before the current curly braces
68
+ resultString += str.substring(lastIndex, match.index);
69
+ lastIndex = regex.lastIndex;
70
+
71
+ try {
72
+ let evalResult = obj[keyToAccess];
73
+ if (typeof evalResult === 'function'){
74
+ evalResult= evalResult.call(obj)
75
+ }
76
+
77
+ resultString += evalResult !== undefined ? evalResult : `${keyToAccess}_value_undefined`;
78
+ } catch (error) {
79
+ console.error(`Error accessing key '${keyToAccess}':`, error);
80
+ resultString += fullMatch; // Keep the original curly braces on error
81
+ }
82
+ }
83
+
84
+ // Append any remaining text after the last curly braces
85
+ resultString += str.substring(lastIndex);
86
+
87
+ return resultString || str; // Return original string if no replacements were made
88
+ }
54
89
 
55
90
  /**
56
91
  * @classdesc Class that all sensor classes should inherit from. Sensor subclasses
@@ -65,8 +100,9 @@ function signalQualityPercentQuad(rssi, perfect_rssi=-20, worst_rssi=-85) {
65
100
  */
66
101
 
67
102
  class BTSensor extends EventEmitter {
68
- static metadata=new Map()
69
-
103
+
104
+ static DEFAULTS = require('./plugin_defaults.json');
105
+
70
106
  /**
71
107
  *
72
108
  * @param {module:node-ble/Device} device
@@ -77,13 +113,11 @@ class BTSensor extends EventEmitter {
77
113
  super()
78
114
 
79
115
  this.device=device
80
- this.name = config?.name
81
-
82
- this.useGATT = gattConfig?.useGATT
83
- this.pollFreq = gattConfig?.pollFreq
84
-
85
- this.Metadatum = this.constructor.Metadatum
86
- this.metadata = new Map()
116
+
117
+ Object.assign(this,config)
118
+ Object.assign(this,gattConfig)
119
+
120
+ this._state = null
87
121
  }
88
122
  /**
89
123
  * @function _test Test sensor parsing
@@ -133,7 +167,7 @@ class BTSensor extends EventEmitter {
133
167
  var b = Buffer.from(data.replaceAll(" ",""),"hex")
134
168
  const d = new this(null,config)
135
169
  d.initMetadata()
136
- d.getPathMetadata().forEach((datum,tag)=>{
170
+ Object.keys(d.getPaths()).forEach((tag)=>{
137
171
  d.on(tag,(v)=>console.log(`${tag}=${v}`))
138
172
  })
139
173
  if (key) {
@@ -145,38 +179,7 @@ class BTSensor extends EventEmitter {
145
179
  d.removeAllListeners()
146
180
 
147
181
  }
148
- static Metadatum =
149
- /**
150
- * @class encapsulates a sensor's metadata
151
- * @todo refactor and/or just plain rethink constructor parameters
152
- */
153
- class Metadatum{
154
-
155
- constructor(tag, unit, description, read, gatt=null, type){
156
- this.tag = tag
157
- this.unit = unit
158
- this.description = description
159
- this.read = read
160
- this.gatt = gatt
161
- this.type = type //schema type e.g. 'number'
162
- }
163
- /**
164
- *
165
- * @returns A JSON object passed by plugin to the plugin's schema
166
- * dynamically updated at runtime upon discovery and interrogation
167
- * of the device
168
- */
169
- asJSONSchema(){
170
- return {
171
- type:this?.type??'string',
172
- title: this?.description,
173
- unit: this?.unit,
174
- enum: this?.enum,
175
- default: this?.default
176
- }
177
- }
178
- }
179
-
182
+
180
183
  //static utility Functions
181
184
  /**
182
185
  *
@@ -274,42 +277,157 @@ class BTSensor extends EventEmitter {
274
277
  *
275
278
  */
276
279
 
277
- async init(){
280
+ initSchema(){
281
+ this._schema = {
282
+ properties:{
283
+ active: {title: "Active", type: "boolean", default: true },
284
+ discoveryTimeout: {title: "Device discovery timeout (in seconds)",
285
+ type: "integer", default:30,
286
+ minimum: 10,
287
+ maximum: 600 },
288
+
289
+ params:{
290
+ title:`Device parameters`,
291
+ description: this.getDescription(),
292
+ type:"object",
293
+ properties:{}
294
+ },
295
+ paths:{
296
+ title:"Signalk Paths",
297
+ description: `Signalk paths to be updated when ${this.getName()}'s values change`,
298
+ type:"object",
299
+ properties:{}
300
+ }
301
+ }
302
+ }
303
+
304
+ if (this.hasGATT()){
305
+
306
+ this._schema.properties.gattParams={
307
+ title:`GATT Specific device parameters`,
308
+ description: this.getGATTDescription(),
309
+ type:"object",
310
+ properties:{
311
+ useGATT: {title: "Use GATT connection", type: "boolean", default: false },
312
+ pollFreq: { type: "number", title: "Polling frequency in seconds"}
313
+ }
314
+ }
315
+ }
316
+
317
+
278
318
  //create the 'name' parameter
279
- var md = this.addMetadatum("name", "string","Name of sensor" )
280
- md.isParam=true
319
+ this.addDefaultParam("name")
320
+ .default=this?.currentProperties?.Name
321
+
322
+ //create the 'location' parameter
323
+
324
+ //this.addDefaultParam("location")
325
+
281
326
  //create the 'RSSI' parameter
327
+ this.addDefaultPath("RSSI","sensors.RSSI")
328
+ this.getPath("RSSI").read=()=>{return this.getRSSI()}
329
+ this.getPath("RSSI").read.bind(this)
330
+
331
+ }
332
+ async init(){
282
333
  this.currentProperties = await this.constructor.getDeviceProps(this.device)
283
- this.addMetadatum("RSSI","db","Signal strength in db")
284
- this.getMetadatum("RSSI").default=`sensors.${this.getMacAddress().replaceAll(':', '')}.rssi`
285
- this.getMetadatum("RSSI").read=()=>{return this.getRSSI()}
286
- this.getMetadatum("RSSI").read.bind(this)
287
- //create GATT params (iff sensor is GATT-ish)
288
- if (this.hasGATT()) {
289
- md = this.addMetadatum("useGATT", "boolean", "Use GATT connection")
290
- md.type="boolean"
291
- md.isParam=true
292
- md.isGATT=true
293
-
294
- md = this.addMetadatum("pollFreq", "s", "Polling frequency in seconds")
295
- md.type="number"
296
- md.isParam=true
297
- md.isGATT=true
334
+ this.initSchema()
335
+
336
+ this.initListen()
337
+ }
338
+
339
+ initListen(){
340
+ Promise.resolve(this.listen())
341
+ }
342
+ activate(config, plugin){
343
+ if (config.paths){
344
+ this.createPaths(config,plugin.id)
345
+ this.initPaths(config,plugin.id)
346
+ this.debug(`Paths activated for ${this.getDisplayName()}`);
298
347
  }
348
+ if (this.usingGATT()){
349
+ try {
350
+ this.activateGATT()
351
+ } catch (e) {
352
+ this.debug(`GATT services unavailable for ${this.getName()}. Reason: ${e}`)
353
+ this._state="ERROR"
354
+ return
355
+ }
356
+ }
357
+ this._state="ACTIVE"
358
+ }
359
+
360
+ activateGATT(){
361
+ this.initGATTConnection().then(async ()=>{
362
+ this.emitGATT()
363
+ if (this.pollFreq){
364
+ this.initGATTInterval()
365
+ }
366
+ else
367
+ await this.initGATTNotifications()
368
+ }).catch((e)=>{
369
+ this.app.debug(`Unable to activate GATT connection for ${this.getName()} (${this.getMacAddress()}): ${e}`)
370
+ })
299
371
  }
300
372
 
301
373
  /**
302
374
  * Add a metadatum instance to the sensor instance
303
375
  *
304
- * @param {String} tag
376
+ * @param {String} tag
305
377
  * @param {...any} args
306
378
  * @returns {this.Metadatum} the new metadatum instance
307
379
  */
308
380
 
309
381
  addMetadatum(tag, ...args){
310
- var metadatum = new this.Metadatum(tag, ...args)
311
- this.getMetadata().set(tag, metadatum)
312
- return metadatum
382
+
383
+ const md = {}
384
+ if (args[0]) md.unit = args[0]
385
+ if (args[1]) md.title = args[1]
386
+ if (args[2]) md.read = args[2]
387
+ if (args[3]) md.gatt = args[3]
388
+ if (args[4]) md.type = args[4]
389
+
390
+ return this.addPath(tag,md)
391
+ }
392
+
393
+ addParameter(tag, param){
394
+
395
+ if (!param.type)
396
+ param.type="string"
397
+
398
+ this._schema.properties.params.properties[tag]=param
399
+ return this._schema.properties.params.properties[tag]
400
+ }
401
+
402
+ addPath(tag, path){
403
+ if (!path.type)
404
+ path.type="string"
405
+
406
+ if (!path.pattern)
407
+ path.pattern="^(?:[^{}\\s]*\\{[a-zA-Z0-9]+\\}[^{}\\s]*|[^{}\\s]*)$"
408
+ this._schema.properties.paths.properties[tag]=path
409
+ return this._schema.properties.paths.properties[tag]
410
+ }
411
+
412
+ addGATTParameter(tag, param){
413
+
414
+ if (!param.type)
415
+ param.type="string"
416
+
417
+ return this._schema.properties.gattParams.properties[tag]=param
418
+ }
419
+
420
+ addDefaultPath(tag,defaultPath){
421
+ const path = eval(`BTSensor.DEFAULTS.${defaultPath}`)
422
+ return this.addPath(tag,Object.assign({}, path))
423
+ }
424
+
425
+ addDefaultParam(tag){
426
+ return this.addParameter(tag,Object.assign({}, BTSensor.DEFAULTS.params[tag]))
427
+ }
428
+
429
+ getJSONSchema(){
430
+ return this._schema
313
431
  }
314
432
 
315
433
  //GATT Initialization functions
@@ -337,6 +455,39 @@ class BTSensor extends EventEmitter {
337
455
  throw new Error("::initGATTNotifications() should be implemented by the BTSensor subclass")
338
456
  }
339
457
 
458
+ deviceConnect() {
459
+
460
+ /* CAUTION: HACK AHEAD
461
+
462
+ Bluez for some cockamamie reason (It's 2025 for chrissake.
463
+ BLUETOOTH ISN'T JUST FOR DESKTOPS ANYMORE, BLUEZ DEVS!)
464
+ SUSPENDS scanning while connected to a device.
465
+
466
+ The next line of code gives the scanner a kick in the arse,
467
+ starting it up again so, I dunno, another device might be able
468
+ to connect and sensor classes could maybe get beacon updates.
469
+
470
+ You know, the little things.
471
+ */
472
+ return connectQueue.enqueue( async ()=>{
473
+ this.debug(`Connecting to ${this.getName()}`)
474
+ await this.device.connect()
475
+ try {
476
+
477
+ await this._adapter.helper.callMethod('StopDiscovery')
478
+ await this._adapter.helper.callMethod('SetDiscoveryFilter', {
479
+ Transport: new Variant('s', this._adapter?._transport??"le")
480
+ })
481
+ await this._adapter.helper.callMethod('StartDiscovery')
482
+
483
+ } catch (e){
484
+ //probably ignorable error. probably.
485
+ console.log(e)
486
+ }
487
+ })
488
+ /* END HACK*/
489
+ }
490
+
340
491
  /**
341
492
  *
342
493
  * Subclasses do NOT need to override this function
@@ -376,12 +527,12 @@ class BTSensor extends EventEmitter {
376
527
  */
377
528
  initPropertiesChanged(){
378
529
 
379
- this.propertiesChanged.bind(this)
530
+ this._propertiesChanged.bind(this)
380
531
  this.device.helper._prepare()
381
532
  this.device.helper.on("PropertiesChanged",
382
533
  ((props)=> {
383
534
  try{
384
- this.propertiesChanged(props)
535
+ this._propertiesChanged(props)
385
536
  }
386
537
  catch(error){
387
538
  this.debug(`Error occured on ${this.getNameAndAddress()}: ${error?.message??error}`)
@@ -392,30 +543,23 @@ class BTSensor extends EventEmitter {
392
543
  //END instance initialization functions
393
544
 
394
545
  //Metadata functions
395
- getMetadata(){
396
- if (this.metadata==undefined)
397
- this.metadata= new Map(this.constructor.getMetadata())
398
- return this.metadata
399
- }
400
546
 
401
- getMetadatum(tag){
402
- return this.getMetadata().get(tag)
547
+ getPath(tag){
548
+ return this._schema.properties.paths.properties[tag]
403
549
  }
404
550
 
405
- getPathMetadata(){
406
- return new Map(
407
- [...this.getMetadata().entries()].filter(([key,value]) => !(value?.isParam??false))
408
- )
551
+ getPaths(){
552
+ return this._schema.properties.paths.properties
409
553
  }
410
- getParamMetadata(){
411
- return new Map(
412
- [...this.getMetadata().entries()].filter(([key,value]) => (value?.isParam??false) && !(value?.isGATT??false))
413
- )
554
+ getParams(){
555
+ return this._schema.properties.params.properties
414
556
  }
415
- getGATTParamMetadata(){
416
- return new Map(
417
- [...this.getMetadata().entries()].filter(([key,value]) => (value?.isParam??false) && (value?.isGATT??false))
418
- )
557
+ getParameter(param){
558
+ return this.getParams()[param]
559
+ }
560
+ getGATTParams(){
561
+ return this._schema.properties.gattParams.properties
562
+
419
563
  }
420
564
  //End metadata functions
421
565
 
@@ -429,9 +573,13 @@ class BTSensor extends EventEmitter {
429
573
  return `${this.getName()} from ${this.getManufacturer()}`
430
574
  }
431
575
  getName(){
432
- return this?.name??this.currentProperties.Name
433
- }
576
+ const name = this?.name??this.currentProperties.Name
577
+ return name?name:"Unknown"
434
578
 
579
+ }
580
+ macAndName(){
581
+ return `${this.getName().replaceAll(':', '-').replaceAll(" ","_")}-${this.getMacAddress().replaceAll(':', '-')}`
582
+ }
435
583
  getNameAndAddress(){
436
584
  return `${this.getName()} at ${this.getMacAddress()}`
437
585
  }
@@ -475,6 +623,8 @@ class BTSensor extends EventEmitter {
475
623
  else
476
624
  return null
477
625
  }
626
+
627
+
478
628
  //END Device property functions
479
629
 
480
630
  //Sensor RSSI state functions
@@ -490,6 +640,13 @@ class BTSensor extends EventEmitter {
490
640
  return signalQualityPercentQuad(rssi)
491
641
  }
492
642
 
643
+ getState(){
644
+ return this._state
645
+ }
646
+
647
+ isActive(){
648
+ return this._state=="ACTIVE"
649
+ }
493
650
  getBars(){
494
651
  const ss = this.getSignalStrength()
495
652
  var bars = ""
@@ -532,7 +689,8 @@ class BTSensor extends EventEmitter {
532
689
  * @param {*} props which contains ManufacturerData and ServiceData (where the sensor's data resides)
533
690
  * set up by BTSensor::initPropertiesChanged()
534
691
  */
535
- propertiesChanged(props){
692
+ _propertiesChanged(props){
693
+ this._lastContact=Date.now()
536
694
 
537
695
  if (props.RSSI) {
538
696
  this.currentProperties.RSSI=this.valueIfVariant(props.RSSI)
@@ -543,8 +701,13 @@ class BTSensor extends EventEmitter {
543
701
 
544
702
  if (props.ManufacturerData)
545
703
  this.currentProperties.ManufacturerData=this.valueIfVariant(props.ManufacturerData)
704
+ if (this.isActive())
705
+ this.propertiesChanged(props)
546
706
 
547
707
  }
708
+ propertiesChanged(props){
709
+ //implemented by subclass
710
+ }
548
711
 
549
712
  /**
550
713
  *
@@ -555,14 +718,18 @@ class BTSensor extends EventEmitter {
555
718
  }
556
719
 
557
720
  emitData(tag, buffer, ...args){
558
- this.emit(tag, this.getMetadatum(tag).read(buffer, ...args))
721
+ const md = this.getPath(tag)
722
+ if (md && md.read)
723
+ this.emit(tag, md.read(buffer, ...args))
724
+
725
+
559
726
  }
560
727
 
561
728
  emitValuesFrom(buffer){
562
- this.getMetadata().forEach((datum, tag)=>{
563
- if (!(datum.isParam||datum.notify) && datum.read)
564
- this.emit(tag, datum.read(buffer))
565
- })
729
+ Object.keys(this.getPaths())
730
+ .forEach(
731
+ (tag)=>this.emitData(tag,buffer)
732
+ )
566
733
  }
567
734
 
568
735
  /**
@@ -576,21 +743,10 @@ class BTSensor extends EventEmitter {
576
743
  listen(){
577
744
  try{
578
745
  this.initPropertiesChanged()
579
- this.propertiesChanged(this.currentProperties)
746
+ this._propertiesChanged(this.currentProperties)
580
747
  } catch(e){
581
748
  this.debug(e)
582
749
  }
583
- if (this.usingGATT()){
584
- this.initGATTConnection().then(async ()=>{
585
- this.emitGATT()
586
- if (this.pollFreq){
587
- this.initGATTInterval()
588
- }
589
- else
590
- await this.initGATTNotifications()
591
- })
592
- .catch((e)=>this.debug(`GATT services unavailable for ${this.getName()}. Reason: ${e}`))
593
- }
594
750
  return this
595
751
  }
596
752
 
@@ -601,12 +757,13 @@ class BTSensor extends EventEmitter {
601
757
  * Called automatically by Plugin::plugin.stop()
602
758
  */
603
759
 
604
- stopListening(){
760
+ async stopListening(){
605
761
  this.removeAllListeners()
606
762
  this.device.helper.removeListeners()
607
763
  if (this.intervalID){
608
764
  clearInterval(this.intervalID)
609
- }
765
+ }
766
+ this._state="ASLEEP"
610
767
  }
611
768
  //END Sensor listen-to-changes functions
612
769
 
@@ -622,6 +779,45 @@ class BTSensor extends EventEmitter {
622
779
 
623
780
  }
624
781
  //End instance utility functions
782
+
783
+ createPaths(config, id){
784
+ // const source = `${this.getName()} (${id})`
785
+
786
+ Object.keys(this.getPaths()).forEach((tag)=>{
787
+ const pathMeta=this.getPath(tag)
788
+ const path = config.paths[tag]
789
+ if (!(path===undefined))
790
+ this.app.handleMessage(id,
791
+ {
792
+ // $source: source,
793
+ updates:
794
+ [{ meta: [{path: preparePath(this, path), value: { units: pathMeta?.unit }}]}]
795
+ })
796
+ })
797
+ }
798
+
799
+ initPaths(deviceConfig, id){
800
+ const source = this.getName()
801
+ Object.keys(this.getPaths()).forEach((tag)=>{
802
+ const pathMeta=this.getPath(tag)
803
+ const path = deviceConfig.paths[tag];
804
+ if (!(path === undefined)) {
805
+ this.on(tag, (val)=>{
806
+ if (pathMeta.notify){
807
+ this.app.notify(tag, val, id )
808
+ } else {
809
+ this.updatePath(preparePath(this,path),val, id, source)
810
+ }
811
+ })
812
+ }
813
+ })
814
+ }
815
+ updatePath(path, val, id, source){
816
+ this.app.handleMessage(id, {updates: [ { $source: source, values: [ { path: path, value: val }] } ] })
817
+ }
818
+ elapsedTimeSinceLastContact(){
819
+ return (Date.now()-this?._lastContact??Date.now())/1000
820
+ }
625
821
  }
626
822
 
627
823
  module.exports = BTSensor
package/Queue.js ADDED
@@ -0,0 +1,48 @@
1
+ /*
2
+ see: https://stackoverflow.com/questions/53540348/js-async-await-tasks-queue
3
+ */
4
+ class Queue {
5
+ constructor() { this._items = []; }
6
+ enqueue(item) { this._items.push(item); }
7
+ dequeue() { return this._items.shift(); }
8
+ get size() { return this._items.length; }
9
+ }
10
+
11
+ class AutoQueue extends Queue {
12
+ constructor() {
13
+ super();
14
+ this._pendingPromise = false;
15
+ }
16
+
17
+ enqueue(action) {
18
+ return new Promise((resolve, reject) => {
19
+ super.enqueue({ action, resolve, reject });
20
+ this.dequeue();
21
+ });
22
+ }
23
+
24
+ async dequeue() {
25
+ if (this._pendingPromise) return false;
26
+
27
+ let item = super.dequeue();
28
+
29
+ if (!item) return false;
30
+
31
+ try {
32
+ this._pendingPromise = true;
33
+
34
+ let payload = await item.action(this);
35
+
36
+ this._pendingPromise = false;
37
+ item.resolve(payload);
38
+ } catch (e) {
39
+ this._pendingPromise = false;
40
+ item.reject(e);
41
+ } finally {
42
+ this.dequeue();
43
+ }
44
+
45
+ return true;
46
+ }
47
+ }
48
+ module.exports = AutoQueue