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/cjs/full-v4.cjs CHANGED
@@ -728,6 +728,23 @@ function parse(str, loose) {
728
728
  };
729
729
  }
730
730
 
731
+ const _isfn = v => typeof v === 'function';
732
+ const _isstr = v => typeof v === 'string';
733
+
734
+ function _interval(fn_callback) {
735
+ let tid;
736
+ return (( td ) => {
737
+ tid = clearInterval(tid);
738
+ if (td) {
739
+ tid = setInterval(fn_callback, 1000 * td);
740
+
741
+
742
+
743
+
744
+ // ensure the interval allows the NodeJS event loop to exit
745
+ tid.unref?.();
746
+ return true} }) }
747
+
731
748
  /*
732
749
  class AbstractTopicRouter ::
733
750
  async invoke(pkt, ctx) ::
@@ -818,7 +835,7 @@ function mqtt_topic_path_router() {
818
835
  let fn = args.pop();
819
836
  let priority = args.pop();
820
837
 
821
- if ('function' !== typeof fn) {
838
+ if (! _isfn(fn)) {
822
839
  if (fn) {throw new TypeError()}
823
840
  fn = _ignore;}
824
841
 
@@ -983,238 +1000,108 @@ function mqtt_pkt_ctx(mqtt_level, opts, pkt_ctx) {
983
1000
  }
984
1001
  }
985
1002
 
986
- function ao_defer_ctx(as_res = (...args) => args) {
987
- let y,n,_pset = (a,b) => { y=a, n=b; };
988
- return p =>(
989
- p = new Promise(_pset)
990
- , as_res(p, y, n)) }
991
-
992
- const ao_defer_v = /* #__PURE__ */
993
- ao_defer_ctx();
994
-
995
- Promise.resolve({type:'init'});
996
-
997
- function _mqtt_conn(client, [on_mqtt, pkt_future]) {
998
- let _q_init = ao_defer_v(), _q_ready = ao_defer_v();
999
- let _send_ready = async (...args) => (await _q_ready[0])(...args);
1000
- let _send_mqtt_pkt, _has_connected;
1001
- client._send = _send_ready;
1002
-
1003
- return {
1004
- async when_ready() {await _q_ready[0];}
1005
-
1006
- , ping: _ping_interval (() =>_send_mqtt_pkt?.('pingreq'))
1007
-
1008
- , reset(err) {
1009
- if (! _send_mqtt_pkt) {return}
1010
-
1011
- if (err) {
1012
- _q_init[2](err);}
1013
-
1014
- _send_mqtt_pkt = null;
1015
- _q_init = ao_defer_v();
1016
- client._send = _send_ready;
1017
-
1018
- // call client.on_conn_reset in next promise microtask
1019
- client.conn_emit('on_disconnect', false===err, err);}
1020
-
1021
- , async send_connect(... args) {
1022
- if (! _send_mqtt_pkt) {
1023
- await _q_init[0]; }// _send_mqtt_pkt is set before fulfilled
1024
-
1025
- // await connack response
1026
- let res = await _send_mqtt_pkt(...args);
1027
- if (0 == res[0].reason) {
1028
- _has_connected = true;
1029
- // resolve _q_ready[0] with _send_mqtt_pkt closure
1030
- _q_ready[1](client._send = _send_mqtt_pkt);
1031
- _q_ready = ao_defer_v();
1032
- client.conn_emit('on_ready');}
1033
-
1034
- return res}
1035
-
1036
- , is_set: (() =>!! _send_mqtt_pkt)
1037
- , set(mqtt_ctx, send_u8_pkt) {
1038
- if (_send_mqtt_pkt) {
1039
- throw new Error('Already connected')}
1040
-
1041
- mqtt_ctx = mqtt_ctx.mqtt_stream();
1042
- let sess_ctx = {mqtt: client};
1043
- let on_mqtt_chunk = u8_buf =>
1044
- on_mqtt(mqtt_ctx.decode(u8_buf), sess_ctx);
1045
-
1046
- _send_mqtt_pkt = async (type, pkt, key) => {
1047
- let res = undefined !== key
1048
- ? pkt_future(key) : true;
1049
-
1050
- await send_u8_pkt(
1051
- mqtt_ctx.encode_pkt(type, pkt));
1003
+ async function _mqtt_cmd_evt(target, answer, pkt, ctx) {
1004
+ /* target : on_mqtt_type = {
1005
+ mqtt_pkt(pkt, ctx) {}, // generic
1006
+
1007
+ mqtt_auth(pkt, ctx) {},
1008
+ mqtt_connect(pkt, ctx) {},
1009
+ mqtt_connack(pkt, ctx) {},
1010
+ mqtt_disconnect(pkt, ctx) {},
1011
+
1012
+ mqtt_publish(pkt, ctx) {},
1013
+ mqtt_subscribe(pkt, ctx) {},
1014
+ mqtt_unsubscribe(pkt, ctx) {},
1015
+
1016
+ mqtt_pingreq(pkt, ctx) {},
1017
+ mqtt_pingresp(pkt, ctx) {},
1018
+ } */
1019
+
1020
+ let pkt_fn = target[`mqtt_${pkt.type}`] || target.mqtt_pkt;
1021
+ await pkt_fn?.call(target, pkt, ctx);}
1022
+
1023
+ function _mqtt_cmd_type(target, answer, pkt, ctx) {
1024
+ answer(pkt.type, pkt);
1025
+ _mqtt_cmd_evt(target, answer, pkt, ctx);}
1026
+
1027
+ function _mqtt_cmd_id(target, answer, pkt) {
1028
+ answer(pkt.pkt_id, pkt);}
1029
+
1030
+
1031
+ const _mqtt_cmdids =[
1032
+ _ => {} // 0x0 reserved
1033
+ , _mqtt_cmd_evt // 0x1 connect
1034
+ , _mqtt_cmd_type // 0x2 connack
1035
+ , _mqtt_cmd_evt // 0x3 publish
1036
+ , _mqtt_cmd_id // 0x4 puback
1037
+ , _mqtt_cmd_id // 0x5 pubrec
1038
+ , _mqtt_cmd_id // 0x6 pubrel
1039
+ , _mqtt_cmd_id // 0x7 pubcomp
1040
+ , _mqtt_cmd_evt // 0x8 subscribe
1041
+ , _mqtt_cmd_id // 0x9 suback
1042
+ , _mqtt_cmd_evt // 0xa unsubscribe
1043
+ , _mqtt_cmd_id // 0xb unsuback
1044
+ , _mqtt_cmd_type // 0xc pingreq
1045
+ , _mqtt_cmd_type // 0xd pingresp
1046
+ , _mqtt_cmd_evt // 0xe disconnect
1047
+ , _mqtt_cmd_type ];// 0xf auth
1052
1048
 
1053
- return res};
1054
-
1055
- _q_init[1](_send_mqtt_pkt); // resolve _q_init with _send_mqtt_pkt closure
1056
-
1057
- // call client.on_live in next promise microtask
1058
- client.conn_emit('on_live', _has_connected);
1059
- return on_mqtt_chunk} } }
1060
-
1061
-
1062
- function _ping_interval(send_ping) {
1063
- let tid;
1064
- return (( td ) => {
1065
- tid = clearInterval(tid);
1066
- if (td) {
1067
- tid = setInterval(send_ping, 1000 * td);
1068
-
1069
-
1070
-
1071
-
1072
- // ensure the interval allows the NodeJS event loop to exit
1073
- tid.unref?.();
1074
- return true} }) }
1049
+ function _mqtt_dispatch(opt, target) {
1050
+ let hashbelt=[], rotate_ts=0;
1051
+ // default rotate at 1s across 5 buckets
1052
+ let { td: rotate_td=1000, n: rotate_n=5 } = opt?.rotate || {};
1075
1053
 
1076
- const _mqtt_cmdid_dispatch ={
1077
- create(target) {
1078
- return {__proto__: this, target, hashbelt: [new Map()]} }
1054
+ // Promise / future scaffolding
1055
+ let _pkt_id=100, _ftr_key; // use _ftr_key to reuse _by_key closure
1056
+ let _ftr_by_key = fn_answer => hashbelt[0].set(_ftr_key, fn_answer);
1079
1057
 
1080
- , bind_pkt_future(_pkt_id=100) {
1081
- let {hashbelt} = this;
1058
+ on_mqtt([]); // init hashbelt and rotate_ts
1059
+ return [on_mqtt, pkt_future]
1082
1060
 
1083
- let _tmp_; // use _tmp_ to reuse _by_key closure
1084
- let _by_key = answer_monad =>
1085
- hashbelt[0].set(_tmp_, answer_monad);
1086
1061
 
1087
- return (( pkt_or_key ) => {
1088
- if ('string' === typeof pkt_or_key) {
1089
- _tmp_ = pkt_or_key;}
1090
- else {
1091
- _pkt_id = (_pkt_id + 1) & 0xffff;
1092
- _tmp_ = pkt_or_key.pkt_id = _pkt_id;}
1062
+ function pkt_future(pkt_or_key) {
1063
+ if (! _isstr(pkt_or_key)) {
1064
+ _pkt_id = (_pkt_id + 1) & 0xffff; // 16-bit unsigned short
1065
+ _ftr_key = pkt_or_key.pkt_id = _pkt_id;}
1066
+ else _ftr_key = pkt_or_key;
1093
1067
 
1094
- return new Promise(_by_key)}) }
1068
+ return new Promise(_ftr_by_key)}
1095
1069
 
1096
- , answer(key, pkt) {
1097
- for (let map of this.hashbelt) {
1098
- let answer_monad = map.get(key);
1099
- if (undefined !== answer_monad) {
1070
+ function answer(key, pkt) {
1071
+ for (let map of hashbelt) {
1072
+ let fn_answer = map.get(key);
1073
+ if (fn_answer) {
1100
1074
  map.delete(key);
1101
1075
 
1102
- answer_monad([pkt, /*err*/]); // option/maybe monad
1076
+ fn_answer([pkt, /*err*/]); // option/maybe monad
1103
1077
  return true} }
1104
1078
  return false}
1105
1079
 
1106
- , rotate_belt(n) {
1107
- let {hashbelt} = this;
1108
- hashbelt.unshift(new Map());
1109
- for (let old of hashbelt.splice(n || 5)) {
1110
- for (let answer_monad of old.values()) {
1111
- answer_monad([/*pkt*/, 'expired']); } } }// option/maybe monad
1112
-
1113
- , cmdids: ((() => {
1114
- return [
1115
- (() =>{} )// 0x0 reserved
1116
- , by_evt // 0x1 connect
1117
- , by_type // 0x2 connack
1118
- , by_evt // 0x3 publish
1119
- , by_id // 0x4 puback
1120
- , by_id // 0x5 pubrec
1121
- , by_id // 0x6 pubrel
1122
- , by_id // 0x7 pubcomp
1123
- , by_evt // 0x8 subscribe
1124
- , by_id // 0x9 suback
1125
- , by_evt // 0xa unsubscribe
1126
- , by_id // 0xb unsuback
1127
- , by_type // 0xc pingreq
1128
- , by_type // 0xd pingresp
1129
- , by_evt // 0xe disconnect
1130
- , by_type ]// 0xf auth
1131
-
1132
-
1133
- function by_id(disp, pkt) {
1134
- disp.answer(pkt.pkt_id, pkt); }
1135
-
1136
- function by_type(disp, pkt, ctx) {
1137
- disp.answer(pkt.type, pkt);
1138
- by_evt(disp, pkt, ctx);}
1139
-
1140
- async function by_evt({target}, pkt, ctx) {
1141
- let fn = target[`mqtt_${pkt.type}`]
1142
- || target.mqtt_pkt;
1143
-
1144
- await fn?.call(target, pkt, ctx);} })()) };
1145
-
1146
- /*
1147
- on_mqtt_type = {
1148
- mqtt_auth(pkt, ctx) ::
1149
- mqtt_connect(pkt, ctx) ::
1150
- mqtt_connack(pkt, ctx) ::
1151
- mqtt_disconnect(pkt, ctx) ::
1152
-
1153
- mqtt_publish(pkt, ctx)
1154
- mqtt_subscribe(pkt, ctx) ::
1155
- mqtt_unsubscribe(pkt, ctx) ::
1156
-
1157
- mqtt_pingreq(pkt, ctx) ::
1158
- mqtt_pingresp(pkt, ctx) ::
1159
- }
1160
- */
1161
-
1162
- function _mqtt_dispatch(opt, target) {
1163
- let _disp_ = _mqtt_cmdid_dispatch.create(target);
1164
- let { cmdids } = _disp_;
1165
-
1166
- // default rotate at 1s across 5 buckets
1167
- let { td: rotate_td=1000, n: rotate_n=5 } =
1168
- opt && opt.rotate || {};
1169
-
1170
- let rotate_ts = rotate_td + Date.now();
1171
-
1172
- return [on_mqtt,
1173
- _disp_.bind_pkt_future()]
1174
-
1175
1080
  function on_mqtt(pkt_list, ctx) {
1176
1081
  for (let pkt of pkt_list) {
1177
- cmdids[pkt.id](_disp_, pkt, ctx); }
1082
+ _mqtt_cmdids[pkt.id](target, answer, pkt, ctx);}
1178
1083
 
1179
- if (Date.now() > rotate_ts) {
1180
- _disp_.rotate_belt(rotate_n);
1181
- rotate_ts = rotate_td + Date.now();} } }
1084
+ // rotate after rotate_ts
1085
+ let now = Date.now();
1086
+ if (now > rotate_ts) {
1087
+ rotate_ts = rotate_td + now;
1088
+ hashbelt.unshift(new Map());
1089
+ while (hashbelt.length > rotate_n) {
1090
+ for (let fn_answer of hashbelt.pop().values()) {
1091
+ fn_answer([/*pkt*/, 'expired']); } } } } }// option/maybe monad
1182
1092
 
1183
1093
  class MQTTError extends Error {
1184
1094
  constructor(mqtt_pkt, reason=mqtt_pkt.reason) {
1095
+ // use hex-encoded reasons to match MQTT spec documentation
1185
1096
  super(`[0x${reason.toString(16)}] ${reason.reason}`);
1186
1097
  this.mqtt_pkt = mqtt_pkt;
1187
1098
  this.reason = reason;} }
1188
1099
 
1189
1100
  class MQTTBase {
1190
- constructor(opt={}) {
1191
- this.with(opt);
1192
- this._conn_ = _mqtt_conn(this,
1193
- this._init_dispatch(opt, this)); }
1194
-
1195
- with(fns_ns) {
1196
- for (let [k,v] of Object.entries(fns_ns)) {
1197
- if ('function' === typeof v) {this[k] = v;} }
1198
- return this}
1199
-
1200
- async conn_emit(evt, arg, err_arg) {
1201
- this.log_conn?.(evt, arg, err_arg);
1202
- try {
1203
- let fn_evt = this[await evt]; // microtask break using `await evt`
1204
- if (fn_evt) {
1205
- await fn_evt.call(this, this, arg, err_arg);}
1206
- else if (err_arg) {throw err_arg} }
1207
- catch (err) {
1208
- this.on_error(err, evt);} }
1209
-
1210
- on_error(err, evt) {
1211
- console.warn('[[u8-mqtt error: %s]]', evt, err); }
1212
-
1213
1101
  // Handshaking Packets
1214
-
1215
1102
  async connect(pkt={}) {
1216
1103
  let cid = pkt.client_id;
1217
- if ('string' !== typeof cid) {
1104
+ if (! _isstr(cid)) {
1218
1105
  // see init_client_id implementation in core.jsy
1219
1106
  pkt.client_id = cid = this.client_id || this.init_client_id(cid);}
1220
1107
  this.client_id = cid;
@@ -1222,23 +1109,20 @@ class MQTTBase {
1222
1109
  if (null == pkt.keep_alive) {
1223
1110
  pkt.keep_alive = 60;}
1224
1111
 
1225
- let res = await this._conn_
1226
- .send_connect('connect', pkt, 'connack');
1227
-
1228
- if (0 != res[0].reason) {
1229
- throw new this.MQTTError(res[0])}
1230
-
1231
- // TODO: merge with server's keep_alive frequency
1232
- this._conn_.ping(pkt.keep_alive);
1233
- return res}
1112
+ let response = await this._send0('connect', pkt, 'connack');
1113
+ if (0 != response[0].reason) {// compare to 0 to coerce to number
1114
+ throw new this.MQTTError(response[0])}
1115
+ return this.conn.on_conn(pkt, response)}
1234
1116
 
1235
1117
  async disconnect(pkt={}) {
1236
- let res = await this._send('disconnect', pkt);
1237
- this._conn_.reset(false);
1238
- return res}
1118
+ let response = await this._send0('disconnect', pkt);
1119
+ return this.conn.on_dis(pkt, response)}
1239
1120
 
1240
- auth(pkt={}) {
1241
- return this._send('auth', pkt, 'auth')}
1121
+ async auth(pkt={}) {
1122
+ let response = await this._send0('auth', pkt, 'auth');
1123
+ if (response[0].reason) {
1124
+ throw new this.MQTTError(response[0])}
1125
+ return this.conn.on_auth(pkt, response)}
1242
1126
 
1243
1127
  ping() {return this._send('pingreq', null, 'pingresp')}
1244
1128
  puback({pkt_id}) {return this._send('puback', {pkt_id})}
@@ -1275,20 +1159,18 @@ class MQTTBase {
1275
1159
  // alias: publish -- because 'pub' is shorter for semantic aliases above
1276
1160
  async pub(pkt, pub_opt) {
1277
1161
  if (undefined === pkt.payload) {
1278
- if ('function' === typeof pub_opt) {
1162
+ if (_isfn(pub_opt)) {
1163
+ // pub_opt as a function is fn_encode value
1279
1164
  pub_opt = {fn_encode: pub_opt};}
1280
1165
 
1281
- let {msg} = pkt;
1282
- switch (typeof msg) {
1283
- case 'function':
1284
- pub_opt = {...pub_opt, fn_encode: msg};
1285
- // flow into 'undefined' case
1286
- case 'undefined':
1287
- // return a single-value closure to publish packets
1288
- return v => this.pub({...pkt, [pkt.arg || 'payload']: v}, pub_opt)}
1166
+ let msg = pkt.msg, fn_encode = pub_opt?.fn_encode;
1167
+ if (null == msg || _isfn(msg)) {
1168
+ // when msg is a function, return closure using fn_encode
1169
+ if (msg) {pub_opt = {...pub_opt, fn_encode: msg};}
1170
+ // return a single-value closure to publish packets
1171
+ return v => this.pub({...pkt, [pkt.arg || 'payload']: v}, pub_opt)}
1289
1172
 
1290
1173
  // Encode payload from msg; fn_encode allows alternative to JSON.stringify
1291
- let {fn_encode} = pub_opt || {};
1292
1174
  pkt.payload = fn_encode
1293
1175
  ? await fn_encode(msg)
1294
1176
  : JSON.stringify(msg);}
@@ -1300,31 +1182,31 @@ class MQTTBase {
1300
1182
  pkt = pub_opt.xform(pkt) || pkt;} }
1301
1183
 
1302
1184
  return this._send('publish', pkt,
1303
- pkt.qos ? pkt : void 0 ) }// key
1185
+ pkt.qos ? pkt : null ) }// key
1304
1186
 
1305
1187
 
1306
1188
  // Internal API
1307
1189
 
1308
- /* async _send(type, pkt) -- provided by _conn_ and transport */
1190
+ /* async _send0(type, pkt) -- provided by conn and transport */
1191
+ /* async _send(type, pkt) -- provided by conn and transport */
1309
1192
 
1310
1193
  _init_dispatch(opt) {
1311
1194
  this.constructor?._once_();
1312
1195
  let target ={__proto__: opt.on_mqtt_type};
1313
1196
  target.mqtt_publish ||=
1314
1197
  this._init_router?.(opt, this, target);
1315
- return _mqtt_dispatch(this, target)}
1198
+ return _mqtt_dispatch(opt, target)}
1316
1199
 
1317
1200
  static _aliases() {
1318
1201
  return ' publish:pub sub:subscribe unsub:unsubscribe json_post:obj_post json_send:obj_send json_store:obj_store'}
1319
1202
 
1320
- static _once_(self=this) {
1321
- self._once_ = _=>0;
1322
- let p = self.prototype;
1203
+ static _once_(klass=this) {
1204
+ klass._once_ = _=>0;
1205
+ var alias, name, p = klass.prototype;
1323
1206
  p.MQTTError = MQTTError;
1324
- for (let alias of self._aliases().split(/\s+/)) {
1325
- alias = alias.split(':');
1326
- let fn = alias[1] && p[alias[1]];
1327
- if (fn) {p[alias[0]] = fn;} } } }
1207
+ for (alias of klass._aliases().split(/\s+/)) {
1208
+ [alias, name] = alias.split(':');
1209
+ p[alias] = p[name];} } }
1328
1210
 
1329
1211
 
1330
1212
  function _as_topics(pkt, ex, topic_prefix) {
@@ -1349,37 +1231,167 @@ function _as_topics(pkt, ex, topic_prefix) {
1349
1231
  pkt.topics = pkt.topics.map(_prefix_topics);}
1350
1232
  return pkt}
1351
1233
 
1352
- const pkt_api = {
1353
- utf8(u8) { return new TextDecoder('utf-8').decode(u8 || this.payload ) },
1354
- json(u8) { return JSON.parse( this.utf8(u8) || null ) },
1355
- text(u8) { return this.utf8(u8) },
1356
- };
1234
+ const _defer_obj = o =>(
1235
+ o.p = new Promise((a,e) => { o.a=a; o.e=e; })
1236
+ , o);
1237
+
1238
+ function _dfn_reset(client, attr, fn_after) {
1239
+ // a resetable deferred for a function
1240
+ let self = {set}, afn = async (...args) => (await self.p)(...args);
1241
+ return set()
1242
+
1243
+ function set() {
1244
+ if (afn !== client[attr]) {
1245
+ _defer_obj(self).p.then(fn_after, _=>0);
1246
+ client[attr] = afn;}
1247
+ return self} }
1248
+
1249
+ function _mqtt_conn(opt, client, [on_mqtt, pkt_future]) {
1250
+ let _abort;
1251
+ let _dfn_send0 = _dfn_reset(client, '_send0', // client._send0 getter/setter
1252
+ _=> client.conn_emit('on_live', conn.has_connected));
1253
+ let _dfn_ready = _dfn_reset(client, '_send', // client._send getter/setter
1254
+ _=> client.conn_emit('on_ready'));
1255
+ let _keep_alive_ival = _interval (() =>client._send0('pingreq') );// resettable interval for keep_alive ping
1256
+
1257
+ let conn = Object.create({
1258
+ ping: (td=conn.keep_alive) => _keep_alive_ival(td)
1259
+
1260
+ , on_conn(pkt, response) {
1261
+ conn.has_connected = true;
1262
+ conn.keep_alive = opt.keep_alive || response[0].props?.server_keep_alive || pkt.keep_alive;
1263
+ client.conn_emit('on_conn');
1264
+ return opt.use_auth
1265
+ ? response // wait on enhanced authentication step
1266
+ : conn.on_auth(null, response) }// otherwise, connect is also auth
1267
+
1268
+ , on_auth(pkt, response) {
1269
+ _dfn_ready.a(_dfn_send0.p);
1270
+ if (0 != opt.keep_alive) {
1271
+ conn.ping();}
1272
+ client.conn_emit('on_auth', !pkt);
1273
+ return response}
1274
+
1275
+ , on_dis(pkt, response) {
1276
+ conn.reset(false);
1277
+ return response}
1278
+
1279
+ , reset(err) {
1280
+ if (err) {
1281
+ _dfn_send0.e(err); }// send error to uses of _send0 (connect, auth)
1282
+ _abort.e(err); // abort in-progress connections
1283
+
1284
+ delete conn.is_set;
1285
+ conn.ready = handshake();
1286
+ client.conn_emit('on_disconnect', false===err, err);}
1287
+
1288
+ , abort() {
1289
+ _dfn_ready.e(err); // abort all messages awaiting ready state
1290
+ return conn.reset(err)}
1291
+
1292
+ , async setup(gate, send_u8_pkt, init_msg_loop) {
1293
+ if (conn.is_set) {
1294
+ throw new Error() }// already in-progress
1295
+
1296
+ conn.is_set = true;
1297
+ await gate;
1298
+
1299
+ // setup send/recv MQTT parsing context
1300
+ let mqtt_ctx = client.mqtt_ctx.mqtt_stream();
1301
+
1302
+ {// setup inbound message loop
1303
+ let sess_ctx = {mqtt: client}; // mutable session context
1304
+ let on_mqtt_chunk = u8 => on_mqtt(mqtt_ctx.decode(u8), sess_ctx);
1305
+ init_msg_loop(on_mqtt_chunk, conn);}
1306
+
1307
+ // setup outbound message path and transport connection
1308
+ send_u8_pkt = await send_u8_pkt;
1309
+ _dfn_send0.a(
1310
+ async (type, pkt, key) => {
1311
+ let res = undefined !== key
1312
+ ? pkt_future(key) : true;
1313
+
1314
+ await send_u8_pkt(
1315
+ mqtt_ctx.encode_pkt(type, pkt));
1316
+ return res} ); } });
1317
+
1318
+ conn.ready = handshake();
1319
+ return conn
1320
+
1321
+ async function handshake() {
1322
+ _abort = _defer_obj({});
1323
+
1324
+ _keep_alive_ival(0); // clearInterval on keep alive ping
1325
+ _dfn_send0.set(); // reset client._send0 if necessary
1326
+ _dfn_ready.set(); // reset client._send if necessary
1327
+
1328
+ try {
1329
+ // set client._send0 as passtrhough after transport connection
1330
+ client._send0 = await Promise.race([_dfn_send0.p, _abort.p]);
1331
+
1332
+ // set client._send as passtrhough after ready
1333
+ client._send = await Promise.race([_dfn_ready.p, _abort.p]);
1334
+ return true}
1335
+ catch (err) {
1336
+ return false} } }
1337
+
1338
+ const pkt_api ={
1339
+ utf8(u8) {return new TextDecoder('utf-8').decode(u8 || this.payload )}
1340
+ , json(u8) {return JSON.parse( this.utf8(u8) || null )}
1341
+ , text(u8) {return this.utf8(u8)} };
1342
+
1343
+ const opt_default ={
1344
+ sess_stg: globalThis.sessionStorage};
1357
1345
 
1358
1346
  class MQTTCore extends MQTTBase {
1347
+ constructor(opt) {
1348
+ super();
1349
+ this.with(opt);
1350
+ opt ={...opt_default, ...opt};
1351
+ // settings for MQTTCore
1352
+ this.sess_stg = opt.sess_stg;
1353
+ // setup connection and dispatch
1354
+ this.conn = _mqtt_conn(opt, this,
1355
+ this._init_dispatch(opt)); }
1356
+
1357
+ with(fns_ns) {
1358
+ for (let [k,v] of Object.entries(fns_ns)) {
1359
+ if (_isfn(v)) {this[k] = v;} }
1360
+ return this}
1361
+
1362
+
1359
1363
  static mqtt_ctx(mqtt_level, mqtt_opts, pkt_ctx=pkt_api) {
1360
- let self = class extends this {};
1361
- self.prototype.mqtt_ctx =
1364
+ let klass = class extends this {};
1365
+ klass.prototype.mqtt_ctx =
1362
1366
  mqtt_pkt_ctx(mqtt_level, mqtt_opts, pkt_ctx);
1363
- return self}
1367
+ return klass}
1368
+
1369
+
1370
+ async conn_emit(evt, arg, err_arg) {
1371
+ this.log_conn?.(evt, arg, err_arg);
1372
+ try {
1373
+ let fn_evt = this[await evt]; // microtask break using `await evt`
1374
+ if (fn_evt) {
1375
+ await fn_evt.call(this, this, arg, err_arg);}
1376
+ else if (err_arg) {throw err_arg} }
1377
+ catch (err) {
1378
+ this.on_error(err, evt);} }
1379
+
1380
+ on_error(err, evt) {
1381
+ console.warn('[[u8-mqtt error: %s]]', evt, err); }
1382
+ log_conn(evt, arg, err_arg) {
1383
+ console.info('[[u8-mqtt conn: %s]]', evt, arg, err_arg); }
1364
1384
 
1365
1385
 
1366
1386
  // automatic Client Id for connect()
1367
1387
  init_client_id(parts=['u8-mqtt--','']) {
1368
- let sess_stg=this.sess_stg;
1388
+ let sess_stg = this.sess_stg;
1369
1389
  let key, cid = sess_stg?.getItem(key=parts.join(' '));
1370
1390
  if (! cid) {
1371
1391
  cid = parts.join(Math.random().toString(36).slice(2));
1372
1392
  sess_stg?.setItem(key, cid);}
1373
1393
  return cid}
1374
1394
 
1375
- get sess_stg() {return globalThis.sessionStorage}
1376
-
1377
-
1378
- //on_error(err, evt) ::
1379
- // console.warn @ '[[u8-mqtt error: %s]]', evt, err
1380
-
1381
- //log_conn(evt, arg, err_arg) ::
1382
- // console.info @ '[[u8-mqtt log: %s]]', evt, arg, err_arg
1383
1395
 
1384
1396
  on_live(client, is_reconnect) {
1385
1397
  if (is_reconnect) {
@@ -1405,19 +1417,16 @@ class MQTTCore extends MQTTBase {
1405
1417
  return new Promise(done => setTimeout(done, ms)) }
1406
1418
 
1407
1419
  with_async_iter(async_iter, write_u8_pkt) {
1408
- let on_mqtt_chunk = this._conn_.set(
1409
- this.mqtt_ctx,
1410
- write_u8_pkt);
1411
-
1412
- this._msg_loop = ((async () => {
1413
- try {
1414
- async_iter = await async_iter;
1415
- for await (let chunk of async_iter)
1416
- on_mqtt_chunk(chunk);
1417
- this._conn_.reset();}
1418
- catch (err) {
1419
- this._conn_.reset(err);} })());
1420
-
1420
+ this.conn.setup(async_iter,
1421
+ write_u8_pkt,
1422
+ async (on_mqtt_chunk, conn) => {
1423
+ try {
1424
+ async_iter = await async_iter;
1425
+ for await (let chunk of async_iter)
1426
+ on_mqtt_chunk(chunk);
1427
+ conn.reset();}
1428
+ catch (err) {
1429
+ conn.reset(err);} } );
1421
1430
  return this}
1422
1431
 
1423
1432
 
@@ -1488,33 +1497,30 @@ class MQTTCore extends MQTTBase {
1488
1497
 
1489
1498
  websock.binaryType = 'arraybuffer';
1490
1499
 
1491
- let ready, {readyState} = websock;
1500
+ let ws_ready, readyState = websock.readyState;
1492
1501
  if (1 !== readyState) {
1493
1502
  if (0 !== readyState) {
1494
- throw new Error('Invalid WebSocket readyState') }
1495
-
1496
- ready = new Promise(fn => websock.onopen = fn); }
1503
+ throw new Error('WS readyState') }
1497
1504
 
1505
+ ws_ready = new Promise(ready => websock.onopen = ready); }
1498
1506
 
1499
- let {_conn_} = this;
1500
- let on_mqtt_chunk = _conn_.set(
1501
- this.mqtt_ctx,
1502
- async u8_pkt =>(
1503
- await ready
1504
- , websock.send(u8_pkt)) );
1507
+ this.conn.setup(ws_ready,
1508
+ u8_pkt => websock.send(u8_pkt),
1509
+ (on_mqtt_chunk, conn) => {
1510
+ websock.onmessage = evt =>(
1511
+ on_mqtt_chunk(new Uint8Array(evt.data)) );
1505
1512
 
1506
- websock.onmessage = evt =>(on_mqtt_chunk(new Uint8Array(evt.data)));
1507
- websock.onclose = evt => {
1508
- if (! evt.wasClean) {
1509
- var err = new Error('websocket connection close');
1510
- err.code = evt.code;
1511
- err.reason = evt.reason;}
1513
+ websock.onclose = evt => {
1514
+ if (! evt.wasClean) {
1515
+ var err = new Error('websocket close');
1516
+ err.code = evt.code;
1517
+ err.reason = evt.reason;}
1512
1518
 
1513
- _conn_.reset(err);};
1519
+ conn.reset(err);}; } );
1514
1520
 
1515
1521
  return this} }
1516
1522
 
1517
- const version = '0.5.3-node';
1523
+ const version = '0.6.0-node';
1518
1524
 
1519
1525
  const MQTTClient_v4 = /* #__PURE__ */
1520
1526
  with_topic_path_router(