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-v5.cjs CHANGED
@@ -163,10 +163,18 @@ let mqtt_reader_v5$1 = class mqtt_reader_v5 extends mqtt_reader_v4 {
163
163
 
164
164
  let res={}, fork = this.of(buf.subarray(vi, step.k|0));
165
165
  while (fork.has_more()) {
166
- let pt = mqtt_props.get( fork.u8() )
167
- , value = fork[pt.type]();
168
- res[pt.name] = ! pt.op ? value
169
- : fork[pt.op](res[pt.name], value);
166
+ let v, pk = fork.u8(), pt = mqtt_props.get( pk );
167
+
168
+ if (!pt) {
169
+ res.error = `Unknown mqtt_prop enum ${pk}`;
170
+ return res
171
+ }
172
+
173
+ v = fork[pt.type]();
174
+ if (pt.op) // accumulate operation
175
+ v = fork[pt.op](res[pt.name], v);
176
+
177
+ res[pt.name] = v;
170
178
  }
171
179
  return res
172
180
  }
@@ -300,6 +308,8 @@ class mqtt_writer_v5 extends mqtt_writer_v4 {
300
308
  let fork = this.of();
301
309
  for (let [name, value] of props) {
302
310
  let pt = mqtt_props.get(name);
311
+ if (!pt)
312
+ throw new Error(`Unknown mqtt_prop "${name}"`)
303
313
  fork[pt.op || 'one'](value, pt);
304
314
  }
305
315
  this.push(fork.pack());
@@ -983,6 +993,23 @@ function parse(str, loose) {
983
993
  };
984
994
  }
985
995
 
996
+ const _isfn = v => typeof v === 'function';
997
+ const _isstr = v => typeof v === 'string';
998
+
999
+ function _interval(fn_callback) {
1000
+ let tid;
1001
+ return (( td ) => {
1002
+ tid = clearInterval(tid);
1003
+ if (td) {
1004
+ tid = setInterval(fn_callback, 1000 * td);
1005
+
1006
+
1007
+
1008
+
1009
+ // ensure the interval allows the NodeJS event loop to exit
1010
+ tid.unref?.();
1011
+ return true} }) }
1012
+
986
1013
  /*
987
1014
  class AbstractTopicRouter ::
988
1015
  async invoke(pkt, ctx) ::
@@ -1073,7 +1100,7 @@ function mqtt_topic_path_router() {
1073
1100
  let fn = args.pop();
1074
1101
  let priority = args.pop();
1075
1102
 
1076
- if ('function' !== typeof fn) {
1103
+ if (! _isfn(fn)) {
1077
1104
  if (fn) {throw new TypeError()}
1078
1105
  fn = _ignore;}
1079
1106
 
@@ -1238,238 +1265,108 @@ function mqtt_pkt_ctx(mqtt_level, opts, pkt_ctx) {
1238
1265
  }
1239
1266
  }
1240
1267
 
1241
- function ao_defer_ctx(as_res = (...args) => args) {
1242
- let y,n,_pset = (a,b) => { y=a, n=b; };
1243
- return p =>(
1244
- p = new Promise(_pset)
1245
- , as_res(p, y, n)) }
1246
-
1247
- const ao_defer_v = /* #__PURE__ */
1248
- ao_defer_ctx();
1249
-
1250
- Promise.resolve({type:'init'});
1251
-
1252
- function _mqtt_conn(client, [on_mqtt, pkt_future]) {
1253
- let _q_init = ao_defer_v(), _q_ready = ao_defer_v();
1254
- let _send_ready = async (...args) => (await _q_ready[0])(...args);
1255
- let _send_mqtt_pkt, _has_connected;
1256
- client._send = _send_ready;
1257
-
1258
- return {
1259
- async when_ready() {await _q_ready[0];}
1260
-
1261
- , ping: _ping_interval (() =>_send_mqtt_pkt?.('pingreq'))
1262
-
1263
- , reset(err) {
1264
- if (! _send_mqtt_pkt) {return}
1265
-
1266
- if (err) {
1267
- _q_init[2](err);}
1268
-
1269
- _send_mqtt_pkt = null;
1270
- _q_init = ao_defer_v();
1271
- client._send = _send_ready;
1272
-
1273
- // call client.on_conn_reset in next promise microtask
1274
- client.conn_emit('on_disconnect', false===err, err);}
1275
-
1276
- , async send_connect(... args) {
1277
- if (! _send_mqtt_pkt) {
1278
- await _q_init[0]; }// _send_mqtt_pkt is set before fulfilled
1279
-
1280
- // await connack response
1281
- let res = await _send_mqtt_pkt(...args);
1282
- if (0 == res[0].reason) {
1283
- _has_connected = true;
1284
- // resolve _q_ready[0] with _send_mqtt_pkt closure
1285
- _q_ready[1](client._send = _send_mqtt_pkt);
1286
- _q_ready = ao_defer_v();
1287
- client.conn_emit('on_ready');}
1288
-
1289
- return res}
1268
+ async function _mqtt_cmd_evt(target, answer, pkt, ctx) {
1269
+ /* target : on_mqtt_type = {
1270
+ mqtt_pkt(pkt, ctx) {}, // generic
1271
+
1272
+ mqtt_auth(pkt, ctx) {},
1273
+ mqtt_connect(pkt, ctx) {},
1274
+ mqtt_connack(pkt, ctx) {},
1275
+ mqtt_disconnect(pkt, ctx) {},
1276
+
1277
+ mqtt_publish(pkt, ctx) {},
1278
+ mqtt_subscribe(pkt, ctx) {},
1279
+ mqtt_unsubscribe(pkt, ctx) {},
1280
+
1281
+ mqtt_pingreq(pkt, ctx) {},
1282
+ mqtt_pingresp(pkt, ctx) {},
1283
+ } */
1284
+
1285
+ let pkt_fn = target[`mqtt_${pkt.type}`] || target.mqtt_pkt;
1286
+ await pkt_fn?.call(target, pkt, ctx);}
1287
+
1288
+ function _mqtt_cmd_type(target, answer, pkt, ctx) {
1289
+ answer(pkt.type, pkt);
1290
+ _mqtt_cmd_evt(target, answer, pkt, ctx);}
1291
+
1292
+ function _mqtt_cmd_id(target, answer, pkt) {
1293
+ answer(pkt.pkt_id, pkt);}
1294
+
1295
+
1296
+ const _mqtt_cmdids =[
1297
+ _ => {} // 0x0 reserved
1298
+ , _mqtt_cmd_evt // 0x1 connect
1299
+ , _mqtt_cmd_type // 0x2 connack
1300
+ , _mqtt_cmd_evt // 0x3 publish
1301
+ , _mqtt_cmd_id // 0x4 puback
1302
+ , _mqtt_cmd_id // 0x5 pubrec
1303
+ , _mqtt_cmd_id // 0x6 pubrel
1304
+ , _mqtt_cmd_id // 0x7 pubcomp
1305
+ , _mqtt_cmd_evt // 0x8 subscribe
1306
+ , _mqtt_cmd_id // 0x9 suback
1307
+ , _mqtt_cmd_evt // 0xa unsubscribe
1308
+ , _mqtt_cmd_id // 0xb unsuback
1309
+ , _mqtt_cmd_type // 0xc pingreq
1310
+ , _mqtt_cmd_type // 0xd pingresp
1311
+ , _mqtt_cmd_evt // 0xe disconnect
1312
+ , _mqtt_cmd_type ];// 0xf auth
1290
1313
 
1291
- , is_set: (() =>!! _send_mqtt_pkt)
1292
- , set(mqtt_ctx, send_u8_pkt) {
1293
- if (_send_mqtt_pkt) {
1294
- throw new Error('Already connected')}
1295
-
1296
- mqtt_ctx = mqtt_ctx.mqtt_stream();
1297
- let sess_ctx = {mqtt: client};
1298
- let on_mqtt_chunk = u8_buf =>
1299
- on_mqtt(mqtt_ctx.decode(u8_buf), sess_ctx);
1300
-
1301
- _send_mqtt_pkt = async (type, pkt, key) => {
1302
- let res = undefined !== key
1303
- ? pkt_future(key) : true;
1304
-
1305
- await send_u8_pkt(
1306
- mqtt_ctx.encode_pkt(type, pkt));
1307
-
1308
- return res};
1309
-
1310
- _q_init[1](_send_mqtt_pkt); // resolve _q_init with _send_mqtt_pkt closure
1311
-
1312
- // call client.on_live in next promise microtask
1313
- client.conn_emit('on_live', _has_connected);
1314
- return on_mqtt_chunk} } }
1315
-
1316
-
1317
- function _ping_interval(send_ping) {
1318
- let tid;
1319
- return (( td ) => {
1320
- tid = clearInterval(tid);
1321
- if (td) {
1322
- tid = setInterval(send_ping, 1000 * td);
1323
-
1324
-
1325
-
1326
-
1327
- // ensure the interval allows the NodeJS event loop to exit
1328
- tid.unref?.();
1329
- return true} }) }
1314
+ function _mqtt_dispatch(opt, target) {
1315
+ let hashbelt=[], rotate_ts=0;
1316
+ // default rotate at 1s across 5 buckets
1317
+ let { td: rotate_td=1000, n: rotate_n=5 } = opt?.rotate || {};
1330
1318
 
1331
- const _mqtt_cmdid_dispatch ={
1332
- create(target) {
1333
- return {__proto__: this, target, hashbelt: [new Map()]} }
1319
+ // Promise / future scaffolding
1320
+ let _pkt_id=100, _ftr_key; // use _ftr_key to reuse _by_key closure
1321
+ let _ftr_by_key = fn_answer => hashbelt[0].set(_ftr_key, fn_answer);
1334
1322
 
1335
- , bind_pkt_future(_pkt_id=100) {
1336
- let {hashbelt} = this;
1323
+ on_mqtt([]); // init hashbelt and rotate_ts
1324
+ return [on_mqtt, pkt_future]
1337
1325
 
1338
- let _tmp_; // use _tmp_ to reuse _by_key closure
1339
- let _by_key = answer_monad =>
1340
- hashbelt[0].set(_tmp_, answer_monad);
1341
1326
 
1342
- return (( pkt_or_key ) => {
1343
- if ('string' === typeof pkt_or_key) {
1344
- _tmp_ = pkt_or_key;}
1345
- else {
1346
- _pkt_id = (_pkt_id + 1) & 0xffff;
1347
- _tmp_ = pkt_or_key.pkt_id = _pkt_id;}
1327
+ function pkt_future(pkt_or_key) {
1328
+ if (! _isstr(pkt_or_key)) {
1329
+ _pkt_id = (_pkt_id + 1) & 0xffff; // 16-bit unsigned short
1330
+ _ftr_key = pkt_or_key.pkt_id = _pkt_id;}
1331
+ else _ftr_key = pkt_or_key;
1348
1332
 
1349
- return new Promise(_by_key)}) }
1333
+ return new Promise(_ftr_by_key)}
1350
1334
 
1351
- , answer(key, pkt) {
1352
- for (let map of this.hashbelt) {
1353
- let answer_monad = map.get(key);
1354
- if (undefined !== answer_monad) {
1335
+ function answer(key, pkt) {
1336
+ for (let map of hashbelt) {
1337
+ let fn_answer = map.get(key);
1338
+ if (fn_answer) {
1355
1339
  map.delete(key);
1356
1340
 
1357
- answer_monad([pkt, /*err*/]); // option/maybe monad
1341
+ fn_answer([pkt, /*err*/]); // option/maybe monad
1358
1342
  return true} }
1359
1343
  return false}
1360
1344
 
1361
- , rotate_belt(n) {
1362
- let {hashbelt} = this;
1363
- hashbelt.unshift(new Map());
1364
- for (let old of hashbelt.splice(n || 5)) {
1365
- for (let answer_monad of old.values()) {
1366
- answer_monad([/*pkt*/, 'expired']); } } }// option/maybe monad
1367
-
1368
- , cmdids: ((() => {
1369
- return [
1370
- (() =>{} )// 0x0 reserved
1371
- , by_evt // 0x1 connect
1372
- , by_type // 0x2 connack
1373
- , by_evt // 0x3 publish
1374
- , by_id // 0x4 puback
1375
- , by_id // 0x5 pubrec
1376
- , by_id // 0x6 pubrel
1377
- , by_id // 0x7 pubcomp
1378
- , by_evt // 0x8 subscribe
1379
- , by_id // 0x9 suback
1380
- , by_evt // 0xa unsubscribe
1381
- , by_id // 0xb unsuback
1382
- , by_type // 0xc pingreq
1383
- , by_type // 0xd pingresp
1384
- , by_evt // 0xe disconnect
1385
- , by_type ]// 0xf auth
1386
-
1387
-
1388
- function by_id(disp, pkt) {
1389
- disp.answer(pkt.pkt_id, pkt); }
1390
-
1391
- function by_type(disp, pkt, ctx) {
1392
- disp.answer(pkt.type, pkt);
1393
- by_evt(disp, pkt, ctx);}
1394
-
1395
- async function by_evt({target}, pkt, ctx) {
1396
- let fn = target[`mqtt_${pkt.type}`]
1397
- || target.mqtt_pkt;
1398
-
1399
- await fn?.call(target, pkt, ctx);} })()) };
1400
-
1401
- /*
1402
- on_mqtt_type = {
1403
- mqtt_auth(pkt, ctx) ::
1404
- mqtt_connect(pkt, ctx) ::
1405
- mqtt_connack(pkt, ctx) ::
1406
- mqtt_disconnect(pkt, ctx) ::
1407
-
1408
- mqtt_publish(pkt, ctx)
1409
- mqtt_subscribe(pkt, ctx) ::
1410
- mqtt_unsubscribe(pkt, ctx) ::
1411
-
1412
- mqtt_pingreq(pkt, ctx) ::
1413
- mqtt_pingresp(pkt, ctx) ::
1414
- }
1415
- */
1416
-
1417
- function _mqtt_dispatch(opt, target) {
1418
- let _disp_ = _mqtt_cmdid_dispatch.create(target);
1419
- let { cmdids } = _disp_;
1420
-
1421
- // default rotate at 1s across 5 buckets
1422
- let { td: rotate_td=1000, n: rotate_n=5 } =
1423
- opt && opt.rotate || {};
1424
-
1425
- let rotate_ts = rotate_td + Date.now();
1426
-
1427
- return [on_mqtt,
1428
- _disp_.bind_pkt_future()]
1429
-
1430
1345
  function on_mqtt(pkt_list, ctx) {
1431
1346
  for (let pkt of pkt_list) {
1432
- cmdids[pkt.id](_disp_, pkt, ctx); }
1347
+ _mqtt_cmdids[pkt.id](target, answer, pkt, ctx);}
1433
1348
 
1434
- if (Date.now() > rotate_ts) {
1435
- _disp_.rotate_belt(rotate_n);
1436
- rotate_ts = rotate_td + Date.now();} } }
1349
+ // rotate after rotate_ts
1350
+ let now = Date.now();
1351
+ if (now > rotate_ts) {
1352
+ rotate_ts = rotate_td + now;
1353
+ hashbelt.unshift(new Map());
1354
+ while (hashbelt.length > rotate_n) {
1355
+ for (let fn_answer of hashbelt.pop().values()) {
1356
+ fn_answer([/*pkt*/, 'expired']); } } } } }// option/maybe monad
1437
1357
 
1438
1358
  class MQTTError extends Error {
1439
1359
  constructor(mqtt_pkt, reason=mqtt_pkt.reason) {
1360
+ // use hex-encoded reasons to match MQTT spec documentation
1440
1361
  super(`[0x${reason.toString(16)}] ${reason.reason}`);
1441
1362
  this.mqtt_pkt = mqtt_pkt;
1442
1363
  this.reason = reason;} }
1443
1364
 
1444
1365
  class MQTTBase {
1445
- constructor(opt={}) {
1446
- this.with(opt);
1447
- this._conn_ = _mqtt_conn(this,
1448
- this._init_dispatch(opt, this)); }
1449
-
1450
- with(fns_ns) {
1451
- for (let [k,v] of Object.entries(fns_ns)) {
1452
- if ('function' === typeof v) {this[k] = v;} }
1453
- return this}
1454
-
1455
- async conn_emit(evt, arg, err_arg) {
1456
- this.log_conn?.(evt, arg, err_arg);
1457
- try {
1458
- let fn_evt = this[await evt]; // microtask break using `await evt`
1459
- if (fn_evt) {
1460
- await fn_evt.call(this, this, arg, err_arg);}
1461
- else if (err_arg) {throw err_arg} }
1462
- catch (err) {
1463
- this.on_error(err, evt);} }
1464
-
1465
- on_error(err, evt) {
1466
- console.warn('[[u8-mqtt error: %s]]', evt, err); }
1467
-
1468
1366
  // Handshaking Packets
1469
-
1470
1367
  async connect(pkt={}) {
1471
1368
  let cid = pkt.client_id;
1472
- if ('string' !== typeof cid) {
1369
+ if (! _isstr(cid)) {
1473
1370
  // see init_client_id implementation in core.jsy
1474
1371
  pkt.client_id = cid = this.client_id || this.init_client_id(cid);}
1475
1372
  this.client_id = cid;
@@ -1477,23 +1374,20 @@ class MQTTBase {
1477
1374
  if (null == pkt.keep_alive) {
1478
1375
  pkt.keep_alive = 60;}
1479
1376
 
1480
- let res = await this._conn_
1481
- .send_connect('connect', pkt, 'connack');
1482
-
1483
- if (0 != res[0].reason) {
1484
- throw new this.MQTTError(res[0])}
1485
-
1486
- // TODO: merge with server's keep_alive frequency
1487
- this._conn_.ping(pkt.keep_alive);
1488
- return res}
1377
+ let response = await this._send0('connect', pkt, 'connack');
1378
+ if (0 != response[0].reason) {// compare to 0 to coerce to number
1379
+ throw new this.MQTTError(response[0])}
1380
+ return this.conn.on_conn(pkt, response)}
1489
1381
 
1490
1382
  async disconnect(pkt={}) {
1491
- let res = await this._send('disconnect', pkt);
1492
- this._conn_.reset(false);
1493
- return res}
1383
+ let response = await this._send0('disconnect', pkt);
1384
+ return this.conn.on_dis(pkt, response)}
1494
1385
 
1495
- auth(pkt={}) {
1496
- return this._send('auth', pkt, 'auth')}
1386
+ async auth(pkt={}) {
1387
+ let response = await this._send0('auth', pkt, 'auth');
1388
+ if (response[0].reason) {
1389
+ throw new this.MQTTError(response[0])}
1390
+ return this.conn.on_auth(pkt, response)}
1497
1391
 
1498
1392
  ping() {return this._send('pingreq', null, 'pingresp')}
1499
1393
  puback({pkt_id}) {return this._send('puback', {pkt_id})}
@@ -1530,20 +1424,18 @@ class MQTTBase {
1530
1424
  // alias: publish -- because 'pub' is shorter for semantic aliases above
1531
1425
  async pub(pkt, pub_opt) {
1532
1426
  if (undefined === pkt.payload) {
1533
- if ('function' === typeof pub_opt) {
1427
+ if (_isfn(pub_opt)) {
1428
+ // pub_opt as a function is fn_encode value
1534
1429
  pub_opt = {fn_encode: pub_opt};}
1535
1430
 
1536
- let {msg} = pkt;
1537
- switch (typeof msg) {
1538
- case 'function':
1539
- pub_opt = {...pub_opt, fn_encode: msg};
1540
- // flow into 'undefined' case
1541
- case 'undefined':
1542
- // return a single-value closure to publish packets
1543
- return v => this.pub({...pkt, [pkt.arg || 'payload']: v}, pub_opt)}
1431
+ let msg = pkt.msg, fn_encode = pub_opt?.fn_encode;
1432
+ if (null == msg || _isfn(msg)) {
1433
+ // when msg is a function, return closure using fn_encode
1434
+ if (msg) {pub_opt = {...pub_opt, fn_encode: msg};}
1435
+ // return a single-value closure to publish packets
1436
+ return v => this.pub({...pkt, [pkt.arg || 'payload']: v}, pub_opt)}
1544
1437
 
1545
1438
  // Encode payload from msg; fn_encode allows alternative to JSON.stringify
1546
- let {fn_encode} = pub_opt || {};
1547
1439
  pkt.payload = fn_encode
1548
1440
  ? await fn_encode(msg)
1549
1441
  : JSON.stringify(msg);}
@@ -1555,31 +1447,31 @@ class MQTTBase {
1555
1447
  pkt = pub_opt.xform(pkt) || pkt;} }
1556
1448
 
1557
1449
  return this._send('publish', pkt,
1558
- pkt.qos ? pkt : void 0 ) }// key
1450
+ pkt.qos ? pkt : null ) }// key
1559
1451
 
1560
1452
 
1561
1453
  // Internal API
1562
1454
 
1563
- /* async _send(type, pkt) -- provided by _conn_ and transport */
1455
+ /* async _send0(type, pkt) -- provided by conn and transport */
1456
+ /* async _send(type, pkt) -- provided by conn and transport */
1564
1457
 
1565
1458
  _init_dispatch(opt) {
1566
1459
  this.constructor?._once_();
1567
1460
  let target ={__proto__: opt.on_mqtt_type};
1568
1461
  target.mqtt_publish ||=
1569
1462
  this._init_router?.(opt, this, target);
1570
- return _mqtt_dispatch(this, target)}
1463
+ return _mqtt_dispatch(opt, target)}
1571
1464
 
1572
1465
  static _aliases() {
1573
1466
  return ' publish:pub sub:subscribe unsub:unsubscribe json_post:obj_post json_send:obj_send json_store:obj_store'}
1574
1467
 
1575
- static _once_(self=this) {
1576
- self._once_ = _=>0;
1577
- let p = self.prototype;
1468
+ static _once_(klass=this) {
1469
+ klass._once_ = _=>0;
1470
+ var alias, name, p = klass.prototype;
1578
1471
  p.MQTTError = MQTTError;
1579
- for (let alias of self._aliases().split(/\s+/)) {
1580
- alias = alias.split(':');
1581
- let fn = alias[1] && p[alias[1]];
1582
- if (fn) {p[alias[0]] = fn;} } } }
1472
+ for (alias of klass._aliases().split(/\s+/)) {
1473
+ [alias, name] = alias.split(':');
1474
+ p[alias] = p[name];} } }
1583
1475
 
1584
1476
 
1585
1477
  function _as_topics(pkt, ex, topic_prefix) {
@@ -1604,37 +1496,167 @@ function _as_topics(pkt, ex, topic_prefix) {
1604
1496
  pkt.topics = pkt.topics.map(_prefix_topics);}
1605
1497
  return pkt}
1606
1498
 
1607
- const pkt_api = {
1608
- utf8(u8) { return new TextDecoder('utf-8').decode(u8 || this.payload ) },
1609
- json(u8) { return JSON.parse( this.utf8(u8) || null ) },
1610
- text(u8) { return this.utf8(u8) },
1611
- };
1499
+ const _defer_obj = o =>(
1500
+ o.p = new Promise((a,e) => { o.a=a; o.e=e; })
1501
+ , o);
1502
+
1503
+ function _dfn_reset(client, attr, fn_after) {
1504
+ // a resetable deferred for a function
1505
+ let self = {set}, afn = async (...args) => (await self.p)(...args);
1506
+ return set()
1507
+
1508
+ function set() {
1509
+ if (afn !== client[attr]) {
1510
+ _defer_obj(self).p.then(fn_after, _=>0);
1511
+ client[attr] = afn;}
1512
+ return self} }
1513
+
1514
+ function _mqtt_conn(opt, client, [on_mqtt, pkt_future]) {
1515
+ let _abort;
1516
+ let _dfn_send0 = _dfn_reset(client, '_send0', // client._send0 getter/setter
1517
+ _=> client.conn_emit('on_live', conn.has_connected));
1518
+ let _dfn_ready = _dfn_reset(client, '_send', // client._send getter/setter
1519
+ _=> client.conn_emit('on_ready'));
1520
+ let _keep_alive_ival = _interval (() =>client._send0('pingreq') );// resettable interval for keep_alive ping
1521
+
1522
+ let conn = Object.create({
1523
+ ping: (td=conn.keep_alive) => _keep_alive_ival(td)
1524
+
1525
+ , on_conn(pkt, response) {
1526
+ conn.has_connected = true;
1527
+ conn.keep_alive = opt.keep_alive || response[0].props?.server_keep_alive || pkt.keep_alive;
1528
+ client.conn_emit('on_conn');
1529
+ return opt.use_auth
1530
+ ? response // wait on enhanced authentication step
1531
+ : conn.on_auth(null, response) }// otherwise, connect is also auth
1532
+
1533
+ , on_auth(pkt, response) {
1534
+ _dfn_ready.a(_dfn_send0.p);
1535
+ if (0 != opt.keep_alive) {
1536
+ conn.ping();}
1537
+ client.conn_emit('on_auth', !pkt);
1538
+ return response}
1539
+
1540
+ , on_dis(pkt, response) {
1541
+ conn.reset(false);
1542
+ return response}
1543
+
1544
+ , reset(err) {
1545
+ if (err) {
1546
+ _dfn_send0.e(err); }// send error to uses of _send0 (connect, auth)
1547
+ _abort.e(err); // abort in-progress connections
1548
+
1549
+ delete conn.is_set;
1550
+ conn.ready = handshake();
1551
+ client.conn_emit('on_disconnect', false===err, err);}
1552
+
1553
+ , abort() {
1554
+ _dfn_ready.e(err); // abort all messages awaiting ready state
1555
+ return conn.reset(err)}
1556
+
1557
+ , async setup(gate, send_u8_pkt, init_msg_loop) {
1558
+ if (conn.is_set) {
1559
+ throw new Error() }// already in-progress
1560
+
1561
+ conn.is_set = true;
1562
+ await gate;
1563
+
1564
+ // setup send/recv MQTT parsing context
1565
+ let mqtt_ctx = client.mqtt_ctx.mqtt_stream();
1566
+
1567
+ {// setup inbound message loop
1568
+ let sess_ctx = {mqtt: client}; // mutable session context
1569
+ let on_mqtt_chunk = u8 => on_mqtt(mqtt_ctx.decode(u8), sess_ctx);
1570
+ init_msg_loop(on_mqtt_chunk, conn);}
1571
+
1572
+ // setup outbound message path and transport connection
1573
+ send_u8_pkt = await send_u8_pkt;
1574
+ _dfn_send0.a(
1575
+ async (type, pkt, key) => {
1576
+ let res = undefined !== key
1577
+ ? pkt_future(key) : true;
1578
+
1579
+ await send_u8_pkt(
1580
+ mqtt_ctx.encode_pkt(type, pkt));
1581
+ return res} ); } });
1582
+
1583
+ conn.ready = handshake();
1584
+ return conn
1585
+
1586
+ async function handshake() {
1587
+ _abort = _defer_obj({});
1588
+
1589
+ _keep_alive_ival(0); // clearInterval on keep alive ping
1590
+ _dfn_send0.set(); // reset client._send0 if necessary
1591
+ _dfn_ready.set(); // reset client._send if necessary
1592
+
1593
+ try {
1594
+ // set client._send0 as passtrhough after transport connection
1595
+ client._send0 = await Promise.race([_dfn_send0.p, _abort.p]);
1596
+
1597
+ // set client._send as passtrhough after ready
1598
+ client._send = await Promise.race([_dfn_ready.p, _abort.p]);
1599
+ return true}
1600
+ catch (err) {
1601
+ return false} } }
1602
+
1603
+ const pkt_api ={
1604
+ utf8(u8) {return new TextDecoder('utf-8').decode(u8 || this.payload )}
1605
+ , json(u8) {return JSON.parse( this.utf8(u8) || null )}
1606
+ , text(u8) {return this.utf8(u8)} };
1607
+
1608
+ const opt_default ={
1609
+ sess_stg: globalThis.sessionStorage};
1612
1610
 
1613
1611
  class MQTTCore extends MQTTBase {
1612
+ constructor(opt) {
1613
+ super();
1614
+ this.with(opt);
1615
+ opt ={...opt_default, ...opt};
1616
+ // settings for MQTTCore
1617
+ this.sess_stg = opt.sess_stg;
1618
+ // setup connection and dispatch
1619
+ this.conn = _mqtt_conn(opt, this,
1620
+ this._init_dispatch(opt)); }
1621
+
1622
+ with(fns_ns) {
1623
+ for (let [k,v] of Object.entries(fns_ns)) {
1624
+ if (_isfn(v)) {this[k] = v;} }
1625
+ return this}
1626
+
1627
+
1614
1628
  static mqtt_ctx(mqtt_level, mqtt_opts, pkt_ctx=pkt_api) {
1615
- let self = class extends this {};
1616
- self.prototype.mqtt_ctx =
1629
+ let klass = class extends this {};
1630
+ klass.prototype.mqtt_ctx =
1617
1631
  mqtt_pkt_ctx(mqtt_level, mqtt_opts, pkt_ctx);
1618
- return self}
1632
+ return klass}
1633
+
1634
+
1635
+ async conn_emit(evt, arg, err_arg) {
1636
+ this.log_conn?.(evt, arg, err_arg);
1637
+ try {
1638
+ let fn_evt = this[await evt]; // microtask break using `await evt`
1639
+ if (fn_evt) {
1640
+ await fn_evt.call(this, this, arg, err_arg);}
1641
+ else if (err_arg) {throw err_arg} }
1642
+ catch (err) {
1643
+ this.on_error(err, evt);} }
1644
+
1645
+ on_error(err, evt) {
1646
+ console.warn('[[u8-mqtt error: %s]]', evt, err); }
1647
+ log_conn(evt, arg, err_arg) {
1648
+ console.info('[[u8-mqtt conn: %s]]', evt, arg, err_arg); }
1619
1649
 
1620
1650
 
1621
1651
  // automatic Client Id for connect()
1622
1652
  init_client_id(parts=['u8-mqtt--','']) {
1623
- let sess_stg=this.sess_stg;
1653
+ let sess_stg = this.sess_stg;
1624
1654
  let key, cid = sess_stg?.getItem(key=parts.join(' '));
1625
1655
  if (! cid) {
1626
1656
  cid = parts.join(Math.random().toString(36).slice(2));
1627
1657
  sess_stg?.setItem(key, cid);}
1628
1658
  return cid}
1629
1659
 
1630
- get sess_stg() {return globalThis.sessionStorage}
1631
-
1632
-
1633
- //on_error(err, evt) ::
1634
- // console.warn @ '[[u8-mqtt error: %s]]', evt, err
1635
-
1636
- //log_conn(evt, arg, err_arg) ::
1637
- // console.info @ '[[u8-mqtt log: %s]]', evt, arg, err_arg
1638
1660
 
1639
1661
  on_live(client, is_reconnect) {
1640
1662
  if (is_reconnect) {
@@ -1660,19 +1682,16 @@ class MQTTCore extends MQTTBase {
1660
1682
  return new Promise(done => setTimeout(done, ms)) }
1661
1683
 
1662
1684
  with_async_iter(async_iter, write_u8_pkt) {
1663
- let on_mqtt_chunk = this._conn_.set(
1664
- this.mqtt_ctx,
1665
- write_u8_pkt);
1666
-
1667
- this._msg_loop = ((async () => {
1668
- try {
1669
- async_iter = await async_iter;
1670
- for await (let chunk of async_iter)
1671
- on_mqtt_chunk(chunk);
1672
- this._conn_.reset();}
1673
- catch (err) {
1674
- this._conn_.reset(err);} })());
1675
-
1685
+ this.conn.setup(async_iter,
1686
+ write_u8_pkt,
1687
+ async (on_mqtt_chunk, conn) => {
1688
+ try {
1689
+ async_iter = await async_iter;
1690
+ for await (let chunk of async_iter)
1691
+ on_mqtt_chunk(chunk);
1692
+ conn.reset();}
1693
+ catch (err) {
1694
+ conn.reset(err);} } );
1676
1695
  return this}
1677
1696
 
1678
1697
 
@@ -1743,33 +1762,30 @@ class MQTTCore extends MQTTBase {
1743
1762
 
1744
1763
  websock.binaryType = 'arraybuffer';
1745
1764
 
1746
- let ready, {readyState} = websock;
1765
+ let ws_ready, readyState = websock.readyState;
1747
1766
  if (1 !== readyState) {
1748
1767
  if (0 !== readyState) {
1749
- throw new Error('Invalid WebSocket readyState') }
1750
-
1751
- ready = new Promise(fn => websock.onopen = fn); }
1768
+ throw new Error('WS readyState') }
1752
1769
 
1770
+ ws_ready = new Promise(ready => websock.onopen = ready); }
1753
1771
 
1754
- let {_conn_} = this;
1755
- let on_mqtt_chunk = _conn_.set(
1756
- this.mqtt_ctx,
1757
- async u8_pkt =>(
1758
- await ready
1759
- , websock.send(u8_pkt)) );
1772
+ this.conn.setup(ws_ready,
1773
+ u8_pkt => websock.send(u8_pkt),
1774
+ (on_mqtt_chunk, conn) => {
1775
+ websock.onmessage = evt =>(
1776
+ on_mqtt_chunk(new Uint8Array(evt.data)) );
1760
1777
 
1761
- websock.onmessage = evt =>(on_mqtt_chunk(new Uint8Array(evt.data)));
1762
- websock.onclose = evt => {
1763
- if (! evt.wasClean) {
1764
- var err = new Error('websocket connection close');
1765
- err.code = evt.code;
1766
- err.reason = evt.reason;}
1778
+ websock.onclose = evt => {
1779
+ if (! evt.wasClean) {
1780
+ var err = new Error('websocket close');
1781
+ err.code = evt.code;
1782
+ err.reason = evt.reason;}
1767
1783
 
1768
- _conn_.reset(err);};
1784
+ conn.reset(err);}; } );
1769
1785
 
1770
1786
  return this} }
1771
1787
 
1772
- const version = '0.5.3-node';
1788
+ const version = '0.6.0-node';
1773
1789
 
1774
1790
  const MQTTClient_v4 = /* #__PURE__ */
1775
1791
  with_topic_path_router(