u8-mqtt 0.5.2 → 0.6.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.
Files changed (116) hide show
  1. package/README.md +23 -6
  2. package/cjs/basic-v4.cjs +293 -284
  3. package/cjs/basic-v4.cjs.map +1 -1
  4. package/cjs/basic-v5.cjs +307 -288
  5. package/cjs/basic-v5.cjs.map +1 -1
  6. package/cjs/full-v4.cjs +1538 -0
  7. package/cjs/full-v4.cjs.map +1 -0
  8. package/cjs/full-v5.cjs +1812 -0
  9. package/cjs/full-v5.cjs.map +1 -0
  10. package/cjs/index.cjs +320 -302
  11. package/cjs/index.cjs.map +1 -1
  12. package/cjs/v4.cjs +305 -296
  13. package/cjs/v4.cjs.map +1 -1
  14. package/cjs/v5.cjs +319 -300
  15. package/cjs/v5.cjs.map +1 -1
  16. package/code/_cmdid_dispatch.jsy +45 -69
  17. package/code/_conn.jsy +96 -72
  18. package/code/_dispatch.jsy +36 -28
  19. package/code/_utils.jsy +17 -0
  20. package/code/base.jsy +35 -63
  21. package/code/core.jsy +78 -56
  22. package/code/full-v4.js +20 -0
  23. package/code/full-v5.js +29 -0
  24. package/code/router_path.jsy +2 -1
  25. package/esm/basic-v4.js +293 -284
  26. package/esm/basic-v4.js.map +1 -1
  27. package/esm/basic-v5.js +307 -288
  28. package/esm/basic-v5.js.map +1 -1
  29. package/esm/deno/basic-v4.js +297 -288
  30. package/esm/deno/basic-v4.js.map +1 -1
  31. package/esm/deno/basic-v5.js +311 -292
  32. package/esm/deno/basic-v5.js.map +1 -1
  33. package/esm/deno/full-v4.js +1526 -0
  34. package/esm/deno/full-v4.js.map +1 -0
  35. package/esm/deno/full-v5.js +1798 -0
  36. package/esm/deno/full-v5.js.map +1 -0
  37. package/esm/deno/index.js +324 -305
  38. package/esm/deno/index.js.map +1 -1
  39. package/esm/deno/v4.js +309 -300
  40. package/esm/deno/v4.js.map +1 -1
  41. package/esm/deno/v5.js +323 -304
  42. package/esm/deno/v5.js.map +1 -1
  43. package/esm/full-v4.js +1526 -0
  44. package/esm/full-v4.js.map +1 -0
  45. package/esm/full-v5.js +1798 -0
  46. package/esm/full-v5.js.map +1 -0
  47. package/esm/index.js +320 -301
  48. package/esm/index.js.map +1 -1
  49. package/esm/node/basic-v4.js +293 -284
  50. package/esm/node/basic-v4.js.map +1 -1
  51. package/esm/node/basic-v4.mjs +293 -284
  52. package/esm/node/basic-v4.mjs.map +1 -1
  53. package/esm/node/basic-v5.js +307 -288
  54. package/esm/node/basic-v5.js.map +1 -1
  55. package/esm/node/basic-v5.mjs +307 -288
  56. package/esm/node/basic-v5.mjs.map +1 -1
  57. package/esm/node/full-v4.js +1529 -0
  58. package/esm/node/full-v4.js.map +1 -0
  59. package/esm/node/full-v4.mjs +1529 -0
  60. package/esm/node/full-v4.mjs.map +1 -0
  61. package/esm/node/full-v5.js +1801 -0
  62. package/esm/node/full-v5.js.map +1 -0
  63. package/esm/node/full-v5.mjs +1801 -0
  64. package/esm/node/full-v5.mjs.map +1 -0
  65. package/esm/node/index.js +320 -301
  66. package/esm/node/index.js.map +1 -1
  67. package/esm/node/index.mjs +320 -301
  68. package/esm/node/index.mjs.map +1 -1
  69. package/esm/node/v4.js +305 -296
  70. package/esm/node/v4.js.map +1 -1
  71. package/esm/node/v4.mjs +305 -296
  72. package/esm/node/v4.mjs.map +1 -1
  73. package/esm/node/v5.js +319 -300
  74. package/esm/node/v5.js.map +1 -1
  75. package/esm/node/v5.mjs +319 -300
  76. package/esm/node/v5.mjs.map +1 -1
  77. package/esm/v4.js +305 -296
  78. package/esm/v4.js.map +1 -1
  79. package/esm/v5.js +319 -300
  80. package/esm/v5.js.map +1 -1
  81. package/esm/web/basic-v4.js +293 -284
  82. package/esm/web/basic-v4.js.map +1 -1
  83. package/esm/web/basic-v4.min.js +1 -1
  84. package/esm/web/basic-v4.min.js.br +0 -0
  85. package/esm/web/basic-v4.min.js.gz +0 -0
  86. package/esm/web/basic-v5.js +307 -288
  87. package/esm/web/basic-v5.js.map +1 -1
  88. package/esm/web/basic-v5.min.js +1 -1
  89. package/esm/web/basic-v5.min.js.br +0 -0
  90. package/esm/web/basic-v5.min.js.gz +0 -0
  91. package/esm/web/full-v4.js +1526 -0
  92. package/esm/web/full-v4.js.map +1 -0
  93. package/esm/web/full-v4.min.js +1 -0
  94. package/esm/web/full-v4.min.js.br +0 -0
  95. package/esm/web/full-v4.min.js.gz +0 -0
  96. package/esm/web/full-v5.js +1798 -0
  97. package/esm/web/full-v5.js.map +1 -0
  98. package/esm/web/full-v5.min.js +1 -0
  99. package/esm/web/full-v5.min.js.br +0 -0
  100. package/esm/web/full-v5.min.js.gz +0 -0
  101. package/esm/web/index.js +320 -301
  102. package/esm/web/index.js.map +1 -1
  103. package/esm/web/index.min.js +1 -1
  104. package/esm/web/index.min.js.br +0 -0
  105. package/esm/web/index.min.js.gz +0 -0
  106. package/esm/web/v4.js +305 -296
  107. package/esm/web/v4.js.map +1 -1
  108. package/esm/web/v4.min.js +1 -1
  109. package/esm/web/v4.min.js.br +0 -0
  110. package/esm/web/v4.min.js.gz +0 -0
  111. package/esm/web/v5.js +319 -300
  112. package/esm/web/v5.js.map +1 -1
  113. package/esm/web/v5.min.js +1 -1
  114. package/esm/web/v5.min.js.br +0 -0
  115. package/esm/web/v5.min.js.gz +0 -0
  116. package/package.json +7 -8
package/cjs/v4.cjs CHANGED
@@ -547,6 +547,23 @@ function parse(str, loose) {
547
547
  };
548
548
  }
549
549
 
550
+ const _isfn = v => typeof v === 'function';
551
+ const _isstr = v => typeof v === 'string';
552
+
553
+ function _interval(fn_callback) {
554
+ let tid;
555
+ return (( td ) => {
556
+ tid = clearInterval(tid);
557
+ if (td) {
558
+ tid = setInterval(fn_callback, 1000 * td);
559
+
560
+
561
+
562
+
563
+ // ensure the interval allows the NodeJS event loop to exit
564
+ tid.unref?.();
565
+ return true} }) }
566
+
550
567
  /*
551
568
  class AbstractTopicRouter ::
552
569
  async invoke(pkt, ctx) ::
@@ -637,7 +654,7 @@ function mqtt_topic_path_router() {
637
654
  let fn = args.pop();
638
655
  let priority = args.pop();
639
656
 
640
- if ('function' !== typeof fn) {
657
+ if (! _isfn(fn)) {
641
658
  if (fn) {throw new TypeError()}
642
659
  fn = _ignore;}
643
660
 
@@ -787,6 +804,9 @@ function mqtt_pkt_ctx(mqtt_level, opts, pkt_ctx) {
787
804
  return _encode_by_type[type]( mqtt_level, pkt ) },
788
805
 
789
806
  decode_pkt(b0, u8_body) {
807
+ if (b0.map) // Uint8Array in first arg
808
+ return mqtt_raw_dispatch(this)(b0)[0]
809
+
790
810
  let fn_decode = _decode_by_id[b0>>>4] || _decode_by_id[0];
791
811
  return fn_decode?.({__proto__: this.pkt_ctx, b0}, u8_body) },
792
812
 
@@ -799,262 +819,129 @@ function mqtt_pkt_ctx(mqtt_level, opts, pkt_ctx) {
799
819
  }
800
820
  }
801
821
 
802
- function ao_defer_ctx(as_res = (...args) => args) {
803
- let y,n,_pset = (a,b) => { y=a, n=b; };
804
- return p =>(
805
- p = new Promise(_pset)
806
- , as_res(p, y, n)) }
807
-
808
- const ao_defer_v = /* #__PURE__ */
809
- ao_defer_ctx();
810
-
811
- Promise.resolve({type:'init'});
812
-
813
- function _mqtt_conn(client, [on_mqtt, pkt_future]) {
814
- let _q_init = ao_defer_v(), _q_ready = ao_defer_v();
815
- let _send_ready = async (...args) => (await _q_ready[0])(...args);
816
- let _send_mqtt_pkt, _has_connected;
817
- client._send = _send_ready;
818
-
819
- return {
820
- async when_ready() {await _q_ready[0];}
821
-
822
- , ping: _ping_interval (() =>_send_mqtt_pkt?.('pingreq'))
823
-
824
- , reset(err) {
825
- if (! _send_mqtt_pkt) {return}
826
-
827
- if (err) {
828
- _q_init[2](err);}
829
-
830
- _send_mqtt_pkt = null;
831
- _q_init = ao_defer_v();
832
- client._send = _send_ready;
833
-
834
- // call client.on_conn_reset in next promise microtask
835
- client.conn_emit('on_disconnect', false===err, err);}
836
-
837
- , async send_connect(... args) {
838
- if (! _send_mqtt_pkt) {
839
- await _q_init[0]; }// _send_mqtt_pkt is set before fulfilled
840
-
841
- // await connack response
842
- let res = await _send_mqtt_pkt(...args);
843
- if (0 == res[0].reason) {
844
- _has_connected = true;
845
- // resolve _q_ready[0] with _send_mqtt_pkt closure
846
- _q_ready[1](client._send = _send_mqtt_pkt);
847
- _q_ready = ao_defer_v();
848
- client.conn_emit('on_ready');}
849
-
850
- return res}
851
-
852
- , is_set: (() =>!! _send_mqtt_pkt)
853
- , set(mqtt_ctx, send_u8_pkt) {
854
- if (_send_mqtt_pkt) {
855
- throw new Error('Already connected')}
856
-
857
- mqtt_ctx = mqtt_ctx.mqtt_stream();
858
- let sess_ctx = {mqtt: client};
859
- let on_mqtt_chunk = u8_buf =>
860
- on_mqtt(mqtt_ctx.decode(u8_buf), sess_ctx);
861
-
862
- _send_mqtt_pkt = async (type, pkt, key) => {
863
- let res = undefined !== key
864
- ? pkt_future(key) : true;
865
-
866
- await send_u8_pkt(
867
- mqtt_ctx.encode_pkt(type, pkt));
868
-
869
- return res};
822
+ async function _mqtt_cmd_evt(target, answer, pkt, ctx) {
823
+ /* target : on_mqtt_type = {
824
+ mqtt_pkt(pkt, ctx) {}, // generic
825
+
826
+ mqtt_auth(pkt, ctx) {},
827
+ mqtt_connect(pkt, ctx) {},
828
+ mqtt_connack(pkt, ctx) {},
829
+ mqtt_disconnect(pkt, ctx) {},
830
+
831
+ mqtt_publish(pkt, ctx) {},
832
+ mqtt_subscribe(pkt, ctx) {},
833
+ mqtt_unsubscribe(pkt, ctx) {},
834
+
835
+ mqtt_pingreq(pkt, ctx) {},
836
+ mqtt_pingresp(pkt, ctx) {},
837
+ } */
838
+
839
+ let pkt_fn = target[`mqtt_${pkt.type}`] || target.mqtt_pkt;
840
+ await pkt_fn?.call(target, pkt, ctx);}
841
+
842
+ function _mqtt_cmd_type(target, answer, pkt, ctx) {
843
+ answer(pkt.type, pkt);
844
+ _mqtt_cmd_evt(target, answer, pkt, ctx);}
845
+
846
+ function _mqtt_cmd_id(target, answer, pkt) {
847
+ answer(pkt.pkt_id, pkt);}
848
+
849
+
850
+ const _mqtt_cmdids =[
851
+ _ => {} // 0x0 reserved
852
+ , _mqtt_cmd_evt // 0x1 connect
853
+ , _mqtt_cmd_type // 0x2 connack
854
+ , _mqtt_cmd_evt // 0x3 publish
855
+ , _mqtt_cmd_id // 0x4 puback
856
+ , _mqtt_cmd_id // 0x5 pubrec
857
+ , _mqtt_cmd_id // 0x6 pubrel
858
+ , _mqtt_cmd_id // 0x7 pubcomp
859
+ , _mqtt_cmd_evt // 0x8 subscribe
860
+ , _mqtt_cmd_id // 0x9 suback
861
+ , _mqtt_cmd_evt // 0xa unsubscribe
862
+ , _mqtt_cmd_id // 0xb unsuback
863
+ , _mqtt_cmd_type // 0xc pingreq
864
+ , _mqtt_cmd_type // 0xd pingresp
865
+ , _mqtt_cmd_evt // 0xe disconnect
866
+ , _mqtt_cmd_type ];// 0xf auth
870
867
 
871
- _q_init[1](_send_mqtt_pkt); // resolve _q_init with _send_mqtt_pkt closure
872
-
873
- // call client.on_live in next promise microtask
874
- client.conn_emit('on_live', _has_connected);
875
- return on_mqtt_chunk} } }
876
-
877
-
878
- function _ping_interval(send_ping) {
879
- let tid;
880
- return (( td ) => {
881
- tid = clearInterval(tid);
882
- if (td) {
883
- tid = setInterval(send_ping, 1000 * td);
884
-
885
-
886
-
887
-
888
- // ensure the interval allows the NodeJS event loop to exit
889
- tid.unref?.();
890
- return true} }) }
868
+ function _mqtt_dispatch(opt, target) {
869
+ let hashbelt=[], rotate_ts=0;
870
+ // default rotate at 1s across 5 buckets
871
+ let { td: rotate_td=1000, n: rotate_n=5 } = opt?.rotate || {};
891
872
 
892
- const _mqtt_cmdid_dispatch ={
893
- create(target) {
894
- return {__proto__: this, target, hashbelt: [new Map()]} }
873
+ // Promise / future scaffolding
874
+ let _pkt_id=100, _ftr_key; // use _ftr_key to reuse _by_key closure
875
+ let _ftr_by_key = fn_answer => hashbelt[0].set(_ftr_key, fn_answer);
895
876
 
896
- , bind_pkt_future(_pkt_id=100) {
897
- let {hashbelt} = this;
877
+ on_mqtt([]); // init hashbelt and rotate_ts
878
+ return [on_mqtt, pkt_future]
898
879
 
899
- let _tmp_; // use _tmp_ to reuse _by_key closure
900
- let _by_key = answer_monad =>
901
- hashbelt[0].set(_tmp_, answer_monad);
902
880
 
903
- return (( pkt_or_key ) => {
904
- if ('string' === typeof pkt_or_key) {
905
- _tmp_ = pkt_or_key;}
906
- else {
907
- _pkt_id = (_pkt_id + 1) & 0xffff;
908
- _tmp_ = pkt_or_key.pkt_id = _pkt_id;}
881
+ function pkt_future(pkt_or_key) {
882
+ if (! _isstr(pkt_or_key)) {
883
+ _pkt_id = (_pkt_id + 1) & 0xffff; // 16-bit unsigned short
884
+ _ftr_key = pkt_or_key.pkt_id = _pkt_id;}
885
+ else _ftr_key = pkt_or_key;
909
886
 
910
- return new Promise(_by_key)}) }
887
+ return new Promise(_ftr_by_key)}
911
888
 
912
- , answer(key, pkt) {
913
- for (let map of this.hashbelt) {
914
- let answer_monad = map.get(key);
915
- if (undefined !== answer_monad) {
889
+ function answer(key, pkt) {
890
+ for (let map of hashbelt) {
891
+ let fn_answer = map.get(key);
892
+ if (fn_answer) {
916
893
  map.delete(key);
917
894
 
918
- answer_monad([pkt, /*err*/]); // option/maybe monad
895
+ fn_answer([pkt, /*err*/]); // option/maybe monad
919
896
  return true} }
920
897
  return false}
921
898
 
922
- , rotate_belt(n) {
923
- let {hashbelt} = this;
924
- hashbelt.unshift(new Map());
925
- for (let old of hashbelt.splice(n || 5)) {
926
- for (let answer_monad of old.values()) {
927
- answer_monad([/*pkt*/, 'expired']); } } }// option/maybe monad
928
-
929
- , cmdids: ((() => {
930
- return [
931
- (() =>{} )// 0x0 reserved
932
- , by_evt // 0x1 connect
933
- , by_type // 0x2 connack
934
- , by_evt // 0x3 publish
935
- , by_id // 0x4 puback
936
- , by_id // 0x5 pubrec
937
- , by_id // 0x6 pubrel
938
- , by_id // 0x7 pubcomp
939
- , by_evt // 0x8 subscribe
940
- , by_id // 0x9 suback
941
- , by_evt // 0xa unsubscribe
942
- , by_id // 0xb unsuback
943
- , by_type // 0xc pingreq
944
- , by_type // 0xd pingresp
945
- , by_evt // 0xe disconnect
946
- , by_type ]// 0xf auth
947
-
948
-
949
- function by_id(disp, pkt) {
950
- disp.answer(pkt.pkt_id, pkt); }
951
-
952
- function by_type(disp, pkt, ctx) {
953
- disp.answer(pkt.type, pkt);
954
- by_evt(disp, pkt, ctx);}
955
-
956
- async function by_evt({target}, pkt, ctx) {
957
- let fn = target[`mqtt_${pkt.type}`]
958
- || target.mqtt_pkt;
959
-
960
- await fn?.call(target, pkt, ctx);} })()) };
961
-
962
- /*
963
- on_mqtt_type = {
964
- mqtt_auth(pkt, ctx) ::
965
- mqtt_connect(pkt, ctx) ::
966
- mqtt_connack(pkt, ctx) ::
967
- mqtt_disconnect(pkt, ctx) ::
968
-
969
- mqtt_publish(pkt, ctx)
970
- mqtt_subscribe(pkt, ctx) ::
971
- mqtt_unsubscribe(pkt, ctx) ::
972
-
973
- mqtt_pingreq(pkt, ctx) ::
974
- mqtt_pingresp(pkt, ctx) ::
975
- }
976
- */
977
-
978
- function _mqtt_dispatch(opt, target) {
979
- let _disp_ = _mqtt_cmdid_dispatch.create(target);
980
- let { cmdids } = _disp_;
981
-
982
- // default rotate at 1s across 5 buckets
983
- let { td: rotate_td=1000, n: rotate_n=5 } =
984
- opt && opt.rotate || {};
985
-
986
- let rotate_ts = rotate_td + Date.now();
987
-
988
- return [on_mqtt,
989
- _disp_.bind_pkt_future()]
990
-
991
899
  function on_mqtt(pkt_list, ctx) {
992
900
  for (let pkt of pkt_list) {
993
- cmdids[pkt.id](_disp_, pkt, ctx); }
901
+ _mqtt_cmdids[pkt.id](target, answer, pkt, ctx);}
994
902
 
995
- if (Date.now() > rotate_ts) {
996
- _disp_.rotate_belt(rotate_n);
997
- rotate_ts = rotate_td + Date.now();} } }
903
+ // rotate after rotate_ts
904
+ let now = Date.now();
905
+ if (now > rotate_ts) {
906
+ rotate_ts = rotate_td + now;
907
+ hashbelt.unshift(new Map());
908
+ while (hashbelt.length > rotate_n) {
909
+ for (let fn_answer of hashbelt.pop().values()) {
910
+ fn_answer([/*pkt*/, 'expired']); } } } } }// option/maybe monad
998
911
 
999
912
  class MQTTError extends Error {
1000
913
  constructor(mqtt_pkt, reason=mqtt_pkt.reason) {
914
+ // use hex-encoded reasons to match MQTT spec documentation
1001
915
  super(`[0x${reason.toString(16)}] ${reason.reason}`);
1002
916
  this.mqtt_pkt = mqtt_pkt;
1003
917
  this.reason = reason;} }
1004
918
 
1005
919
  class MQTTBase {
1006
- constructor(opt={}) {
1007
- this.with(opt);
1008
- this._conn_ = _mqtt_conn(this,
1009
- this._init_dispatch(opt, this)); }
1010
-
1011
- with(fns_ns) {
1012
- for (let [k,v] of Object.entries(fns_ns)) {
1013
- if ('function' === typeof v) {this[k] = v;} }
1014
- return this}
1015
-
1016
- async conn_emit(evt, arg, err_arg) {
1017
- this.log_conn?.(evt, arg, err_arg);
1018
- try {
1019
- let fn_evt = this[await evt]; // microtask break using `await evt`
1020
- if (fn_evt) {
1021
- await fn_evt.call(this, this, arg, err_arg);}
1022
- else if (err_arg) {throw err_arg} }
1023
- catch (err) {
1024
- this.on_error(err, evt);} }
1025
-
1026
- on_error(err, evt) {
1027
- console.warn('[[u8-mqtt error: %s]]', evt, err); }
1028
-
1029
920
  // Handshaking Packets
1030
-
1031
921
  async connect(pkt={}) {
1032
- let cid = pkt.client_id || this.client_id;
1033
- if ('string' !== typeof cid) {
922
+ let cid = pkt.client_id;
923
+ if (! _isstr(cid)) {
1034
924
  // see init_client_id implementation in core.jsy
1035
- pkt.client_id = cid = this.init_client_id(cid);}
925
+ pkt.client_id = cid = this.client_id || this.init_client_id(cid);}
1036
926
  this.client_id = cid;
1037
927
 
1038
928
  if (null == pkt.keep_alive) {
1039
929
  pkt.keep_alive = 60;}
1040
930
 
1041
- let res = await this._conn_
1042
- .send_connect('connect', pkt, 'connack');
1043
-
1044
- if (0 != res[0].reason) {
1045
- throw new this.MQTTError(res[0])}
1046
-
1047
- // TODO: merge with server's keep_alive frequency
1048
- this._conn_.ping(pkt.keep_alive);
1049
- return res}
931
+ let response = await this._send0('connect', pkt, 'connack');
932
+ if (0 != response[0].reason) {// compare to 0 to coerce to number
933
+ throw new this.MQTTError(response[0])}
934
+ return this.conn.on_conn(pkt, response)}
1050
935
 
1051
936
  async disconnect(pkt={}) {
1052
- let res = await this._send('disconnect', pkt);
1053
- this._conn_.reset(false);
1054
- return res}
937
+ let response = await this._send0('disconnect', pkt);
938
+ return this.conn.on_dis(pkt, response)}
1055
939
 
1056
- auth(pkt={}) {
1057
- return this._send('auth', pkt, 'auth')}
940
+ async auth(pkt={}) {
941
+ let response = await this._send0('auth', pkt, 'auth');
942
+ if (response[0].reason) {
943
+ throw new this.MQTTError(response[0])}
944
+ return this.conn.on_auth(pkt, response)}
1058
945
 
1059
946
  ping() {return this._send('pingreq', null, 'pingresp')}
1060
947
  puback({pkt_id}) {return this._send('puback', {pkt_id})}
@@ -1091,20 +978,18 @@ class MQTTBase {
1091
978
  // alias: publish -- because 'pub' is shorter for semantic aliases above
1092
979
  async pub(pkt, pub_opt) {
1093
980
  if (undefined === pkt.payload) {
1094
- if ('function' === typeof pub_opt) {
981
+ if (_isfn(pub_opt)) {
982
+ // pub_opt as a function is fn_encode value
1095
983
  pub_opt = {fn_encode: pub_opt};}
1096
984
 
1097
- let {msg} = pkt;
1098
- switch (typeof msg) {
1099
- case 'function':
1100
- pub_opt = {...pub_opt, fn_encode: msg};
1101
- // flow into 'undefined' case
1102
- case 'undefined':
1103
- // return a single-value closure to publish packets
1104
- return v => this.pub({...pkt, [pkt.arg || 'payload']: v}, pub_opt)}
985
+ let msg = pkt.msg, fn_encode = pub_opt?.fn_encode;
986
+ if (null == msg || _isfn(msg)) {
987
+ // when msg is a function, return closure using fn_encode
988
+ if (msg) {pub_opt = {...pub_opt, fn_encode: msg};}
989
+ // return a single-value closure to publish packets
990
+ return v => this.pub({...pkt, [pkt.arg || 'payload']: v}, pub_opt)}
1105
991
 
1106
992
  // Encode payload from msg; fn_encode allows alternative to JSON.stringify
1107
- let {fn_encode} = pub_opt || {};
1108
993
  pkt.payload = fn_encode
1109
994
  ? await fn_encode(msg)
1110
995
  : JSON.stringify(msg);}
@@ -1116,31 +1001,31 @@ class MQTTBase {
1116
1001
  pkt = pub_opt.xform(pkt) || pkt;} }
1117
1002
 
1118
1003
  return this._send('publish', pkt,
1119
- pkt.qos ? pkt : void 0 ) }// key
1004
+ pkt.qos ? pkt : null ) }// key
1120
1005
 
1121
1006
 
1122
1007
  // Internal API
1123
1008
 
1124
- /* async _send(type, pkt) -- provided by _conn_ and transport */
1009
+ /* async _send0(type, pkt) -- provided by conn and transport */
1010
+ /* async _send(type, pkt) -- provided by conn and transport */
1125
1011
 
1126
1012
  _init_dispatch(opt) {
1127
1013
  this.constructor?._once_();
1128
1014
  let target ={__proto__: opt.on_mqtt_type};
1129
1015
  target.mqtt_publish ||=
1130
1016
  this._init_router?.(opt, this, target);
1131
- return _mqtt_dispatch(this, target)}
1017
+ return _mqtt_dispatch(opt, target)}
1132
1018
 
1133
1019
  static _aliases() {
1134
1020
  return ' publish:pub sub:subscribe unsub:unsubscribe json_post:obj_post json_send:obj_send json_store:obj_store'}
1135
1021
 
1136
- static _once_(self=this) {
1137
- self._once_ = _=>0;
1138
- let p = self.prototype;
1022
+ static _once_(klass=this) {
1023
+ klass._once_ = _=>0;
1024
+ var alias, name, p = klass.prototype;
1139
1025
  p.MQTTError = MQTTError;
1140
- for (let alias of self._aliases().split(/\s+/)) {
1141
- alias = alias.split(':');
1142
- let fn = alias[1] && p[alias[1]];
1143
- if (fn) {p[alias[0]] = fn;} } } }
1026
+ for (alias of klass._aliases().split(/\s+/)) {
1027
+ [alias, name] = alias.split(':');
1028
+ p[alias] = p[name];} } }
1144
1029
 
1145
1030
 
1146
1031
  function _as_topics(pkt, ex, topic_prefix) {
@@ -1165,43 +1050,177 @@ function _as_topics(pkt, ex, topic_prefix) {
1165
1050
  pkt.topics = pkt.topics.map(_prefix_topics);}
1166
1051
  return pkt}
1167
1052
 
1168
- const pkt_api = {
1169
- utf8(u8) { return new TextDecoder('utf-8').decode(u8 || this.payload ) },
1170
- json(u8) { return JSON.parse( this.utf8(u8) || null ) },
1171
- text(u8) { return this.utf8(u8) },
1172
- };
1053
+ const _defer_obj = o =>(
1054
+ o.p = new Promise((a,e) => { o.a=a; o.e=e; })
1055
+ , o);
1056
+
1057
+ function _dfn_reset(client, attr, fn_after) {
1058
+ // a resetable deferred for a function
1059
+ let self = {set}, afn = async (...args) => (await self.p)(...args);
1060
+ return set()
1061
+
1062
+ function set() {
1063
+ if (afn !== client[attr]) {
1064
+ _defer_obj(self).p.then(fn_after, _=>0);
1065
+ client[attr] = afn;}
1066
+ return self} }
1067
+
1068
+ function _mqtt_conn(opt, client, [on_mqtt, pkt_future]) {
1069
+ let _abort;
1070
+ let _dfn_send0 = _dfn_reset(client, '_send0', // client._send0 getter/setter
1071
+ _=> client.conn_emit('on_live', conn.has_connected));
1072
+ let _dfn_ready = _dfn_reset(client, '_send', // client._send getter/setter
1073
+ _=> client.conn_emit('on_ready'));
1074
+ let _keep_alive_ival = _interval (() =>client._send0('pingreq') );// resettable interval for keep_alive ping
1075
+
1076
+ let conn = Object.create({
1077
+ ping: (td=conn.keep_alive) => _keep_alive_ival(td)
1078
+
1079
+ , on_conn(pkt, response) {
1080
+ conn.has_connected = true;
1081
+ conn.keep_alive = opt.keep_alive || response[0].props?.server_keep_alive || pkt.keep_alive;
1082
+ client.conn_emit('on_conn');
1083
+ return opt.use_auth
1084
+ ? response // wait on enhanced authentication step
1085
+ : conn.on_auth(null, response) }// otherwise, connect is also auth
1086
+
1087
+ , on_auth(pkt, response) {
1088
+ _dfn_ready.a(_dfn_send0.p);
1089
+ if (0 != opt.keep_alive) {
1090
+ conn.ping();}
1091
+ client.conn_emit('on_auth', !pkt);
1092
+ return response}
1093
+
1094
+ , on_dis(pkt, response) {
1095
+ conn.reset(false);
1096
+ return response}
1097
+
1098
+ , reset(err) {
1099
+ if (err) {
1100
+ _dfn_send0.e(err); }// send error to uses of _send0 (connect, auth)
1101
+ _abort.e(err); // abort in-progress connections
1102
+
1103
+ delete conn.is_set;
1104
+ conn.ready = handshake();
1105
+ client.conn_emit('on_disconnect', false===err, err);}
1106
+
1107
+ , abort() {
1108
+ _dfn_ready.e(err); // abort all messages awaiting ready state
1109
+ return conn.reset(err)}
1110
+
1111
+ , async setup(gate, send_u8_pkt, init_msg_loop) {
1112
+ if (conn.is_set) {
1113
+ throw new Error() }// already in-progress
1114
+
1115
+ conn.is_set = true;
1116
+ await gate;
1117
+
1118
+ // setup send/recv MQTT parsing context
1119
+ let mqtt_ctx = client.mqtt_ctx.mqtt_stream();
1120
+
1121
+ {// setup inbound message loop
1122
+ let sess_ctx = {mqtt: client}; // mutable session context
1123
+ let on_mqtt_chunk = u8 => on_mqtt(mqtt_ctx.decode(u8), sess_ctx);
1124
+ init_msg_loop(on_mqtt_chunk, conn);}
1125
+
1126
+ // setup outbound message path and transport connection
1127
+ send_u8_pkt = await send_u8_pkt;
1128
+ _dfn_send0.a(
1129
+ async (type, pkt, key) => {
1130
+ let res = undefined !== key
1131
+ ? pkt_future(key) : true;
1132
+
1133
+ await send_u8_pkt(
1134
+ mqtt_ctx.encode_pkt(type, pkt));
1135
+ return res} ); } });
1136
+
1137
+ conn.ready = handshake();
1138
+ return conn
1139
+
1140
+ async function handshake() {
1141
+ _abort = _defer_obj({});
1142
+
1143
+ _keep_alive_ival(0); // clearInterval on keep alive ping
1144
+ _dfn_send0.set(); // reset client._send0 if necessary
1145
+ _dfn_ready.set(); // reset client._send if necessary
1146
+
1147
+ try {
1148
+ // set client._send0 as passtrhough after transport connection
1149
+ client._send0 = await Promise.race([_dfn_send0.p, _abort.p]);
1150
+
1151
+ // set client._send as passtrhough after ready
1152
+ client._send = await Promise.race([_dfn_ready.p, _abort.p]);
1153
+ return true}
1154
+ catch (err) {
1155
+ return false} } }
1156
+
1157
+ const pkt_api ={
1158
+ utf8(u8) {return new TextDecoder('utf-8').decode(u8 || this.payload )}
1159
+ , json(u8) {return JSON.parse( this.utf8(u8) || null )}
1160
+ , text(u8) {return this.utf8(u8)} };
1161
+
1162
+ const opt_default ={
1163
+ sess_stg: globalThis.sessionStorage};
1173
1164
 
1174
1165
  class MQTTCore extends MQTTBase {
1166
+ constructor(opt) {
1167
+ super();
1168
+ this.with(opt);
1169
+ opt ={...opt_default, ...opt};
1170
+ // settings for MQTTCore
1171
+ this.sess_stg = opt.sess_stg;
1172
+ // setup connection and dispatch
1173
+ this.conn = _mqtt_conn(opt, this,
1174
+ this._init_dispatch(opt)); }
1175
+
1176
+ with(fns_ns) {
1177
+ for (let [k,v] of Object.entries(fns_ns)) {
1178
+ if (_isfn(v)) {this[k] = v;} }
1179
+ return this}
1180
+
1181
+
1175
1182
  static mqtt_ctx(mqtt_level, mqtt_opts, pkt_ctx=pkt_api) {
1176
- let self = class extends this {};
1177
- self.prototype.mqtt_ctx =
1183
+ let klass = class extends this {};
1184
+ klass.prototype.mqtt_ctx =
1178
1185
  mqtt_pkt_ctx(mqtt_level, mqtt_opts, pkt_ctx);
1179
- return self}
1186
+ return klass}
1187
+
1188
+
1189
+ async conn_emit(evt, arg, err_arg) {
1190
+ this.log_conn?.(evt, arg, err_arg);
1191
+ try {
1192
+ let fn_evt = this[await evt]; // microtask break using `await evt`
1193
+ if (fn_evt) {
1194
+ await fn_evt.call(this, this, arg, err_arg);}
1195
+ else if (err_arg) {throw err_arg} }
1196
+ catch (err) {
1197
+ this.on_error(err, evt);} }
1198
+
1199
+ on_error(err, evt) {
1200
+ console.warn('[[u8-mqtt error: %s]]', evt, err); }
1201
+ log_conn(evt, arg, err_arg) {
1202
+ console.info('[[u8-mqtt conn: %s]]', evt, arg, err_arg); }
1180
1203
 
1181
1204
 
1182
1205
  // automatic Client Id for connect()
1183
1206
  init_client_id(parts=['u8-mqtt--','']) {
1184
- let sess_stg=this.sess_stg;
1207
+ let sess_stg = this.sess_stg;
1185
1208
  let key, cid = sess_stg?.getItem(key=parts.join(' '));
1186
1209
  if (! cid) {
1187
1210
  cid = parts.join(Math.random().toString(36).slice(2));
1188
1211
  sess_stg?.setItem(key, cid);}
1189
1212
  return cid}
1190
1213
 
1191
- get sess_stg() {return globalThis.sessionStorage}
1192
-
1193
-
1194
- //on_error(err, evt) ::
1195
- // console.warn @ '[[u8-mqtt error: %s]]', evt, err
1196
-
1197
- //log_conn(evt, arg, err_arg) ::
1198
- // console.info @ '[[u8-mqtt log: %s]]', evt, arg, err_arg
1199
1214
 
1200
1215
  on_live(client, is_reconnect) {
1201
1216
  if (is_reconnect) {
1202
1217
  return client.connect()} }
1203
1218
 
1204
- //on_reconnect(client) ::
1219
+ // on_ready(client) ::
1220
+ // on_reconnect(client) ::
1221
+ on_disconnect(client, intentional) {
1222
+ if (! intentional) {
1223
+ return client.on_reconnect?.()} }
1205
1224
 
1206
1225
  _use_conn(fn_reconnect) {
1207
1226
  return (this.reconnect = fn_reconnect)?.()}
@@ -1213,27 +1232,20 @@ class MQTTCore extends MQTTBase {
1213
1232
  .then(this.reconnect)
1214
1233
  .then(opt.reconnect, opt.error);} }) }
1215
1234
 
1216
- on_disconnect(client, intentional) {
1217
- if (! intentional) {
1218
- return client.on_reconnect?.()} }
1219
-
1220
1235
  delay(ms) {
1221
1236
  return new Promise(done => setTimeout(done, ms)) }
1222
1237
 
1223
1238
  with_async_iter(async_iter, write_u8_pkt) {
1224
- let on_mqtt_chunk = this._conn_.set(
1225
- this.mqtt_ctx,
1226
- write_u8_pkt);
1227
-
1228
- this._msg_loop = ((async () => {
1229
- try {
1230
- async_iter = await async_iter;
1231
- for await (let chunk of async_iter)
1232
- on_mqtt_chunk(chunk);
1233
- this._conn_.reset();}
1234
- catch (err) {
1235
- this._conn_.reset(err);} })());
1236
-
1239
+ this.conn.setup(async_iter,
1240
+ write_u8_pkt,
1241
+ async (on_mqtt_chunk, conn) => {
1242
+ try {
1243
+ async_iter = await async_iter;
1244
+ for await (let chunk of async_iter)
1245
+ on_mqtt_chunk(chunk);
1246
+ conn.reset();}
1247
+ catch (err) {
1248
+ conn.reset(err);} } );
1237
1249
  return this}
1238
1250
 
1239
1251
 
@@ -1304,33 +1316,30 @@ class MQTTCore extends MQTTBase {
1304
1316
 
1305
1317
  websock.binaryType = 'arraybuffer';
1306
1318
 
1307
- let ready, {readyState} = websock;
1319
+ let ws_ready, readyState = websock.readyState;
1308
1320
  if (1 !== readyState) {
1309
1321
  if (0 !== readyState) {
1310
- throw new Error('Invalid WebSocket readyState') }
1311
-
1312
- ready = new Promise(fn => websock.onopen = fn); }
1322
+ throw new Error('WS readyState') }
1313
1323
 
1324
+ ws_ready = new Promise(ready => websock.onopen = ready); }
1314
1325
 
1315
- let {_conn_} = this;
1316
- let on_mqtt_chunk = _conn_.set(
1317
- this.mqtt_ctx,
1318
- async u8_pkt =>(
1319
- await ready
1320
- , websock.send(u8_pkt)) );
1326
+ this.conn.setup(ws_ready,
1327
+ u8_pkt => websock.send(u8_pkt),
1328
+ (on_mqtt_chunk, conn) => {
1329
+ websock.onmessage = evt =>(
1330
+ on_mqtt_chunk(new Uint8Array(evt.data)) );
1321
1331
 
1322
- websock.onmessage = evt =>(on_mqtt_chunk(new Uint8Array(evt.data)));
1323
- websock.onclose = evt => {
1324
- if (! evt.wasClean) {
1325
- var err = new Error('websocket connection close');
1326
- err.code = evt.code;
1327
- err.reason = evt.reason;}
1332
+ websock.onclose = evt => {
1333
+ if (! evt.wasClean) {
1334
+ var err = new Error('websocket close');
1335
+ err.code = evt.code;
1336
+ err.reason = evt.reason;}
1328
1337
 
1329
- _conn_.reset(err);};
1338
+ conn.reset(err);}; } );
1330
1339
 
1331
1340
  return this} }
1332
1341
 
1333
- const version = '0.5.2-node';
1342
+ const version = '0.6.0-node';
1334
1343
 
1335
1344
  const MQTTClient_v4 = /* #__PURE__ */
1336
1345
  with_topic_path_router(