@seriousme/opifex 1.2.1 → 1.4.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 (108) hide show
  1. package/.github/workflows/update-license-year.sh +14 -0
  2. package/.github/workflows/update-license-year.yml +13 -0
  3. package/CHANGELOG.md +16 -0
  4. package/LICENSE.txt +1 -1
  5. package/README.md +72 -4
  6. package/bin/conformanceServer.ts +1 -1
  7. package/bin/mqtt.ts +5 -2
  8. package/client/client.ts +13 -3
  9. package/client/context.ts +2 -0
  10. package/client/mod.ts +1 -2
  11. package/deno/client.ts +10 -5
  12. package/deno/server.ts +3 -2
  13. package/deno.json +4 -1
  14. package/deno.lock +11 -8
  15. package/dev_utils/dummyConn.ts +4 -4
  16. package/dist/bin/conformanceServer.js +52 -0
  17. package/dist/bin/demoServer.js +29 -57
  18. package/dist/bin/mqtt.js +148 -187
  19. package/dist/client/ConnectionState.js +7 -0
  20. package/dist/client/client.js +129 -0
  21. package/dist/client/context.js +148 -0
  22. package/dist/client/deps.js +5 -0
  23. package/dist/client/handlers/handleConnack.js +18 -0
  24. package/dist/client/handlers/handlePacket.js +50 -0
  25. package/dist/client/handlers/handlePuback.js +6 -0
  26. package/dist/client/handlers/handlePubcomp.js +9 -0
  27. package/dist/client/handlers/handlePublish.js +28 -0
  28. package/dist/client/handlers/handlePubrec.js +17 -0
  29. package/dist/client/handlers/handlePubrel.js +19 -0
  30. package/dist/client/handlers/handleSuback.js +9 -0
  31. package/dist/client/handlers/handleUnsuback.js +7 -0
  32. package/dist/client/mod.js +5 -3
  33. package/dist/client/store/deps.js +1 -0
  34. package/dist/client/store/memoryStore.js +36 -0
  35. package/dist/client/store/store.js +1 -0
  36. package/dist/mqttConn/mqttConn.js +104 -0
  37. package/dist/mqttPacket/AuthenticationResult.js +12 -0
  38. package/dist/mqttPacket/BitMask.js +10 -0
  39. package/dist/mqttPacket/PacketType.js +18 -0
  40. package/dist/mqttPacket/connack.js +27 -0
  41. package/dist/mqttPacket/connect.js +115 -0
  42. package/dist/mqttPacket/decoder.js +89 -0
  43. package/dist/mqttPacket/disconnect.js +14 -0
  44. package/dist/mqttPacket/encoder.js +56 -0
  45. package/dist/mqttPacket/length.js +43 -0
  46. package/dist/mqttPacket/mod.js +71 -0
  47. package/dist/mqttPacket/pingreq.js +15 -0
  48. package/dist/mqttPacket/pingres.js +14 -0
  49. package/dist/mqttPacket/puback.js +20 -0
  50. package/dist/mqttPacket/pubcomp.js +20 -0
  51. package/dist/mqttPacket/publish.js +51 -0
  52. package/dist/mqttPacket/pubrec.js +20 -0
  53. package/dist/mqttPacket/pubrel.js +20 -0
  54. package/dist/mqttPacket/suback.js +29 -0
  55. package/dist/mqttPacket/subscribe.js +52 -0
  56. package/dist/mqttPacket/types.js +1 -0
  57. package/dist/mqttPacket/unsuback.js +20 -0
  58. package/dist/mqttPacket/unsubscribe.js +36 -0
  59. package/dist/mqttPacket/validators.js +15 -0
  60. package/dist/node/client.js +64 -0
  61. package/dist/node/server.js +22 -0
  62. package/dist/node/wrapNodeSocket.js +47 -0
  63. package/dist/persistence/deps.js +3 -0
  64. package/dist/persistence/memory/memoryPersistence.js +117 -0
  65. package/dist/persistence/persistence.js +1 -0
  66. package/dist/persistence/store.js +1 -0
  67. package/dist/server/context.js +100 -0
  68. package/dist/server/deps.js +5 -0
  69. package/dist/server/handlers/handleConnect.js +46 -0
  70. package/dist/server/handlers/handleDisconnect.js +4 -0
  71. package/dist/server/handlers/handlePacket.js +58 -0
  72. package/dist/server/handlers/handlePingreq.js +6 -0
  73. package/dist/server/handlers/handlePuback.js +8 -0
  74. package/dist/server/handlers/handlePubcomp.js +8 -0
  75. package/dist/server/handlers/handlePublish.js +40 -0
  76. package/dist/server/handlers/handlePubrec.js +15 -0
  77. package/dist/server/handlers/handlePubrel.js +18 -0
  78. package/dist/server/handlers/handleSubscribe.js +33 -0
  79. package/dist/server/handlers/handleUnsubscribe.js +12 -0
  80. package/dist/server/mod.js +7 -589
  81. package/dist/server/server.js +47 -0
  82. package/dist/socket/socket.js +36 -0
  83. package/dist/timer/timer.js +40 -0
  84. package/dist/trie/trie.js +95 -0
  85. package/dist/tsconfig.tsbuildinfo +1 -0
  86. package/dist/utils/args.js +61 -0
  87. package/dist/utils/assert.js +19 -0
  88. package/dist/utils/asyncQueue.js +56 -0
  89. package/dist/utils/deferred.js +11 -0
  90. package/dist/utils/logger.js +29 -0
  91. package/dist/utils/mod.js +7 -0
  92. package/dist/utils/nextTick.js +3 -0
  93. package/dist/utils/platform.js +4 -0
  94. package/mqttConn/mqttConn.ts +4 -1
  95. package/node/client.ts +29 -6
  96. package/node/server.ts +2 -0
  97. package/node/wrapNodeSocket.ts +1 -1
  98. package/package.json +17 -4
  99. package/server/mod.ts +1 -2
  100. package/socket/socket.ts +3 -2
  101. package/timer/timer.ts +1 -1
  102. package/tsconfig.json +3 -0
  103. package/.vscode/extensions.json +0 -10
  104. package/.vscode/settings.json +0 -12
  105. package/deno/wrapDenoConn.ts +0 -12
  106. package/dist/client-DqLTBVC2.js +0 -541
  107. package/dist/timer-DDWVNsyG.js +0 -924
  108. package/dist/wrapNodeSocket-BAJm059T.js +0 -115
@@ -1,541 +0,0 @@
1
- import {
2
- a as assert,
3
- b as AuthenticationResultByNumber,
4
- c as PacketNameByType,
5
- l as logger,
6
- M as MqttConn,
7
- P as PacketType,
8
- T as Timer,
9
- } from "./timer-DDWVNsyG.js";
10
- import "node:process";
11
-
12
- class Deferred {
13
- promise;
14
- resolve;
15
- reject;
16
- constructor() {
17
- this.promise = new Promise((resolve, reject) => {
18
- this.resolve = resolve;
19
- this.reject = reject;
20
- });
21
- }
22
- }
23
-
24
- function nextTick() {
25
- return new Promise((resolve) => setTimeout(resolve, 0));
26
- }
27
-
28
- class AsyncQueue {
29
- queue = [];
30
- maxQueueLength = Infinity;
31
- nextResolve = (_value) => {
32
- };
33
- nextReject = (_reason) => {
34
- };
35
- done = false;
36
- hasNext = false;
37
- constructor(maxQueueLength) {
38
- if (maxQueueLength) {
39
- this.maxQueueLength = maxQueueLength;
40
- }
41
- }
42
- async next() {
43
- await nextTick();
44
- if (this.done && this.queue.length === 0) {
45
- return Promise.reject("Closed");
46
- }
47
- return new Promise((resolve, reject) => {
48
- if (this.queue.length > 0) {
49
- const item = this.queue.shift();
50
- if (item) {
51
- return resolve(item);
52
- }
53
- }
54
- this.nextResolve = resolve;
55
- this.nextReject = reject;
56
- this.hasNext = true;
57
- });
58
- }
59
- close(reason = "closed") {
60
- this.done = true;
61
- if (this.hasNext) {
62
- this.nextReject(reason);
63
- }
64
- }
65
- async *[Symbol.asyncIterator]() {
66
- while (true) {
67
- yield this.next();
68
- }
69
- }
70
- push(item) {
71
- if (this.hasNext) {
72
- this.nextResolve(item);
73
- this.hasNext = false;
74
- return;
75
- }
76
- if (this.queue.length > this.maxQueueLength) {
77
- this.queue.shift();
78
- }
79
- this.queue.push(item);
80
- }
81
- get isDone() {
82
- return this.done;
83
- }
84
- }
85
-
86
- const maxPacketId = 65535;
87
-
88
- class MemoryStore {
89
- packetId;
90
- pendingIncoming;
91
- pendingOutgoing;
92
- pendingAckOutgoing;
93
- constructor() {
94
- this.packetId = 0;
95
- this.pendingIncoming = /* @__PURE__ */ new Map();
96
- this.pendingOutgoing = /* @__PURE__ */ new Map();
97
- this.pendingAckOutgoing = /* @__PURE__ */ new Map();
98
- }
99
- nextId() {
100
- const currentId = this.packetId;
101
- do {
102
- this.packetId++;
103
- if (this.packetId > maxPacketId) {
104
- this.packetId = 1;
105
- }
106
- } while (
107
- (this.pendingIncoming.has(this.packetId) ||
108
- this.pendingOutgoing.has(this.packetId) ||
109
- this.pendingAckOutgoing.has(this.packetId)) &&
110
- this.packetId !== currentId
111
- );
112
- assert(this.packetId !== currentId, "No unused packetId available");
113
- return this.packetId;
114
- }
115
- async *pendingOutgoingPackets() {
116
- for (const [_id, packet] of this.pendingAckOutgoing) {
117
- yield packet;
118
- }
119
- for (const [_id, packet] of this.pendingOutgoing) {
120
- yield packet;
121
- }
122
- }
123
- }
124
-
125
- const ConnectionState = {
126
- offline: "offline",
127
- connecting: "connecting",
128
- connected: "connected",
129
- disconnecting: "disconnecting",
130
- disconnected: "disconnected",
131
- };
132
-
133
- async function handleConnack(packet, ctx) {
134
- if (packet.returnCode === 0) {
135
- ctx.connectionState = ConnectionState.connected;
136
- ctx.pingTimer?.reset();
137
- ctx.unresolvedConnect?.resolve(packet.returnCode);
138
- for await (const packet2 of ctx.store.pendingOutgoingPackets()) {
139
- ctx.send(packet2);
140
- }
141
- return;
142
- }
143
- const err = new Error(
144
- `Connect failed: ${AuthenticationResultByNumber[packet.returnCode]}`,
145
- );
146
- ctx.connectionState = ConnectionState.disconnecting;
147
- ctx.pingTimer?.clear();
148
- ctx.unresolvedConnect?.reject(err);
149
- }
150
-
151
- async function handlePublish(ctx, packet) {
152
- const qos = packet.qos || 0;
153
- if (qos === 0) {
154
- ctx.receivePublish(packet);
155
- return;
156
- }
157
- if (packet.id !== void 0) {
158
- if (qos === 1) {
159
- ctx.receivePublish(packet);
160
- await ctx.send({
161
- type: PacketType.puback,
162
- id: packet.id,
163
- });
164
- return;
165
- }
166
- if (ctx.store) {
167
- ctx.store.pendingIncoming.set(packet.id, packet);
168
- await ctx.send({
169
- type: PacketType.pubrec,
170
- id: packet.id,
171
- });
172
- }
173
- }
174
- }
175
-
176
- function handlePuback(ctx, packet) {
177
- const id = packet.id;
178
- ctx.store.pendingOutgoing.delete(id);
179
- ctx.receivePuback(id);
180
- }
181
-
182
- async function handlePubrec(ctx, packet) {
183
- const id = packet.id;
184
- const ack = {
185
- type: PacketType.pubrel,
186
- id,
187
- };
188
- if (ctx.store.pendingOutgoing.has(id)) {
189
- ctx.store.pendingAckOutgoing.set(id, ack);
190
- ctx.store.pendingOutgoing.delete(id);
191
- await ctx.send(ack);
192
- }
193
- }
194
-
195
- async function handlePubrel(ctx, packet) {
196
- const id = packet.id;
197
- if (ctx.store.pendingIncoming.has(id)) {
198
- const storedPacket = ctx.store.pendingIncoming.get(id);
199
- if (storedPacket) {
200
- ctx.receivePublish(storedPacket);
201
- ctx.store.pendingIncoming.delete(id);
202
- await ctx.send({
203
- type: PacketType.pubcomp,
204
- id,
205
- });
206
- }
207
- }
208
- }
209
-
210
- function handlePubcomp(ctx, packet) {
211
- const id = packet.id;
212
- if (ctx.store.pendingAckOutgoing.has(id)) {
213
- ctx.store.pendingAckOutgoing.delete(id);
214
- ctx.receivePuback(id);
215
- }
216
- }
217
-
218
- function handleSuback(ctx, packet) {
219
- const id = packet.id;
220
- ctx.store.pendingOutgoing.delete(id);
221
- ctx.receiveSuback(id, packet.returnCodes);
222
- }
223
-
224
- function handleUnsuback(ctx, packet) {
225
- const id = packet.id;
226
- ctx.store.pendingOutgoing.delete(id);
227
- ctx.receiveUnsuback(id);
228
- }
229
-
230
- async function handlePacket(ctx, packet) {
231
- logger.debug({ received: JSON.stringify(packet, null, 2) });
232
- if (ctx.connectionState !== ConnectionState.connected) {
233
- if (packet.type === PacketType.connack) {
234
- handleConnack(packet, ctx);
235
- } else {
236
- throw new Error(
237
- `Received ${PacketNameByType[packet.type]} packet before connect`,
238
- );
239
- }
240
- } else {
241
- switch (packet.type) {
242
- case PacketType.pingres:
243
- break;
244
- case PacketType.publish:
245
- await handlePublish(ctx, packet);
246
- break;
247
- case PacketType.puback:
248
- await handlePuback(ctx, packet);
249
- break;
250
- case PacketType.pubrel:
251
- await handlePubrel(ctx, packet);
252
- break;
253
- case PacketType.pubrec:
254
- await handlePubrec(ctx, packet);
255
- break;
256
- case PacketType.pubcomp:
257
- await handlePubcomp(ctx, packet);
258
- break;
259
- case PacketType.suback:
260
- await handleSuback(ctx, packet);
261
- break;
262
- case PacketType.unsuback:
263
- handleUnsuback(ctx, packet);
264
- break;
265
- default:
266
- throw new Error(
267
- `Received unexpected ${
268
- PacketNameByType[packet.type]
269
- } packet after connect`,
270
- );
271
- }
272
- }
273
- }
274
-
275
- class Context {
276
- mqttConn;
277
- connectionState;
278
- pingTimer;
279
- unresolvedConnect;
280
- unresolvedPublish;
281
- unresolvedSubscribe;
282
- unresolvedUnSubscribe;
283
- store;
284
- incoming;
285
- constructor(store) {
286
- this.store = store;
287
- this.connectionState = ConnectionState.offline;
288
- this.incoming = new AsyncQueue();
289
- this.unresolvedPublish = /* @__PURE__ */ new Map();
290
- this.unresolvedSubscribe = /* @__PURE__ */ new Map();
291
- this.unresolvedUnSubscribe = /* @__PURE__ */ new Map();
292
- }
293
- async connect(packet) {
294
- this.connectionState = ConnectionState.connecting;
295
- await this.mqttConn?.send(packet);
296
- const keepAlive = packet.keepAlive || 0;
297
- if (keepAlive > 0) {
298
- this.pingTimer = new Timer(
299
- this.sendPing.bind(this),
300
- keepAlive * 1e3,
301
- true,
302
- );
303
- }
304
- }
305
- async disconnect() {
306
- if (this.connectionState !== ConnectionState.connected) {
307
- throw "Not connected";
308
- }
309
- if (this.mqttConn) {
310
- this.connectionState = ConnectionState.disconnecting;
311
- await this.mqttConn.send({ type: PacketType.disconnect });
312
- this.mqttConn.close();
313
- }
314
- }
315
- async send(packet) {
316
- logger.debug({ send: packet });
317
- if (
318
- this.connectionState === ConnectionState.connected &&
319
- !this.mqttConn?.isClosed
320
- ) {
321
- await this.mqttConn?.send(packet);
322
- this.pingTimer?.reset();
323
- return;
324
- }
325
- logger.debug("not connected");
326
- this.pingTimer?.clear();
327
- }
328
- sendPing() {
329
- this.send({ type: PacketType.pingreq });
330
- }
331
- async handleConnection(conn, connectPacket) {
332
- this.mqttConn = new MqttConn({ conn });
333
- if (this.mqttConn === void 0) {
334
- return true;
335
- }
336
- await this.connect(connectPacket);
337
- try {
338
- for await (const packet of this.mqttConn) {
339
- handlePacket(this, packet);
340
- }
341
- logger.debug("No more packets");
342
- } catch (err) {
343
- logger.debug(err);
344
- if (this.mqttConn.isClosed) {
345
- this.mqttConn.close();
346
- }
347
- }
348
- if (this.connectionState === ConnectionState.disconnecting) {
349
- return false;
350
- }
351
- return true;
352
- }
353
- close() {
354
- logger.debug("closing connection");
355
- this.connectionState = ConnectionState.disconnected;
356
- this.pingTimer?.clear();
357
- }
358
- receivePublish(packet) {
359
- this.incoming.push(packet);
360
- }
361
- publish(packet) {
362
- const qos = packet.qos || 0;
363
- if (qos === 0) {
364
- packet.id = 0;
365
- this.send(packet);
366
- return Promise.resolve();
367
- }
368
- packet.id = this.store.nextId();
369
- this.store.pendingOutgoing.set(packet.id, packet);
370
- const deferred = new Deferred();
371
- this.unresolvedPublish.set(packet.id, deferred);
372
- this.send(packet);
373
- return deferred.promise;
374
- }
375
- subscribe(packet) {
376
- packet.id = this.store.nextId();
377
- this.store.pendingOutgoing.set(packet.id, packet);
378
- const deferred = new Deferred();
379
- this.unresolvedSubscribe.set(packet.id, deferred);
380
- this.send(packet);
381
- return deferred.promise;
382
- }
383
- unsubscribe(packet) {
384
- packet.id = this.store.nextId();
385
- this.store.pendingOutgoing.set(packet.id, packet);
386
- const deferred = new Deferred();
387
- this.unresolvedUnSubscribe.set(packet.id, deferred);
388
- this.send(packet);
389
- return deferred.promise;
390
- }
391
- receivePuback(id) {
392
- const unresolvedMap = this.unresolvedPublish;
393
- if (unresolvedMap.has(id)) {
394
- const deferred = unresolvedMap.get(id);
395
- unresolvedMap.delete(id);
396
- deferred?.resolve();
397
- return true;
398
- }
399
- return false;
400
- }
401
- receiveSuback(id, returnCodes) {
402
- const unresolvedMap = this.unresolvedSubscribe;
403
- if (unresolvedMap.has(id)) {
404
- const deferred = unresolvedMap.get(id);
405
- unresolvedMap.delete(id);
406
- deferred?.resolve(returnCodes);
407
- return true;
408
- }
409
- return false;
410
- }
411
- receiveUnsuback(id) {
412
- const unresolvedMap = this.unresolvedUnSubscribe;
413
- if (unresolvedMap.has(id)) {
414
- const deferred = unresolvedMap.get(id);
415
- unresolvedMap.delete(id);
416
- deferred?.resolve();
417
- return true;
418
- }
419
- return false;
420
- }
421
- }
422
-
423
- function generateClientId(prefix) {
424
- return `${prefix}-${Math.random().toString().slice(-10)}`;
425
- }
426
- function backOffSleep(random, attempt) {
427
- const factor = 1.5;
428
- const min = 1e3;
429
- const max = 5e3;
430
- const randomness = 1 + (Math.random());
431
- const delay = Math.floor(
432
- Math.min(randomness * min * factor ** attempt, max),
433
- );
434
- logger.debug({ delay });
435
- return new Promise((resolve) => setTimeout(resolve, delay));
436
- }
437
- const DEFAULT_URL = "mqtt://localhost:1883/";
438
- const DEFAULT_KEEPALIVE = 60;
439
- const DEFAULT_RETRIES = 3;
440
- const CLIENTID_PREFIX = "opifex";
441
- class Client {
442
- clientIdPrefix = CLIENTID_PREFIX;
443
- numberOfRetries = DEFAULT_RETRIES;
444
- url = new URL(DEFAULT_URL);
445
- keepAlive = DEFAULT_KEEPALIVE;
446
- autoReconnect = true;
447
- caCerts;
448
- clientId;
449
- ctx = new Context(new MemoryStore());
450
- connectPacket;
451
- constructor() {
452
- this.clientId = generateClientId(this.clientIdPrefix);
453
- this.numberOfRetries = DEFAULT_RETRIES;
454
- }
455
- createConn(protocol, _hostname, _port, _caCerts) {
456
- throw `Unsupported protocol: ${protocol}`;
457
- }
458
- async doConnect() {
459
- if (!this.connectPacket) {
460
- return;
461
- }
462
- let isReconnect = false;
463
- let attempt = 1;
464
- let lastMessage = "";
465
- let tryConnect = true;
466
- while (tryConnect) {
467
- logger.debug(`${isReconnect ? "re" : ""}connecting`);
468
- try {
469
- const conn = await this.createConn(
470
- this.url.protocol,
471
- this.url.hostname,
472
- Number(this.url.port) ?? void 0,
473
- this.caCerts,
474
- );
475
- tryConnect =
476
- await this.ctx.handleConnection(conn, this.connectPacket) &&
477
- this.autoReconnect;
478
- logger.debug({ tryConnect });
479
- isReconnect = true;
480
- this.connectPacket.clean = false;
481
- this.ctx.close();
482
- } catch (err) {
483
- if (err instanceof Error) {
484
- lastMessage = `Connection failed: ${err.message}`;
485
- }
486
- logger.debug(lastMessage);
487
- if (!isReconnect && attempt > this.numberOfRetries) {
488
- tryConnect = false;
489
- } else {
490
- await backOffSleep(true, attempt++);
491
- }
492
- }
493
- }
494
- if (isReconnect === false) {
495
- this.ctx.unresolvedConnect?.reject(Error(lastMessage));
496
- }
497
- }
498
- connect(params = {}) {
499
- this.url = params?.url || this.url;
500
- this.numberOfRetries = params.numberOfRetries || this.numberOfRetries;
501
- this.caCerts = params?.caCerts;
502
- const options = Object.assign(
503
- {
504
- keepAlive: this.keepAlive,
505
- clientId: this.clientId,
506
- },
507
- params?.options,
508
- );
509
- this.connectPacket = {
510
- type: PacketType.connect,
511
- ...options,
512
- };
513
- const deferred = new Deferred();
514
- this.ctx.unresolvedConnect = deferred;
515
- this.doConnect();
516
- return deferred.promise;
517
- }
518
- async disconnect() {
519
- await this.ctx.disconnect();
520
- }
521
- async publish(params) {
522
- const packet = {
523
- type: PacketType.publish,
524
- ...params,
525
- };
526
- await this.ctx.send(packet);
527
- }
528
- async subscribe(params) {
529
- const packet = {
530
- type: PacketType.subscribe,
531
- id: this.ctx.store.nextId(),
532
- ...params,
533
- };
534
- await this.ctx.send(packet);
535
- }
536
- async *messages() {
537
- yield* this.ctx.incoming;
538
- }
539
- }
540
-
541
- export { Client as C, DEFAULT_URL as D };