u8-mqtt 0.5.3 → 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 (113) hide show
  1. package/cjs/basic-v4.cjs +283 -277
  2. package/cjs/basic-v4.cjs.map +1 -1
  3. package/cjs/basic-v5.cjs +297 -281
  4. package/cjs/basic-v5.cjs.map +1 -1
  5. package/cjs/full-v4.cjs +295 -289
  6. package/cjs/full-v4.cjs.map +1 -1
  7. package/cjs/full-v5.cjs +309 -293
  8. package/cjs/full-v5.cjs.map +1 -1
  9. package/cjs/index.cjs +310 -295
  10. package/cjs/index.cjs.map +1 -1
  11. package/cjs/v4.cjs +295 -289
  12. package/cjs/v4.cjs.map +1 -1
  13. package/cjs/v5.cjs +309 -293
  14. package/cjs/v5.cjs.map +1 -1
  15. package/code/_cmdid_dispatch.jsy +45 -69
  16. package/code/_conn.jsy +96 -72
  17. package/code/_dispatch.jsy +36 -28
  18. package/code/_utils.jsy +17 -0
  19. package/code/base.jsy +33 -61
  20. package/code/core.jsy +73 -51
  21. package/code/router_path.jsy +2 -1
  22. package/esm/basic-v4.js +283 -277
  23. package/esm/basic-v4.js.map +1 -1
  24. package/esm/basic-v5.js +297 -281
  25. package/esm/basic-v5.js.map +1 -1
  26. package/esm/deno/basic-v4.js +287 -281
  27. package/esm/deno/basic-v4.js.map +1 -1
  28. package/esm/deno/basic-v5.js +301 -285
  29. package/esm/deno/basic-v5.js.map +1 -1
  30. package/esm/deno/full-v4.js +299 -293
  31. package/esm/deno/full-v4.js.map +1 -1
  32. package/esm/deno/full-v5.js +313 -297
  33. package/esm/deno/full-v5.js.map +1 -1
  34. package/esm/deno/index.js +314 -298
  35. package/esm/deno/index.js.map +1 -1
  36. package/esm/deno/v4.js +299 -293
  37. package/esm/deno/v4.js.map +1 -1
  38. package/esm/deno/v5.js +313 -297
  39. package/esm/deno/v5.js.map +1 -1
  40. package/esm/full-v4.js +295 -289
  41. package/esm/full-v4.js.map +1 -1
  42. package/esm/full-v5.js +309 -293
  43. package/esm/full-v5.js.map +1 -1
  44. package/esm/index.js +310 -294
  45. package/esm/index.js.map +1 -1
  46. package/esm/node/basic-v4.js +283 -277
  47. package/esm/node/basic-v4.js.map +1 -1
  48. package/esm/node/basic-v4.mjs +283 -277
  49. package/esm/node/basic-v4.mjs.map +1 -1
  50. package/esm/node/basic-v5.js +297 -281
  51. package/esm/node/basic-v5.js.map +1 -1
  52. package/esm/node/basic-v5.mjs +297 -281
  53. package/esm/node/basic-v5.mjs.map +1 -1
  54. package/esm/node/full-v4.js +295 -289
  55. package/esm/node/full-v4.js.map +1 -1
  56. package/esm/node/full-v4.mjs +295 -289
  57. package/esm/node/full-v4.mjs.map +1 -1
  58. package/esm/node/full-v5.js +309 -293
  59. package/esm/node/full-v5.js.map +1 -1
  60. package/esm/node/full-v5.mjs +309 -293
  61. package/esm/node/full-v5.mjs.map +1 -1
  62. package/esm/node/index.js +310 -294
  63. package/esm/node/index.js.map +1 -1
  64. package/esm/node/index.mjs +310 -294
  65. package/esm/node/index.mjs.map +1 -1
  66. package/esm/node/v4.js +295 -289
  67. package/esm/node/v4.js.map +1 -1
  68. package/esm/node/v4.mjs +295 -289
  69. package/esm/node/v4.mjs.map +1 -1
  70. package/esm/node/v5.js +309 -293
  71. package/esm/node/v5.js.map +1 -1
  72. package/esm/node/v5.mjs +309 -293
  73. package/esm/node/v5.mjs.map +1 -1
  74. package/esm/v4.js +295 -289
  75. package/esm/v4.js.map +1 -1
  76. package/esm/v5.js +309 -293
  77. package/esm/v5.js.map +1 -1
  78. package/esm/web/basic-v4.js +283 -277
  79. package/esm/web/basic-v4.js.map +1 -1
  80. package/esm/web/basic-v4.min.js +1 -1
  81. package/esm/web/basic-v4.min.js.br +0 -0
  82. package/esm/web/basic-v4.min.js.gz +0 -0
  83. package/esm/web/basic-v5.js +297 -281
  84. package/esm/web/basic-v5.js.map +1 -1
  85. package/esm/web/basic-v5.min.js +1 -1
  86. package/esm/web/basic-v5.min.js.br +0 -0
  87. package/esm/web/basic-v5.min.js.gz +0 -0
  88. package/esm/web/full-v4.js +295 -289
  89. package/esm/web/full-v4.js.map +1 -1
  90. package/esm/web/full-v4.min.js +1 -1
  91. package/esm/web/full-v4.min.js.br +0 -0
  92. package/esm/web/full-v4.min.js.gz +0 -0
  93. package/esm/web/full-v5.js +309 -293
  94. package/esm/web/full-v5.js.map +1 -1
  95. package/esm/web/full-v5.min.js +1 -1
  96. package/esm/web/full-v5.min.js.br +0 -0
  97. package/esm/web/full-v5.min.js.gz +0 -0
  98. package/esm/web/index.js +310 -294
  99. package/esm/web/index.js.map +1 -1
  100. package/esm/web/index.min.js +1 -1
  101. package/esm/web/index.min.js.br +0 -0
  102. package/esm/web/index.min.js.gz +0 -0
  103. package/esm/web/v4.js +295 -289
  104. package/esm/web/v4.js.map +1 -1
  105. package/esm/web/v4.min.js +1 -1
  106. package/esm/web/v4.min.js.br +0 -0
  107. package/esm/web/v4.min.js.gz +0 -0
  108. package/esm/web/v5.js +309 -293
  109. package/esm/web/v5.js.map +1 -1
  110. package/esm/web/v5.min.js +1 -1
  111. package/esm/web/v5.min.js.br +0 -0
  112. package/esm/web/v5.min.js.gz +0 -0
  113. package/package.json +7 -8
package/esm/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
 
@@ -795,238 +812,108 @@ function mqtt_pkt_ctx(mqtt_level, opts, pkt_ctx) {
795
812
  }
796
813
  }
797
814
 
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));
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
864
860
 
865
- return res};
866
-
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} }) }
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 || {};
887
865
 
888
- const _mqtt_cmdid_dispatch ={
889
- create(target) {
890
- 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);
891
869
 
892
- , bind_pkt_future(_pkt_id=100) {
893
- let {hashbelt} = this;
870
+ on_mqtt([]); // init hashbelt and rotate_ts
871
+ return [on_mqtt, pkt_future]
894
872
 
895
- let _tmp_; // use _tmp_ to reuse _by_key closure
896
- let _by_key = answer_monad =>
897
- hashbelt[0].set(_tmp_, answer_monad);
898
873
 
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;}
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;
905
879
 
906
- return new Promise(_by_key)}) }
880
+ return new Promise(_ftr_by_key)}
907
881
 
908
- , answer(key, pkt) {
909
- for (let map of this.hashbelt) {
910
- let answer_monad = map.get(key);
911
- 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) {
912
886
  map.delete(key);
913
887
 
914
- answer_monad([pkt, /*err*/]); // option/maybe monad
888
+ fn_answer([pkt, /*err*/]); // option/maybe monad
915
889
  return true} }
916
890
  return false}
917
891
 
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
892
  function on_mqtt(pkt_list, ctx) {
988
893
  for (let pkt of pkt_list) {
989
- cmdids[pkt.id](_disp_, pkt, ctx); }
894
+ _mqtt_cmdids[pkt.id](target, answer, pkt, ctx);}
990
895
 
991
- if (Date.now() > rotate_ts) {
992
- _disp_.rotate_belt(rotate_n);
993
- 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
994
904
 
995
905
  class MQTTError extends Error {
996
906
  constructor(mqtt_pkt, reason=mqtt_pkt.reason) {
907
+ // use hex-encoded reasons to match MQTT spec documentation
997
908
  super(`[0x${reason.toString(16)}] ${reason.reason}`);
998
909
  this.mqtt_pkt = mqtt_pkt;
999
910
  this.reason = reason;} }
1000
911
 
1001
912
  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
913
  // Handshaking Packets
1026
-
1027
914
  async connect(pkt={}) {
1028
915
  let cid = pkt.client_id;
1029
- if ('string' !== typeof cid) {
916
+ if (! _isstr(cid)) {
1030
917
  // see init_client_id implementation in core.jsy
1031
918
  pkt.client_id = cid = this.client_id || this.init_client_id(cid);}
1032
919
  this.client_id = cid;
@@ -1034,23 +921,20 @@ class MQTTBase {
1034
921
  if (null == pkt.keep_alive) {
1035
922
  pkt.keep_alive = 60;}
1036
923
 
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}
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)}
1046
928
 
1047
929
  async disconnect(pkt={}) {
1048
- let res = await this._send('disconnect', pkt);
1049
- this._conn_.reset(false);
1050
- return res}
930
+ let response = await this._send0('disconnect', pkt);
931
+ return this.conn.on_dis(pkt, response)}
1051
932
 
1052
- auth(pkt={}) {
1053
- 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)}
1054
938
 
1055
939
  ping() {return this._send('pingreq', null, 'pingresp')}
1056
940
  puback({pkt_id}) {return this._send('puback', {pkt_id})}
@@ -1087,20 +971,18 @@ class MQTTBase {
1087
971
  // alias: publish -- because 'pub' is shorter for semantic aliases above
1088
972
  async pub(pkt, pub_opt) {
1089
973
  if (undefined === pkt.payload) {
1090
- if ('function' === typeof pub_opt) {
974
+ if (_isfn(pub_opt)) {
975
+ // pub_opt as a function is fn_encode value
1091
976
  pub_opt = {fn_encode: pub_opt};}
1092
977
 
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)}
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)}
1101
984
 
1102
985
  // Encode payload from msg; fn_encode allows alternative to JSON.stringify
1103
- let {fn_encode} = pub_opt || {};
1104
986
  pkt.payload = fn_encode
1105
987
  ? await fn_encode(msg)
1106
988
  : JSON.stringify(msg);}
@@ -1112,31 +994,31 @@ class MQTTBase {
1112
994
  pkt = pub_opt.xform(pkt) || pkt;} }
1113
995
 
1114
996
  return this._send('publish', pkt,
1115
- pkt.qos ? pkt : void 0 ) }// key
997
+ pkt.qos ? pkt : null ) }// key
1116
998
 
1117
999
 
1118
1000
  // Internal API
1119
1001
 
1120
- /* 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 */
1121
1004
 
1122
1005
  _init_dispatch(opt) {
1123
1006
  this.constructor?._once_();
1124
1007
  let target ={__proto__: opt.on_mqtt_type};
1125
1008
  target.mqtt_publish ||=
1126
1009
  this._init_router?.(opt, this, target);
1127
- return _mqtt_dispatch(this, target)}
1010
+ return _mqtt_dispatch(opt, target)}
1128
1011
 
1129
1012
  static _aliases() {
1130
1013
  return ' publish:pub sub:subscribe unsub:unsubscribe json_post:obj_post json_send:obj_send json_store:obj_store'}
1131
1014
 
1132
- static _once_(self=this) {
1133
- self._once_ = _=>0;
1134
- let p = self.prototype;
1015
+ static _once_(klass=this) {
1016
+ klass._once_ = _=>0;
1017
+ var alias, name, p = klass.prototype;
1135
1018
  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;} } } }
1019
+ for (alias of klass._aliases().split(/\s+/)) {
1020
+ [alias, name] = alias.split(':');
1021
+ p[alias] = p[name];} } }
1140
1022
 
1141
1023
 
1142
1024
  function _as_topics(pkt, ex, topic_prefix) {
@@ -1161,37 +1043,167 @@ function _as_topics(pkt, ex, topic_prefix) {
1161
1043
  pkt.topics = pkt.topics.map(_prefix_topics);}
1162
1044
  return pkt}
1163
1045
 
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
- };
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};
1169
1157
 
1170
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
+
1171
1175
  static mqtt_ctx(mqtt_level, mqtt_opts, pkt_ctx=pkt_api) {
1172
- let self = class extends this {};
1173
- self.prototype.mqtt_ctx =
1176
+ let klass = class extends this {};
1177
+ klass.prototype.mqtt_ctx =
1174
1178
  mqtt_pkt_ctx(mqtt_level, mqtt_opts, pkt_ctx);
1175
- 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); }
1176
1196
 
1177
1197
 
1178
1198
  // automatic Client Id for connect()
1179
1199
  init_client_id(parts=['u8-mqtt--','']) {
1180
- let sess_stg=this.sess_stg;
1200
+ let sess_stg = this.sess_stg;
1181
1201
  let key, cid = sess_stg?.getItem(key=parts.join(' '));
1182
1202
  if (! cid) {
1183
1203
  cid = parts.join(Math.random().toString(36).slice(2));
1184
1204
  sess_stg?.setItem(key, cid);}
1185
1205
  return cid}
1186
1206
 
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
1207
 
1196
1208
  on_live(client, is_reconnect) {
1197
1209
  if (is_reconnect) {
@@ -1217,19 +1229,16 @@ class MQTTCore extends MQTTBase {
1217
1229
  return new Promise(done => setTimeout(done, ms)) }
1218
1230
 
1219
1231
  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
-
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);} } );
1233
1242
  return this}
1234
1243
 
1235
1244
 
@@ -1300,33 +1309,30 @@ class MQTTCore extends MQTTBase {
1300
1309
 
1301
1310
  websock.binaryType = 'arraybuffer';
1302
1311
 
1303
- let ready, {readyState} = websock;
1312
+ let ws_ready, readyState = websock.readyState;
1304
1313
  if (1 !== readyState) {
1305
1314
  if (0 !== readyState) {
1306
- throw new Error('Invalid WebSocket readyState') }
1307
-
1308
- ready = new Promise(fn => websock.onopen = fn); }
1315
+ throw new Error('WS readyState') }
1309
1316
 
1317
+ ws_ready = new Promise(ready => websock.onopen = ready); }
1310
1318
 
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)) );
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)) );
1317
1324
 
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;}
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;}
1324
1330
 
1325
- _conn_.reset(err);};
1331
+ conn.reset(err);}; } );
1326
1332
 
1327
1333
  return this} }
1328
1334
 
1329
- const version = '0.5.3';
1335
+ const version = '0.6.0';
1330
1336
 
1331
1337
  const MQTTClient_v4 = /* #__PURE__ */
1332
1338
  with_topic_path_router(