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