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