@seriousme/opifex 1.6.3 → 1.7.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 (91) hide show
  1. package/package.json +12 -2
  2. package/.github/dependabot.yml +0 -7
  3. package/.github/workflows/closeStale.yml +0 -15
  4. package/.github/workflows/codeql-analysis.yml +0 -71
  5. package/.github/workflows/deno-ci.yml +0 -16
  6. package/.github/workflows/nodejs-ci.yml +0 -23
  7. package/.github/workflows/publish.yml +0 -16
  8. package/.github/workflows/update-license-year.sh +0 -14
  9. package/.github/workflows/update-license-year.yml +0 -13
  10. package/CHANGELOG.md +0 -80
  11. package/deno.json +0 -16
  12. package/deno.lock +0 -34
  13. package/dist/bin/conformanceServer.js +0 -97
  14. package/dist/bin/demoServer.js +0 -96
  15. package/dist/bin/mqtt.js +0 -256
  16. package/dist/client/ConnectionState.js +0 -16
  17. package/dist/client/client.js +0 -187
  18. package/dist/client/context.js +0 -152
  19. package/dist/client/deps.js +0 -5
  20. package/dist/client/handlers/handleConnack.js +0 -24
  21. package/dist/client/handlers/handlePacket.js +0 -57
  22. package/dist/client/handlers/handlePuback.js +0 -13
  23. package/dist/client/handlers/handlePubcomp.js +0 -16
  24. package/dist/client/handlers/handlePublish.js +0 -33
  25. package/dist/client/handlers/handlePubrec.js +0 -26
  26. package/dist/client/handlers/handlePubrel.js +0 -31
  27. package/dist/client/handlers/handleSuback.js +0 -14
  28. package/dist/client/handlers/handleUnsuback.js +0 -15
  29. package/dist/client/mod.js +0 -5
  30. package/dist/client/store/deps.js +0 -1
  31. package/dist/client/store/memoryStore.js +0 -58
  32. package/dist/client/store/store.js +0 -4
  33. package/dist/mqttConn/mqttConn.js +0 -104
  34. package/dist/mqttPacket/AuthenticationResult.js +0 -16
  35. package/dist/mqttPacket/BitMask.js +0 -10
  36. package/dist/mqttPacket/PacketType.js +0 -18
  37. package/dist/mqttPacket/connack.js +0 -27
  38. package/dist/mqttPacket/connect.js +0 -115
  39. package/dist/mqttPacket/decoder.js +0 -89
  40. package/dist/mqttPacket/disconnect.js +0 -14
  41. package/dist/mqttPacket/encoder.js +0 -56
  42. package/dist/mqttPacket/length.js +0 -43
  43. package/dist/mqttPacket/mod.js +0 -76
  44. package/dist/mqttPacket/pingreq.js +0 -15
  45. package/dist/mqttPacket/pingres.js +0 -14
  46. package/dist/mqttPacket/puback.js +0 -20
  47. package/dist/mqttPacket/pubcomp.js +0 -20
  48. package/dist/mqttPacket/publish.js +0 -51
  49. package/dist/mqttPacket/pubrec.js +0 -20
  50. package/dist/mqttPacket/pubrel.js +0 -20
  51. package/dist/mqttPacket/suback.js +0 -29
  52. package/dist/mqttPacket/subscribe.js +0 -52
  53. package/dist/mqttPacket/types.js +0 -1
  54. package/dist/mqttPacket/unsuback.js +0 -20
  55. package/dist/mqttPacket/unsubscribe.js +0 -36
  56. package/dist/mqttPacket/validators.js +0 -15
  57. package/dist/node/tcpClient.js +0 -81
  58. package/dist/node/tcpServer.js +0 -50
  59. package/dist/node/wrapNodeSocket.js +0 -56
  60. package/dist/persistence/deps.js +0 -3
  61. package/dist/persistence/memory/memoryPersistence.js +0 -118
  62. package/dist/persistence/persistence.js +0 -1
  63. package/dist/persistence/store.js +0 -1
  64. package/dist/server/context.js +0 -100
  65. package/dist/server/deps.js +0 -5
  66. package/dist/server/handlers/handleConnect.js +0 -80
  67. package/dist/server/handlers/handleDisconnect.js +0 -9
  68. package/dist/server/handlers/handlePacket.js +0 -65
  69. package/dist/server/handlers/handlePingreq.js +0 -11
  70. package/dist/server/handlers/handlePuback.js +0 -16
  71. package/dist/server/handlers/handlePubcomp.js +0 -14
  72. package/dist/server/handlers/handlePublish.js +0 -54
  73. package/dist/server/handlers/handlePubrec.js +0 -23
  74. package/dist/server/handlers/handlePubrel.js +0 -27
  75. package/dist/server/handlers/handleSubscribe.js +0 -56
  76. package/dist/server/handlers/handleUnsubscribe.js +0 -18
  77. package/dist/server/mod.js +0 -7
  78. package/dist/server/server.js +0 -47
  79. package/dist/socket/socket.js +0 -36
  80. package/dist/timer/timer.js +0 -40
  81. package/dist/trie/trie.js +0 -95
  82. package/dist/tsconfig.tsbuildinfo +0 -1
  83. package/dist/utils/args.js +0 -75
  84. package/dist/utils/assert.js +0 -20
  85. package/dist/utils/asyncQueue.js +0 -69
  86. package/dist/utils/deferred.js +0 -14
  87. package/dist/utils/logger.js +0 -58
  88. package/dist/utils/mod.js +0 -11
  89. package/dist/utils/nextTick.js +0 -18
  90. package/dist/utils/platform.js +0 -7
  91. package/tsconfig.json +0 -22
@@ -1,56 +0,0 @@
1
- import { Writable } from "node:stream";
2
- /**
3
- * Closes a Node.js socket if it is not already closed
4
- * @param sock - The Node.js socket to close
5
- */
6
- function closer(sock) {
7
- if (!sock.closed) {
8
- sock.end();
9
- }
10
- }
11
- /**
12
- * Wraps a Node.js socket into a standard web streams-based socket connection
13
- * @param socket - The Node.js socket to wrap
14
- * @returns A socket connection object with readable/writable streams and close method
15
- */
16
- export function wrapNodeSocket(socket) {
17
- const readable = new ReadableStream({
18
- type: "bytes",
19
- start(controller) {
20
- socket.on("data", (data) => {
21
- controller.enqueue(data);
22
- const desiredSize = controller.desiredSize ?? 0;
23
- if (desiredSize <= 0) {
24
- // The internal queue is full, so propagate
25
- // the backpressure signal to the underlying source.
26
- socket.pause();
27
- }
28
- });
29
- socket.on("error", (err) => controller.error(err));
30
- socket.on("end", () => {
31
- // close the controller
32
- controller.close();
33
- // and unlock the last BYOB read request
34
- controller.byobRequest?.respond(0);
35
- });
36
- },
37
- pull: () => {
38
- socket.resume();
39
- },
40
- cancel: () => {
41
- socket.end();
42
- },
43
- });
44
- const writable = Writable.toWeb(socket);
45
- const remoteAddr = {
46
- hostname: socket.remoteAddress || "",
47
- port: socket.remotePort || 0,
48
- };
49
- const conn = {
50
- readable: readable,
51
- writable: writable,
52
- close: () => closer(socket),
53
- remoteAddr,
54
- };
55
- return conn;
56
- }
@@ -1,3 +0,0 @@
1
- export { PacketType } from "../mqttPacket/mod.js";
2
- export { Trie } from "../trie/trie.js";
3
- export { logger } from "../utils/mod.js";
@@ -1,118 +0,0 @@
1
- import { Trie, } from "../deps.js";
2
- import { assert } from "../../utils/mod.js";
3
- const maxPacketId = 0xffff;
4
- export class MemoryStore {
5
- existingSession = false;
6
- clientId;
7
- packetId;
8
- pendingIncoming;
9
- pendingOutgoing;
10
- pendingAckOutgoing;
11
- subscriptions;
12
- constructor(clientId) {
13
- this.packetId = 0;
14
- this.pendingIncoming = new Map();
15
- this.pendingOutgoing = new Map();
16
- this.pendingAckOutgoing = new Set();
17
- this.subscriptions = new Map();
18
- this.clientId = clientId;
19
- }
20
- nextId() {
21
- const currentId = this.packetId;
22
- do {
23
- this.packetId++;
24
- if (this.packetId > maxPacketId) {
25
- this.packetId = 0;
26
- }
27
- } while ((this.pendingOutgoing.has(this.packetId) ||
28
- this.pendingAckOutgoing.has(this.packetId)) &&
29
- this.packetId !== currentId);
30
- assert(this.packetId !== currentId, "No unused packetId available");
31
- return this.packetId;
32
- }
33
- }
34
- export class MemoryPersistence {
35
- clientList;
36
- retained;
37
- trie;
38
- constructor() {
39
- this.clientList = new Map();
40
- this.retained = new Map();
41
- this.trie = new Trie(true);
42
- }
43
- registerClient(clientId, handler, clean) {
44
- const existingClient = this.clientList.get(clientId);
45
- const store = !clean && existingClient
46
- ? existingClient.store
47
- : new MemoryStore(clientId);
48
- this.clientList.set(clientId, { store, handler });
49
- return store;
50
- }
51
- deregisterClient(clientId) {
52
- const client = this.clientList.get(clientId);
53
- if (client) {
54
- this.unsubscribeAll(client.store);
55
- this.clientList.delete(clientId);
56
- }
57
- }
58
- subscribe(store, topicFilter, qos) {
59
- const clientId = store.clientId;
60
- if (!store.subscriptions.has(topicFilter)) {
61
- store.subscriptions.set(topicFilter, qos);
62
- this.trie.add(topicFilter, { clientId, qos });
63
- }
64
- }
65
- unsubscribe(store, topicFilter) {
66
- const clientId = store.clientId;
67
- const qos = store.subscriptions.get(topicFilter);
68
- if (qos) {
69
- store.subscriptions.delete(topicFilter);
70
- this.trie.remove(topicFilter, { clientId, qos });
71
- }
72
- }
73
- unsubscribeAll(store) {
74
- for (const [topicFilter, _qos] of store.subscriptions) {
75
- this.unsubscribe(store, topicFilter);
76
- }
77
- }
78
- publish(topic, packet) {
79
- if (packet.retain) {
80
- this.retained.set(packet.topic, packet);
81
- if (packet.payload === undefined) {
82
- this.retained.delete(packet.topic);
83
- }
84
- }
85
- // dedup clients
86
- const clients = new Map();
87
- for (const { clientId, qos } of this.trie.match(topic)) {
88
- const prevQos = clients.get(clientId);
89
- if (!prevQos || prevQos < qos) {
90
- clients.set(clientId, qos);
91
- }
92
- }
93
- // publish the message to all clients
94
- for (const [clientId, qos] of clients) {
95
- const newPacket = Object.assign({}, packet);
96
- newPacket.retain = false;
97
- newPacket.qos = qos;
98
- // logger.debug(`publish ${topic} to client ${clientId}`);
99
- const client = this.clientList.get(clientId);
100
- client?.handler(packet);
101
- }
102
- }
103
- handleRetained(clientId) {
104
- const retainedTrie = new Trie();
105
- const client = this.clientList.get(clientId);
106
- const store = client?.store;
107
- if (store) {
108
- for (const [topicFilter, _qos] of store.subscriptions) {
109
- retainedTrie.add(topicFilter, clientId);
110
- }
111
- for (const [topic, packet] of this.retained) {
112
- if (retainedTrie.match(topic).length > 0) {
113
- client?.handler(packet);
114
- }
115
- }
116
- }
117
- }
118
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1,100 +0,0 @@
1
- import { logger, MqttConn, PacketNameByType, PacketType, } from "./deps.js";
2
- export const SysPrefix = "$";
3
- export const utf8Encoder = new TextEncoder();
4
- /**
5
- * The Context class is used to maintain state of a MQTT connection
6
- * It handles:
7
- * - connect/disconnect including broadcasting of these events
8
- * - publish
9
- * - persistence
10
- * - the will
11
- */
12
- export class Context {
13
- connected;
14
- conn;
15
- mqttConn;
16
- persistence;
17
- handlers;
18
- static clientList = new Map();
19
- store;
20
- will;
21
- timer;
22
- constructor(persistence, conn, handlers) {
23
- this.persistence = persistence;
24
- this.connected = false;
25
- this.conn = conn;
26
- this.mqttConn = new MqttConn({ conn });
27
- this.handlers = handlers;
28
- }
29
- async send(packet) {
30
- logger.debug("Sending", PacketNameByType[packet.type]);
31
- logger.debug(JSON.stringify(packet, null, 2));
32
- await this.mqttConn.send(packet);
33
- }
34
- connect(clientId, clean) {
35
- logger.debug("Connecting", clientId);
36
- const existingSession = Context.clientList.get(clientId);
37
- if (existingSession) {
38
- existingSession.close(false);
39
- }
40
- this.store = this.persistence.registerClient(clientId, this.doPublish.bind(this), clean);
41
- this.connected = true;
42
- this.broadcast("$SYS/connect/clients", clientId);
43
- logger.debug("Connected", clientId);
44
- }
45
- doPublish(packet) {
46
- const qos = packet.qos || 0;
47
- if (qos === 0) {
48
- packet.id = 0;
49
- this.send(packet);
50
- return;
51
- }
52
- if (this.store) {
53
- packet.id = this.store.nextId();
54
- this.store.pendingOutgoing.set(packet.id, packet);
55
- this.send(packet);
56
- }
57
- }
58
- clean(clientId) {
59
- this.persistence.deregisterClient(clientId);
60
- }
61
- close(executewill = true) {
62
- if (this.connected) {
63
- logger.debug(`Closing ${this.store?.clientId} while mqttConn is ${this.mqttConn.isClosed ? "" : "not "}closed`);
64
- this.connected = false;
65
- if (typeof this.timer === "object") {
66
- this.timer.clear();
67
- }
68
- if (this.store) {
69
- this.broadcast("$SYS/disconnect/clients", this.store.clientId);
70
- }
71
- if (executewill) {
72
- this.handleWill();
73
- }
74
- }
75
- if (!this.mqttConn.isClosed) {
76
- this.mqttConn.close();
77
- }
78
- }
79
- handleWill() {
80
- if (this.will) {
81
- if (!this.will.topic.startsWith(SysPrefix) &&
82
- this.handlers.isAuthorizedToPublish &&
83
- this.handlers.isAuthorizedToPublish(this, this.will.topic)) {
84
- this.persistence.publish(this.will.topic, this.will);
85
- }
86
- }
87
- }
88
- broadcast(topic, payload, retain = false) {
89
- const packet = {
90
- type: PacketType.publish,
91
- topic,
92
- retain,
93
- payload: utf8Encoder.encode(payload),
94
- };
95
- if (packet.retain === true) {
96
- this.persistence.retained.set(packet.topic, packet);
97
- }
98
- this.persistence.publish(packet.topic, packet);
99
- }
100
- }
@@ -1,5 +0,0 @@
1
- export { MqttConn } from "../mqttConn/mqttConn.js";
2
- export { MemoryPersistence } from "../persistence/memory/memoryPersistence.js";
3
- export { AuthenticationResult, PacketNameByType, PacketType, } from "../mqttPacket/mod.js";
4
- export { Timer } from "../timer/timer.js";
5
- export { logger } from "../utils/mod.js";
@@ -1,80 +0,0 @@
1
- import { AuthenticationResult, logger, PacketType, Timer, } from "../deps.js";
2
- /**
3
- * Checks if the client is authenticated based on the provided credentials
4
- * @param ctx - The connection context
5
- * @param packet - The MQTT CONNECT packet
6
- * @returns Authentication result indicating if the client is authenticated
7
- */
8
- function isAuthenticated(ctx, packet) {
9
- if (ctx.handlers.isAuthenticated) {
10
- return ctx.handlers.isAuthenticated(ctx, packet.clientId || "", packet.username || "", packet.password || new Uint8Array(0));
11
- }
12
- return AuthenticationResult.ok;
13
- }
14
- /**
15
- * Validates the CONNECT packet
16
- * @param ctx - The connection context
17
- * @param packet - The MQTT CONNECT packet to validate
18
- * @returns Authentication result indicating if the CONNECT packet is valid
19
- */
20
- function validateConnect(ctx, packet) {
21
- if (packet.protocolLevel !== 4) {
22
- return AuthenticationResult.unacceptableProtocol;
23
- }
24
- return isAuthenticated(ctx, packet);
25
- }
26
- /**
27
- * Processes the validated CONNECT packet
28
- * @param packet - The MQTT CONNECT packet
29
- * @param ctx - The connection context
30
- * @param clientId - The client ID
31
- */
32
- function processValidatedConnect(returnCode, packet, ctx, clientId) {
33
- if (returnCode === AuthenticationResult.ok) {
34
- if (packet.will) {
35
- ctx.will = {
36
- type: PacketType.publish,
37
- qos: packet.will.qos,
38
- retain: packet.will.retain,
39
- topic: packet.will.topic,
40
- payload: packet.will.payload,
41
- };
42
- }
43
- ctx.connect(clientId, packet.clean || false);
44
- const keepAlive = packet.keepAlive || 0;
45
- if (keepAlive > 0) {
46
- logger.debug(`Setting keepalive to ${keepAlive * 1500} ms`);
47
- ctx.timer = new Timer(() => {
48
- ctx.close();
49
- }, Math.floor(keepAlive * 1500));
50
- }
51
- // is this a new session?
52
- // either because its the first time for the client
53
- // or it specifically asked for a clean one
54
- const previousSession = ctx.store?.existingSession;
55
- // client now has a history
56
- if (!previousSession && ctx.store) {
57
- ctx.store.existingSession = true;
58
- }
59
- return previousSession || false;
60
- }
61
- return false;
62
- }
63
- /**
64
- * Handles the MQTT CONNECT packet
65
- * @param ctx - The connection context
66
- * @param packet - The MQTT CONNECT packet to handle
67
- */
68
- export function handleConnect(ctx, packet) {
69
- const clientId = packet.clientId || `Opifex-${crypto.randomUUID()}`;
70
- const returnCode = validateConnect(ctx, packet);
71
- const sessionPresent = processValidatedConnect(returnCode, packet, ctx, clientId);
72
- ctx.send({
73
- type: PacketType.connack,
74
- sessionPresent,
75
- returnCode,
76
- });
77
- if (returnCode !== AuthenticationResult.ok) {
78
- ctx.close();
79
- }
80
- }
@@ -1,9 +0,0 @@
1
- /**
2
- * Handles client disconnection by clearing the will message and closing the connection
3
- * @param {Context} ctx - The connection context object
4
- * @returns {void}
5
- */
6
- export function handleDisconnect(ctx) {
7
- ctx.will = undefined;
8
- ctx.close();
9
- }
@@ -1,65 +0,0 @@
1
- import { PacketNameByType, PacketType, } from "../deps.js";
2
- import { handleConnect } from "./handleConnect.js";
3
- import { handlePingreq } from "./handlePingreq.js";
4
- import { handlePublish } from "./handlePublish.js";
5
- import { handlePuback } from "./handlePuback.js";
6
- import { handlePubrec } from "./handlePubrec.js";
7
- import { handlePubrel } from "./handlePubrel.js";
8
- import { handlePubcomp } from "./handlePubcomp.js";
9
- import { handleSubscribe } from "./handleSubscribe.js";
10
- import { handleUnsubscribe } from "./handleUnsubscribe.js";
11
- import { handleDisconnect } from "./handleDisconnect.js";
12
- import { logger } from "../deps.js";
13
- /**
14
- * Handles incoming MQTT packets based on their type and connection state
15
- * @param ctx - The connection context containing client state and configuration
16
- * @param packet - The MQTT packet to handle
17
- * @throws Error if receiving unexpected packet types or packets before connect
18
- * @returns Promise that resolves when packet handling is complete
19
- */
20
- export async function handlePacket(ctx, packet) {
21
- logger.debug("handling", PacketNameByType[packet.type]);
22
- logger.debug(JSON.stringify(packet, null, 2));
23
- if (!ctx.connected) {
24
- if (packet.type === PacketType.connect) {
25
- handleConnect(ctx, packet);
26
- }
27
- else {
28
- throw new Error(`Received ${PacketNameByType[packet.type]} packet before connect`);
29
- }
30
- }
31
- else {
32
- switch (packet.type) {
33
- case PacketType.pingreq:
34
- await handlePingreq(ctx);
35
- break;
36
- case PacketType.publish:
37
- await handlePublish(ctx, packet);
38
- break;
39
- case PacketType.puback:
40
- handlePuback(ctx, packet);
41
- break;
42
- case PacketType.pubrel:
43
- await handlePubrel(ctx, packet);
44
- break;
45
- case PacketType.pubrec:
46
- await handlePubrec(ctx, packet);
47
- break;
48
- case PacketType.pubcomp:
49
- handlePubcomp(ctx, packet);
50
- break;
51
- case PacketType.subscribe:
52
- await handleSubscribe(ctx, packet);
53
- break;
54
- case PacketType.unsubscribe:
55
- await handleUnsubscribe(ctx, packet);
56
- break;
57
- case PacketType.disconnect:
58
- handleDisconnect(ctx);
59
- break;
60
- default:
61
- throw new Error(`Received unexpected ${PacketNameByType[packet.type]} packet after connect`);
62
- }
63
- ctx.timer?.reset();
64
- }
65
- }
@@ -1,11 +0,0 @@
1
- import { PacketType } from "../deps.js";
2
- /**
3
- * Handles PINGREQ packet by responding with a PINGRESP packet
4
- * @param ctx - The connection context containing send method
5
- * @returns Promise that resolves when PINGRESP is sent
6
- */
7
- export async function handlePingreq(ctx) {
8
- await ctx.send({
9
- type: PacketType.pingres,
10
- });
11
- }
@@ -1,16 +0,0 @@
1
- /**
2
- * Handles PUBACK (Publish Acknowledgment) packets in MQTT protocol
3
- * @param ctx - The connection context containing the client's state and configuration
4
- * @param packet - The PUBACK packet received from the client
5
- * @description
6
- * PUBACK packets are sent in response to PUBLISH packets with QoS level 1.
7
- * This function removes the original PUBLISH packet from the pending outgoing messages store
8
- * once acknowledgment is received.
9
- */
10
- export function handlePuback(ctx, packet) {
11
- // qos 1 only
12
- const id = packet.id;
13
- if (ctx.store?.pendingOutgoing.has(id)) {
14
- ctx.store.pendingOutgoing.delete(id);
15
- }
16
- }
@@ -1,14 +0,0 @@
1
- /**
2
- * Handles PUBCOMP packets which are the response to PUBREL packets in QoS 2 flow
3
- * @param ctx - The connection context containing the client state and configuration
4
- * @param packet - The PUBCOMP packet received from the client
5
- * @description
6
- * This is the fourth and final packet of the QoS 2 protocol exchange.
7
- * When received, it removes the message from the pendingAckOutgoing store.
8
- */
9
- export function handlePubcomp(ctx, packet) {
10
- const id = packet.id;
11
- if (ctx.store?.pendingAckOutgoing.has(id)) {
12
- ctx.store.pendingAckOutgoing.delete(id);
13
- }
14
- }
@@ -1,54 +0,0 @@
1
- import { SysPrefix } from "../context.js";
2
- import { PacketType } from "../deps.js";
3
- /**
4
- * Checks if a client is authorized to publish to a given topic
5
- * @param ctx - The connection context
6
- * @param topic - The topic to check authorization for
7
- * @returns boolean indicating if client is authorized to publish
8
- */
9
- function authorizedToPublish(ctx, topic) {
10
- if (topic.startsWith(SysPrefix)) {
11
- return false;
12
- }
13
- if (ctx.handlers.isAuthorizedToPublish) {
14
- return ctx.handlers.isAuthorizedToPublish(ctx, topic);
15
- }
16
- return true;
17
- }
18
- /**
19
- * Handles MQTT PUBLISH packets
20
- * @param ctx - The connection context
21
- * @param packet - The PUBLISH packet to process
22
- * @returns Promise that resolves when packet is processed
23
- * @throws Error if packet processing fails
24
- */
25
- export async function handlePublish(ctx, packet) {
26
- if (!authorizedToPublish(ctx, packet.topic)) {
27
- return;
28
- }
29
- const qos = packet.qos || 0;
30
- if (qos === 0) {
31
- ctx.persistence.publish(packet.topic, packet);
32
- return;
33
- }
34
- if (packet.id !== undefined) {
35
- // qos 1
36
- if (qos === 1) {
37
- const id = packet.id; // retain the id
38
- ctx.persistence.publish(packet.topic, packet);
39
- await ctx.send({
40
- type: PacketType.puback,
41
- id,
42
- });
43
- return;
44
- }
45
- // qos 2
46
- if (ctx.store) {
47
- ctx.store.pendingIncoming.set(packet.id, packet);
48
- await ctx.send({
49
- type: PacketType.pubrec,
50
- id: packet.id,
51
- });
52
- }
53
- }
54
- }
@@ -1,23 +0,0 @@
1
- import { PacketType } from "../deps.js";
2
- /**
3
- * Handles PUBREC (QoS 2 Publish Received) packets
4
- * @param ctx - The connection context
5
- * @param packet - The PUBREC packet received from the client
6
- * @returns Promise that resolves when handling is complete
7
- * @description
8
- * For QoS 2 message flow:
9
- * 1. Discards the original publish message
10
- * 2. Stores that PUBREC was received for the packet ID
11
- * 3. Sends PUBREL packet in response
12
- */
13
- export async function handlePubrec(ctx, packet) {
14
- const id = packet.id;
15
- if (ctx.store?.pendingOutgoing.has(id)) {
16
- ctx.store.pendingOutgoing.delete(id);
17
- ctx.store.pendingAckOutgoing.add(id);
18
- await ctx.send({
19
- type: PacketType.pubrel,
20
- id,
21
- });
22
- }
23
- }
@@ -1,27 +0,0 @@
1
- import { PacketType } from "../deps.js";
2
- /**
3
- * Handles PUBREL (QoS 2 publish release) packets
4
- *
5
- * @param ctx - The connection context
6
- * @param packet - The PUBREL packet received from the client
7
- * @returns Promise that resolves when handling is complete
8
- * @description
9
- * For QoS 2 message delivery:
10
- * 1. Initiates onward delivery of the Application Message
11
- * 2. Discards the stored message
12
- * 3. Sends PUBCOMP packet with the Packet Identifier
13
- */
14
- export async function handlePubrel(ctx, packet) {
15
- const id = packet.id;
16
- if (ctx.store?.pendingIncoming.has(id)) {
17
- const storedPacket = ctx.store.pendingIncoming.get(id);
18
- if (storedPacket) {
19
- ctx.persistence.publish(storedPacket.topic, storedPacket);
20
- ctx.store.pendingIncoming.delete(id);
21
- await ctx.send({
22
- type: PacketType.pubcomp,
23
- id,
24
- });
25
- }
26
- }
27
- }
@@ -1,56 +0,0 @@
1
- import { PacketType, } from "../deps.js";
2
- /**
3
- * @constant {number} SubscriptionFailure
4
- * @description Code indicating a failed subscription attempt
5
- */
6
- const SubscriptionFailure = 0x80;
7
- /**
8
- * Checks if a client is authorized to subscribe to a topic
9
- * @param ctx - The connection context
10
- * @param topicFilter - The topic filter to check authorization for
11
- * @returns True if authorized, false otherwise
12
- */
13
- function authorizedToSubscribe(ctx, topicFilter) {
14
- if (ctx.handlers.isAuthorizedToSubscribe) {
15
- return ctx.handlers.isAuthorizedToSubscribe(ctx, topicFilter);
16
- }
17
- return true;
18
- }
19
- /**
20
- * @function handleSubscribe
21
- * @description Processes an MQTT SUBSCRIBE packet
22
- * @param {Context} ctx - The connection context
23
- * @param {SubscribePacket} packet - The SUBSCRIBE packet received from the client
24
- * @returns {Promise<void>}
25
- * @throws {Error} If subscription processing fails
26
- * @remarks The order of return codes in the SUBACK Packet MUST match the order of Topic Filters in the SUBSCRIBE Packet [MQTT-3.9.3-1]
27
- */
28
- export async function handleSubscribe(ctx, packet) {
29
- /*
30
- * The order of return codes in the SUBACK Packet MUST match the order of
31
- * Topic Filters in the SUBSCRIBE Packet [MQTT-3.9.3-1].
32
- */
33
- const validSubscriptions = [];
34
- const returnCodes = packet.subscriptions.map((sub) => {
35
- if (ctx.store) {
36
- if (!authorizedToSubscribe(ctx, sub.topicFilter)) {
37
- return SubscriptionFailure;
38
- }
39
- ctx.persistence.subscribe(ctx.store, sub.topicFilter, sub.qos);
40
- validSubscriptions.push(sub);
41
- return sub.qos;
42
- }
43
- return SubscriptionFailure;
44
- });
45
- await ctx.send({
46
- type: PacketType.suback,
47
- id: packet.id,
48
- returnCodes: returnCodes,
49
- });
50
- /*
51
- * send any retained messages that match these subscriptions
52
- */
53
- if (ctx.store) {
54
- ctx.persistence.handleRetained(ctx.store.clientId);
55
- }
56
- }
@@ -1,18 +0,0 @@
1
- import { PacketType } from "../deps.js";
2
- /**
3
- * Handles MQTT unsubscribe packets by removing subscriptions and sending acknowledgement
4
- * @param ctx - The connection context containing client information and methods
5
- * @param packet - The MQTT unsubscribe packet containing topics to unsubscribe from
6
- * @returns Promise that resolves when unsubscribe is complete and acknowledged
7
- */
8
- export async function handleUnsubscribe(ctx, packet) {
9
- for (const topic of packet.topicFilters) {
10
- if (ctx.store) {
11
- ctx.persistence.unsubscribe(ctx.store, topic);
12
- }
13
- }
14
- await ctx.send({
15
- type: PacketType.unsuback,
16
- id: packet.id,
17
- });
18
- }