@seriousme/opifex 1.2.0 → 1.3.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 (106) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +72 -4
  3. package/bin/conformanceServer.ts +1 -1
  4. package/bin/mqtt.ts +5 -2
  5. package/client/client.ts +3 -3
  6. package/client/context.ts +2 -0
  7. package/client/mod.ts +1 -2
  8. package/deno/client.ts +4 -3
  9. package/deno/server.ts +3 -2
  10. package/deno.json +4 -2
  11. package/deno.lock +0 -16
  12. package/dev_utils/dummyConn.ts +4 -4
  13. package/dist/bin/conformanceServer.js +52 -0
  14. package/dist/bin/demoServer.js +29 -57
  15. package/dist/bin/mqtt.js +148 -187
  16. package/dist/client/ConnectionState.js +7 -0
  17. package/dist/client/client.js +125 -0
  18. package/dist/client/context.js +148 -0
  19. package/dist/client/deps.js +5 -0
  20. package/dist/client/handlers/handleConnack.js +18 -0
  21. package/dist/client/handlers/handlePacket.js +50 -0
  22. package/dist/client/handlers/handlePuback.js +6 -0
  23. package/dist/client/handlers/handlePubcomp.js +9 -0
  24. package/dist/client/handlers/handlePublish.js +28 -0
  25. package/dist/client/handlers/handlePubrec.js +17 -0
  26. package/dist/client/handlers/handlePubrel.js +19 -0
  27. package/dist/client/handlers/handleSuback.js +9 -0
  28. package/dist/client/handlers/handleUnsuback.js +7 -0
  29. package/dist/client/mod.js +5 -3
  30. package/dist/client/store/deps.js +1 -0
  31. package/dist/client/store/memoryStore.js +36 -0
  32. package/dist/client/store/store.js +1 -0
  33. package/dist/mqttConn/mqttConn.js +104 -0
  34. package/dist/mqttPacket/AuthenticationResult.js +12 -0
  35. package/dist/mqttPacket/BitMask.js +10 -0
  36. package/dist/mqttPacket/PacketType.js +18 -0
  37. package/dist/mqttPacket/connack.js +27 -0
  38. package/dist/mqttPacket/connect.js +115 -0
  39. package/dist/mqttPacket/decoder.js +89 -0
  40. package/dist/mqttPacket/disconnect.js +14 -0
  41. package/dist/mqttPacket/encoder.js +56 -0
  42. package/dist/mqttPacket/length.js +43 -0
  43. package/dist/mqttPacket/mod.js +71 -0
  44. package/dist/mqttPacket/pingreq.js +15 -0
  45. package/dist/mqttPacket/pingres.js +14 -0
  46. package/dist/mqttPacket/puback.js +20 -0
  47. package/dist/mqttPacket/pubcomp.js +20 -0
  48. package/dist/mqttPacket/publish.js +51 -0
  49. package/dist/mqttPacket/pubrec.js +20 -0
  50. package/dist/mqttPacket/pubrel.js +20 -0
  51. package/dist/mqttPacket/suback.js +29 -0
  52. package/dist/mqttPacket/subscribe.js +52 -0
  53. package/dist/mqttPacket/types.js +1 -0
  54. package/dist/mqttPacket/unsuback.js +20 -0
  55. package/dist/mqttPacket/unsubscribe.js +36 -0
  56. package/dist/mqttPacket/validators.js +15 -0
  57. package/dist/node/client.js +65 -0
  58. package/dist/node/server.js +22 -0
  59. package/dist/node/wrapNodeSocket.js +47 -0
  60. package/dist/persistence/deps.js +3 -0
  61. package/dist/persistence/memory/memoryPersistence.js +117 -0
  62. package/dist/persistence/persistence.js +1 -0
  63. package/dist/persistence/store.js +1 -0
  64. package/dist/server/context.js +100 -0
  65. package/dist/server/deps.js +5 -0
  66. package/dist/server/handlers/handleConnect.js +46 -0
  67. package/dist/server/handlers/handleDisconnect.js +4 -0
  68. package/dist/server/handlers/handlePacket.js +58 -0
  69. package/dist/server/handlers/handlePingreq.js +6 -0
  70. package/dist/server/handlers/handlePuback.js +8 -0
  71. package/dist/server/handlers/handlePubcomp.js +8 -0
  72. package/dist/server/handlers/handlePublish.js +40 -0
  73. package/dist/server/handlers/handlePubrec.js +15 -0
  74. package/dist/server/handlers/handlePubrel.js +18 -0
  75. package/dist/server/handlers/handleSubscribe.js +33 -0
  76. package/dist/server/handlers/handleUnsubscribe.js +12 -0
  77. package/dist/server/mod.js +7 -589
  78. package/dist/server/server.js +47 -0
  79. package/dist/socket/socket.js +36 -0
  80. package/dist/timer/timer.js +40 -0
  81. package/dist/trie/trie.js +95 -0
  82. package/dist/tsconfig.tsbuildinfo +1 -0
  83. package/dist/utils/args.js +61 -0
  84. package/dist/utils/assert.js +19 -0
  85. package/dist/utils/asyncQueue.js +56 -0
  86. package/dist/utils/deferred.js +11 -0
  87. package/dist/utils/logger.js +29 -0
  88. package/dist/utils/mod.js +7 -0
  89. package/dist/utils/nextTick.js +3 -0
  90. package/dist/utils/platform.js +4 -0
  91. package/mqttConn/mqttConn.ts +4 -1
  92. package/node/README.md +3 -1
  93. package/node/client.ts +30 -6
  94. package/node/server.ts +2 -0
  95. package/node/wrapNodeSocket.ts +4 -4
  96. package/package.json +16 -3
  97. package/server/mod.ts +1 -2
  98. package/socket/socket.ts +3 -2
  99. package/timer/timer.ts +1 -1
  100. package/tsconfig.json +3 -0
  101. package/.vscode/extensions.json +0 -10
  102. package/.vscode/settings.json +0 -12
  103. package/deno/wrapDenoConn.ts +0 -12
  104. package/dist/client-DqLTBVC2.js +0 -541
  105. package/dist/timer-DDWVNsyG.js +0 -924
  106. package/dist/wrapNodeSocket-BAJm059T.js +0 -115
@@ -1,589 +1,7 @@
1
- import {
2
- A as AuthenticationResult,
3
- a as assert,
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 Trie {
13
- #value;
14
- #children;
15
- separator = "/";
16
- wildcardOne = "+";
17
- wildcardSubtree = "#";
18
- reservedPrefix = "$";
19
- looseCompare;
20
- constructor(looseCompare = false) {
21
- this.#value = [];
22
- this.#children = /* @__PURE__ */ new Map();
23
- this.looseCompare = looseCompare;
24
- }
25
- matchChild(child, parts) {
26
- const childNode = this.#children.get(child);
27
- return childNode ? childNode._match(parts) : [];
28
- }
29
- match(key) {
30
- const parts = key.split(this.separator);
31
- if (parts.length > 0 && parts[0].charAt(0) === this.reservedPrefix) {
32
- return this._matchPrefix(parts);
33
- }
34
- return this._match(parts);
35
- }
36
- _matchPrefix(parts) {
37
- if (parts.length === 0) {
38
- return this.#value ? this.#value : [];
39
- }
40
- const [first, ...rest] = parts;
41
- return this.matchChild(first, rest);
42
- }
43
- _match(parts) {
44
- if (parts.length === 0) {
45
- return this.#value ? this.#value : [];
46
- }
47
- const [first, ...rest] = parts;
48
- const exact = this.matchChild(first, rest);
49
- const single = this.matchChild(this.wildcardOne, rest);
50
- const subtree = this.matchChild(this.wildcardSubtree, []);
51
- const results = exact.concat(single, subtree);
52
- return results;
53
- }
54
- add(key, value) {
55
- return this._add(key.split(this.separator), value);
56
- }
57
- _add(parts, value) {
58
- if (parts.length === 0) {
59
- this.#value = this.#value.concat(value);
60
- return;
61
- }
62
- const [first, ...rest] = parts;
63
- const child = this.#children.get(first);
64
- if (child instanceof Trie) {
65
- child._add(rest, value);
66
- } else {
67
- const node = new Trie(this.looseCompare);
68
- this.#children.set(first, node);
69
- node._add(rest, value);
70
- }
71
- return;
72
- }
73
- remove(key, value) {
74
- return this._remove(key.split(this.separator), value);
75
- }
76
- _remove(parts, value) {
77
- if (parts.length === 0) {
78
- const arr = this.#value || [];
79
- this.#value = arr.filter(this.filter(value));
80
- return;
81
- }
82
- const [first, ...rest] = parts;
83
- const node = this.#children.get(first);
84
- if (node) {
85
- node._remove(rest, value);
86
- if (node.#value.length === 0 && this.#children.size === 0) {
87
- this.#children.delete(first);
88
- }
89
- }
90
- }
91
- filter(value) {
92
- if (this.looseCompare && typeof value === "object") {
93
- return (item) => {
94
- for (const key in value) {
95
- if (value[key] !== item[key]) {
96
- return true;
97
- }
98
- }
99
- return false;
100
- };
101
- }
102
- return (item) => item !== value;
103
- }
104
- }
105
-
106
- const maxPacketId = 65535;
107
- class MemoryStore {
108
- clientId;
109
- packetId;
110
- pendingIncoming;
111
- pendingOutgoing;
112
- pendingAckOutgoing;
113
- subscriptions;
114
- constructor(clientId) {
115
- this.packetId = 0;
116
- this.pendingIncoming = /* @__PURE__ */ new Map();
117
- this.pendingOutgoing = /* @__PURE__ */ new Map();
118
- this.pendingAckOutgoing = /* @__PURE__ */ new Set();
119
- this.subscriptions = /* @__PURE__ */ new Map();
120
- this.clientId = clientId;
121
- }
122
- nextId() {
123
- const currentId = this.packetId;
124
- do {
125
- this.packetId++;
126
- if (this.packetId > maxPacketId) {
127
- this.packetId = 0;
128
- }
129
- } while (
130
- (this.pendingOutgoing.has(this.packetId) ||
131
- this.pendingAckOutgoing.has(this.packetId)) &&
132
- this.packetId !== currentId
133
- );
134
- assert(this.packetId !== currentId, "No unused packetId available");
135
- return this.packetId;
136
- }
137
- }
138
- class MemoryPersistence {
139
- clientList;
140
- retained;
141
- trie;
142
- constructor() {
143
- this.clientList = /* @__PURE__ */ new Map();
144
- this.retained = /* @__PURE__ */ new Map();
145
- this.trie = new Trie(true);
146
- }
147
- registerClient(clientId, handler, clean) {
148
- const existingClient = this.clientList.get(clientId);
149
- const store = !clean && existingClient
150
- ? existingClient.store
151
- : new MemoryStore(clientId);
152
- this.clientList.set(clientId, { store, handler });
153
- return store;
154
- }
155
- deregisterClient(clientId) {
156
- const client = this.clientList.get(clientId);
157
- if (client) {
158
- this.unsubscribeAll(client.store);
159
- this.clientList.delete(clientId);
160
- }
161
- }
162
- subscribe(store, topicFilter, qos) {
163
- const clientId = store.clientId;
164
- if (!store.subscriptions.has(topicFilter)) {
165
- store.subscriptions.set(topicFilter, qos);
166
- this.trie.add(topicFilter, { clientId, qos });
167
- }
168
- }
169
- unsubscribe(store, topicFilter) {
170
- const clientId = store.clientId;
171
- const qos = store.subscriptions.get(topicFilter);
172
- if (qos) {
173
- store.subscriptions.delete(topicFilter);
174
- this.trie.remove(topicFilter, { clientId, qos });
175
- }
176
- }
177
- unsubscribeAll(store) {
178
- for (const [topicFilter, _qos] of store.subscriptions) {
179
- this.unsubscribe(store, topicFilter);
180
- }
181
- }
182
- publish(topic, packet) {
183
- if (packet.retain) {
184
- this.retained.set(packet.topic, packet);
185
- if (packet.payload === void 0) {
186
- this.retained.delete(packet.topic);
187
- }
188
- }
189
- const clients = /* @__PURE__ */ new Map();
190
- for (const { clientId, qos } of this.trie.match(topic)) {
191
- const prevQos = clients.get(clientId);
192
- if (!prevQos || prevQos < qos) {
193
- clients.set(clientId, qos);
194
- }
195
- }
196
- for (const [clientId, qos] of clients) {
197
- const newPacket = Object.assign({}, packet);
198
- newPacket.retain = false;
199
- newPacket.qos = qos;
200
- const client = this.clientList.get(clientId);
201
- client?.handler(packet);
202
- }
203
- }
204
- handleRetained(clientId) {
205
- const retainedTrie = new Trie();
206
- const client = this.clientList.get(clientId);
207
- const store = client?.store;
208
- if (store) {
209
- for (const [topicFilter, _qos] of store.subscriptions) {
210
- retainedTrie.add(topicFilter, clientId);
211
- }
212
- for (const [topic, packet] of this.retained) {
213
- if (retainedTrie.match(topic).length > 0) {
214
- client?.handler(packet);
215
- }
216
- }
217
- }
218
- }
219
- }
220
-
221
- const SysPrefix = "$";
222
- const utf8Encoder = new TextEncoder();
223
- class Context {
224
- connected;
225
- conn;
226
- mqttConn;
227
- persistence;
228
- handlers;
229
- static clientList = /* @__PURE__ */ new Map();
230
- store;
231
- will;
232
- timer;
233
- constructor(persistence, conn, handlers) {
234
- this.persistence = persistence;
235
- this.connected = false;
236
- this.conn = conn;
237
- this.mqttConn = new MqttConn({ conn });
238
- this.handlers = handlers;
239
- }
240
- async send(packet) {
241
- logger.debug("Sending", PacketNameByType[packet.type]);
242
- logger.debug(JSON.stringify(packet, null, 2));
243
- await this.mqttConn.send(packet);
244
- }
245
- connect(clientId, clean) {
246
- logger.debug("Connecting", clientId);
247
- const existingSession = Context.clientList.get(clientId);
248
- if (existingSession) {
249
- existingSession.close(false);
250
- }
251
- this.store = this.persistence.registerClient(
252
- clientId,
253
- this.doPublish.bind(this),
254
- clean,
255
- );
256
- this.connected = true;
257
- this.broadcast("$SYS/connect/clients", clientId);
258
- logger.debug("Connected", clientId);
259
- }
260
- doPublish(packet) {
261
- const qos = packet.qos || 0;
262
- if (qos === 0) {
263
- packet.id = 0;
264
- this.send(packet);
265
- return;
266
- }
267
- if (this.store) {
268
- packet.id = this.store.nextId();
269
- this.store.pendingOutgoing.set(packet.id, packet);
270
- this.send(packet);
271
- }
272
- }
273
- clean(clientId) {
274
- this.persistence.deregisterClient(clientId);
275
- }
276
- close(executewill = true) {
277
- if (this.connected) {
278
- logger.debug(
279
- `Closing ${this.store?.clientId} while mqttConn is ${
280
- this.mqttConn.isClosed ? "" : "not "
281
- }closed`,
282
- );
283
- this.connected = false;
284
- if (typeof this.timer === "object") {
285
- this.timer.clear();
286
- }
287
- if (this.store) {
288
- this.broadcast("$SYS/disconnect/clients", this.store.clientId);
289
- }
290
- if (executewill) {
291
- this.handleWill();
292
- }
293
- }
294
- if (!this.mqttConn.isClosed) {
295
- this.mqttConn.close();
296
- }
297
- }
298
- handleWill() {
299
- if (this.will) {
300
- if (
301
- !this.will.topic.startsWith(SysPrefix) &&
302
- this.handlers.isAuthorizedToPublish &&
303
- this.handlers.isAuthorizedToPublish(this, this.will.topic)
304
- ) {
305
- this.persistence.publish(this.will.topic, this.will);
306
- }
307
- }
308
- }
309
- broadcast(topic, payload, retain = false) {
310
- const packet = {
311
- type: PacketType.publish,
312
- topic,
313
- retain,
314
- payload: utf8Encoder.encode(payload),
315
- };
316
- if (packet.retain === true) {
317
- this.persistence.retained.set(packet.topic, packet);
318
- }
319
- this.persistence.publish(packet.topic, packet);
320
- }
321
- }
322
-
323
- function isAuthenticated(ctx, packet) {
324
- if (ctx.handlers.isAuthenticated) {
325
- return ctx.handlers.isAuthenticated(
326
- ctx,
327
- packet.clientId || "",
328
- packet.username || "",
329
- packet.password || new Uint8Array(0),
330
- );
331
- }
332
- return AuthenticationResult.ok;
333
- }
334
- function validateConnect(ctx, packet) {
335
- if (packet.protocolLevel !== 4) {
336
- return AuthenticationResult.unacceptableProtocol;
337
- }
338
- return isAuthenticated(ctx, packet);
339
- }
340
- function handleConnect(ctx, packet) {
341
- const clientId = packet.clientId || `Opifex-${crypto.randomUUID()}`;
342
- const returnCode = validateConnect(ctx, packet);
343
- if (returnCode === AuthenticationResult.ok) {
344
- if (packet.will) {
345
- ctx.will = {
346
- type: PacketType.publish,
347
- qos: packet.will.qos,
348
- retain: packet.will.retain,
349
- topic: packet.will.topic,
350
- payload: packet.will.payload,
351
- };
352
- }
353
- ctx.connect(clientId, packet.clean || false);
354
- const keepAlive = packet.keepAlive || 0;
355
- if (keepAlive > 0) {
356
- logger.debug(`Setting keepalive to ${keepAlive * 1500} ms`);
357
- ctx.timer = new Timer(() => {
358
- ctx.close();
359
- }, Math.floor(keepAlive * 1500));
360
- }
361
- }
362
- const sessionPresent = false;
363
- ctx.send({
364
- type: PacketType.connack,
365
- sessionPresent,
366
- returnCode,
367
- });
368
- if (returnCode !== AuthenticationResult.ok) {
369
- ctx.close();
370
- }
371
- }
372
-
373
- async function handlePingreq(ctx) {
374
- await ctx.send({
375
- type: PacketType.pingres,
376
- });
377
- }
378
-
379
- function authorizedToPublish(ctx, topic) {
380
- if (topic.startsWith(SysPrefix)) {
381
- return false;
382
- }
383
- if (ctx.handlers.isAuthorizedToPublish) {
384
- return ctx.handlers.isAuthorizedToPublish(ctx, topic);
385
- }
386
- return true;
387
- }
388
- async function handlePublish(ctx, packet) {
389
- if (!authorizedToPublish(ctx, packet.topic)) {
390
- return;
391
- }
392
- const qos = packet.qos || 0;
393
- if (qos === 0) {
394
- ctx.persistence.publish(packet.topic, packet);
395
- return;
396
- }
397
- if (packet.id !== void 0) {
398
- if (qos === 1) {
399
- ctx.persistence.publish(packet.topic, packet);
400
- await ctx.send({
401
- type: PacketType.puback,
402
- id: packet.id,
403
- });
404
- return;
405
- }
406
- if (ctx.store) {
407
- ctx.store.pendingIncoming.set(packet.id, packet);
408
- await ctx.send({
409
- type: PacketType.pubrec,
410
- id: packet.id,
411
- });
412
- }
413
- }
414
- }
415
-
416
- function handlePuback(ctx, packet) {
417
- const id = packet.id;
418
- if (ctx.store?.pendingOutgoing.has(id)) {
419
- ctx.store.pendingOutgoing.delete(id);
420
- }
421
- }
422
-
423
- async function handlePubrec(ctx, packet) {
424
- const id = packet.id;
425
- if (ctx.store?.pendingOutgoing.has(id)) {
426
- ctx.store.pendingOutgoing.delete(id);
427
- ctx.store.pendingAckOutgoing.add(id);
428
- await ctx.send({
429
- type: PacketType.pubrel,
430
- id,
431
- });
432
- }
433
- }
434
-
435
- async function handlePubrel(ctx, packet) {
436
- const id = packet.id;
437
- if (ctx.store?.pendingIncoming.has(id)) {
438
- const storedPacket = ctx.store.pendingIncoming.get(id);
439
- if (storedPacket) {
440
- ctx.persistence.publish(storedPacket.topic, storedPacket);
441
- ctx.store.pendingIncoming.delete(id);
442
- await ctx.send({
443
- type: PacketType.pubcomp,
444
- id,
445
- });
446
- }
447
- }
448
- }
449
-
450
- function handlePubcomp(ctx, packet) {
451
- const id = packet.id;
452
- if (ctx.store?.pendingAckOutgoing.has(id)) {
453
- ctx.store.pendingAckOutgoing.delete(id);
454
- }
455
- }
456
-
457
- const SubscriptionFailure = 128;
458
- function authorizedToSubscribe(ctx, topicFilter) {
459
- if (ctx.handlers.isAuthorizedToSubscribe) {
460
- return ctx.handlers.isAuthorizedToSubscribe(ctx, topicFilter);
461
- }
462
- return true;
463
- }
464
- async function handleSubscribe(ctx, packet) {
465
- const returnCodes = packet.subscriptions.map((sub) => {
466
- if (ctx.store) {
467
- if (!authorizedToSubscribe(ctx, sub.topicFilter)) {
468
- return SubscriptionFailure;
469
- }
470
- ctx.persistence.subscribe(ctx.store, sub.topicFilter, sub.qos);
471
- return sub.qos;
472
- }
473
- return SubscriptionFailure;
474
- });
475
- await ctx.send({
476
- type: PacketType.suback,
477
- id: packet.id,
478
- returnCodes,
479
- });
480
- if (ctx.store) {
481
- ctx.persistence.handleRetained(ctx.store.clientId);
482
- }
483
- }
484
-
485
- async function handleUnsubscribe(ctx, packet) {
486
- for (const topic of packet.topicFilters) {
487
- if (ctx.store) {
488
- ctx.persistence.unsubscribe(ctx.store, topic);
489
- }
490
- }
491
- await ctx.send({
492
- type: PacketType.unsuback,
493
- id: packet.id,
494
- });
495
- }
496
-
497
- function handleDisconnect(ctx) {
498
- ctx.will = void 0;
499
- ctx.close();
500
- }
501
-
502
- async function handlePacket(ctx, packet) {
503
- logger.debug("handling", PacketNameByType[packet.type]);
504
- logger.debug(JSON.stringify(packet, null, 2));
505
- if (!ctx.connected) {
506
- if (packet.type === PacketType.connect) {
507
- handleConnect(ctx, packet);
508
- } else {
509
- throw new Error(
510
- `Received ${PacketNameByType[packet.type]} packet before connect`,
511
- );
512
- }
513
- } else {
514
- switch (packet.type) {
515
- case PacketType.pingreq:
516
- await handlePingreq(ctx);
517
- break;
518
- case PacketType.publish:
519
- await handlePublish(ctx, packet);
520
- break;
521
- case PacketType.puback:
522
- handlePuback(ctx, packet);
523
- break;
524
- case PacketType.pubrel:
525
- await handlePubrel(ctx, packet);
526
- break;
527
- case PacketType.pubrec:
528
- await handlePubrec(ctx, packet);
529
- break;
530
- case PacketType.pubcomp:
531
- handlePubcomp(ctx, packet);
532
- break;
533
- case PacketType.subscribe:
534
- await handleSubscribe(ctx, packet);
535
- break;
536
- case PacketType.unsubscribe:
537
- await handleUnsubscribe(ctx, packet);
538
- break;
539
- case PacketType.disconnect:
540
- handleDisconnect(ctx);
541
- break;
542
- default:
543
- throw new Error(
544
- `Received unexpected ${
545
- PacketNameByType[packet.type]
546
- } packet after connect`,
547
- );
548
- }
549
- ctx.timer?.reset();
550
- }
551
- }
552
-
553
- const defaultIsAuthenticated = (_ctx, _clientId, _username, _password) =>
554
- AuthenticationResult.ok;
555
- const defaultIsAuthorized = (_ctx, _topic) => true;
556
- class MqttServer {
557
- handlers;
558
- persistence;
559
- constructor({
560
- persistence,
561
- handlers,
562
- }) {
563
- this.persistence = persistence || new MemoryPersistence();
564
- this.handlers = {
565
- isAuthenticated: handlers?.isAuthenticated || defaultIsAuthenticated,
566
- isAuthorizedToPublish: handlers?.isAuthorizedToPublish ||
567
- defaultIsAuthorized,
568
- isAuthorizedToSubscribe: handlers?.isAuthorizedToSubscribe ||
569
- defaultIsAuthorized,
570
- };
571
- }
572
- async serve(conn) {
573
- const ctx = new Context(this.persistence, conn, this.handlers);
574
- if (conn.remoteAddr?.transport === "tcp") {
575
- logger.debug(`socket connected from ${conn.remoteAddr.hostname}`);
576
- }
577
- try {
578
- for await (const packet of ctx.mqttConn) {
579
- handlePacket(ctx, packet);
580
- }
581
- } catch (err) {
582
- logger.debug(`Error while serving:${err}`);
583
- } finally {
584
- ctx.close();
585
- }
586
- }
587
- }
588
-
589
- export { AuthenticationResult, Context, MqttServer };
1
+ /**
2
+ * This module provides an MQTT Server interface see the /bin folder for examples
3
+ * @module
4
+ */
5
+ export { AuthenticationResult } from "./deps.js";
6
+ export { MqttServer } from "./server.js";
7
+ export { Context } from "./context.js";
@@ -0,0 +1,47 @@
1
+ import { Context } from "./context.js";
2
+ import { AuthenticationResult, logger, MemoryPersistence, } from "./deps.js";
3
+ import { handlePacket } from "./handlers/handlePacket.js";
4
+ const defaultIsAuthenticated = (_ctx, _clientId, _username, _password) => AuthenticationResult.ok;
5
+ const defaultIsAuthorized = (_ctx, _topic) => true;
6
+ /** The MqttServer class provides a MQTT server with configurable persistence and
7
+ * authentication/authorization handlers.
8
+ *
9
+ * The default handlers are:
10
+ * - isAuthenticated: always returns ok
11
+ * - isAuthorizedToPublish: always returns true
12
+ * - isAuthorizedToSubscribe: always returns true
13
+ *
14
+ * To customize the handlers, pass in a Handlers object.
15
+ * To customize the persistence, pass in a Persistence object.
16
+ */
17
+ export class MqttServer {
18
+ handlers;
19
+ persistence;
20
+ constructor({ persistence, handlers, }) {
21
+ this.persistence = persistence || new MemoryPersistence();
22
+ this.handlers = {
23
+ isAuthenticated: handlers?.isAuthenticated || defaultIsAuthenticated,
24
+ isAuthorizedToPublish: handlers?.isAuthorizedToPublish ||
25
+ defaultIsAuthorized,
26
+ isAuthorizedToSubscribe: handlers?.isAuthorizedToSubscribe ||
27
+ defaultIsAuthorized,
28
+ };
29
+ }
30
+ async serve(conn) {
31
+ const ctx = new Context(this.persistence, conn, this.handlers);
32
+ if (conn.remoteAddr?.transport === "tcp") {
33
+ logger.debug(`socket connected from ${conn.remoteAddr.hostname}`);
34
+ }
35
+ try {
36
+ for await (const packet of ctx.mqttConn) {
37
+ handlePacket(ctx, packet);
38
+ }
39
+ }
40
+ catch (err) {
41
+ logger.debug(`Error while serving:${err}`);
42
+ }
43
+ finally {
44
+ ctx.close();
45
+ }
46
+ }
47
+ }
@@ -0,0 +1,36 @@
1
+ export class Conn {
2
+ reader;
3
+ writer;
4
+ closed;
5
+ remoteAddr;
6
+ closer;
7
+ constructor(sockConn) {
8
+ this.closed = false;
9
+ this.reader = sockConn.readable.getReader({ mode: "byob" });
10
+ this.writer = sockConn.writable.getWriter();
11
+ // this.writer.closed.then(() => this.closed = true);
12
+ this.closer = sockConn.close.bind(sockConn);
13
+ this.remoteAddr = sockConn.remoteAddr;
14
+ }
15
+ async read(buff) {
16
+ const buff2 = new Uint8Array(buff.length);
17
+ const result = await this.reader.read(buff2);
18
+ if (!result.done) {
19
+ buff.set(result.value);
20
+ }
21
+ return result.value?.byteLength || null;
22
+ }
23
+ write(data) {
24
+ if (this.closed) {
25
+ return Promise.reject();
26
+ }
27
+ this.writer.write(data);
28
+ return new Promise((resolve) => resolve(data.length));
29
+ }
30
+ close() {
31
+ if (!this.closed) {
32
+ this.closed = true;
33
+ this.closer();
34
+ }
35
+ }
36
+ }
@@ -0,0 +1,40 @@
1
+ export class Timer {
2
+ delay; // delay in microseconds
3
+ // deno-lint-ignore ban-types
4
+ action; // function to perform when timer expires
5
+ timer;
6
+ end = 0;
7
+ running = false;
8
+ // deno-lint-ignore ban-types
9
+ constructor(action, delay, wait = false) {
10
+ this.delay = delay;
11
+ this.action = action;
12
+ if (!wait) {
13
+ this.reset();
14
+ }
15
+ }
16
+ startTimer(delay) {
17
+ this.running = true;
18
+ this.timer = setTimeout(() => this.ring(), delay);
19
+ }
20
+ ring() {
21
+ const timeLeft = this.end - Date.now();
22
+ // check if delay has passed, else execute action
23
+ if (timeLeft > 0) {
24
+ this.startTimer(timeLeft);
25
+ return;
26
+ }
27
+ this.running = false;
28
+ this.action();
29
+ }
30
+ reset() {
31
+ this.end = Date.now() + this.delay;
32
+ if (!this.running) {
33
+ this.startTimer(this.delay);
34
+ }
35
+ }
36
+ clear() {
37
+ clearTimeout(this.timer);
38
+ this.running = false;
39
+ }
40
+ }