@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.
- package/.github/workflows/update-license-year.sh +14 -0
- package/.github/workflows/update-license-year.yml +13 -0
- package/CHANGELOG.md +16 -0
- package/LICENSE.txt +1 -1
- package/README.md +72 -4
- package/bin/conformanceServer.ts +1 -1
- package/bin/mqtt.ts +5 -2
- package/client/client.ts +13 -3
- package/client/context.ts +2 -0
- package/client/mod.ts +1 -2
- package/deno/client.ts +10 -5
- package/deno/server.ts +3 -2
- package/deno.json +4 -1
- package/deno.lock +11 -8
- package/dev_utils/dummyConn.ts +4 -4
- package/dist/bin/conformanceServer.js +52 -0
- package/dist/bin/demoServer.js +29 -57
- package/dist/bin/mqtt.js +148 -187
- package/dist/client/ConnectionState.js +7 -0
- package/dist/client/client.js +129 -0
- package/dist/client/context.js +148 -0
- package/dist/client/deps.js +5 -0
- package/dist/client/handlers/handleConnack.js +18 -0
- package/dist/client/handlers/handlePacket.js +50 -0
- package/dist/client/handlers/handlePuback.js +6 -0
- package/dist/client/handlers/handlePubcomp.js +9 -0
- package/dist/client/handlers/handlePublish.js +28 -0
- package/dist/client/handlers/handlePubrec.js +17 -0
- package/dist/client/handlers/handlePubrel.js +19 -0
- package/dist/client/handlers/handleSuback.js +9 -0
- package/dist/client/handlers/handleUnsuback.js +7 -0
- package/dist/client/mod.js +5 -3
- package/dist/client/store/deps.js +1 -0
- package/dist/client/store/memoryStore.js +36 -0
- package/dist/client/store/store.js +1 -0
- package/dist/mqttConn/mqttConn.js +104 -0
- package/dist/mqttPacket/AuthenticationResult.js +12 -0
- package/dist/mqttPacket/BitMask.js +10 -0
- package/dist/mqttPacket/PacketType.js +18 -0
- package/dist/mqttPacket/connack.js +27 -0
- package/dist/mqttPacket/connect.js +115 -0
- package/dist/mqttPacket/decoder.js +89 -0
- package/dist/mqttPacket/disconnect.js +14 -0
- package/dist/mqttPacket/encoder.js +56 -0
- package/dist/mqttPacket/length.js +43 -0
- package/dist/mqttPacket/mod.js +71 -0
- package/dist/mqttPacket/pingreq.js +15 -0
- package/dist/mqttPacket/pingres.js +14 -0
- package/dist/mqttPacket/puback.js +20 -0
- package/dist/mqttPacket/pubcomp.js +20 -0
- package/dist/mqttPacket/publish.js +51 -0
- package/dist/mqttPacket/pubrec.js +20 -0
- package/dist/mqttPacket/pubrel.js +20 -0
- package/dist/mqttPacket/suback.js +29 -0
- package/dist/mqttPacket/subscribe.js +52 -0
- package/dist/mqttPacket/types.js +1 -0
- package/dist/mqttPacket/unsuback.js +20 -0
- package/dist/mqttPacket/unsubscribe.js +36 -0
- package/dist/mqttPacket/validators.js +15 -0
- package/dist/node/client.js +64 -0
- package/dist/node/server.js +22 -0
- package/dist/node/wrapNodeSocket.js +47 -0
- package/dist/persistence/deps.js +3 -0
- package/dist/persistence/memory/memoryPersistence.js +117 -0
- package/dist/persistence/persistence.js +1 -0
- package/dist/persistence/store.js +1 -0
- package/dist/server/context.js +100 -0
- package/dist/server/deps.js +5 -0
- package/dist/server/handlers/handleConnect.js +46 -0
- package/dist/server/handlers/handleDisconnect.js +4 -0
- package/dist/server/handlers/handlePacket.js +58 -0
- package/dist/server/handlers/handlePingreq.js +6 -0
- package/dist/server/handlers/handlePuback.js +8 -0
- package/dist/server/handlers/handlePubcomp.js +8 -0
- package/dist/server/handlers/handlePublish.js +40 -0
- package/dist/server/handlers/handlePubrec.js +15 -0
- package/dist/server/handlers/handlePubrel.js +18 -0
- package/dist/server/handlers/handleSubscribe.js +33 -0
- package/dist/server/handlers/handleUnsubscribe.js +12 -0
- package/dist/server/mod.js +7 -589
- package/dist/server/server.js +47 -0
- package/dist/socket/socket.js +36 -0
- package/dist/timer/timer.js +40 -0
- package/dist/trie/trie.js +95 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/utils/args.js +61 -0
- package/dist/utils/assert.js +19 -0
- package/dist/utils/asyncQueue.js +56 -0
- package/dist/utils/deferred.js +11 -0
- package/dist/utils/logger.js +29 -0
- package/dist/utils/mod.js +7 -0
- package/dist/utils/nextTick.js +3 -0
- package/dist/utils/platform.js +4 -0
- package/mqttConn/mqttConn.ts +4 -1
- package/node/client.ts +29 -6
- package/node/server.ts +2 -0
- package/node/wrapNodeSocket.ts +1 -1
- package/package.json +17 -4
- package/server/mod.ts +1 -2
- package/socket/socket.ts +3 -2
- package/timer/timer.ts +1 -1
- package/tsconfig.json +3 -0
- package/.vscode/extensions.json +0 -10
- package/.vscode/settings.json +0 -12
- package/deno/wrapDenoConn.ts +0 -12
- package/dist/client-DqLTBVC2.js +0 -541
- package/dist/timer-DDWVNsyG.js +0 -924
- package/dist/wrapNodeSocket-BAJm059T.js +0 -115
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { Trie, } from "../deps.js";
|
|
2
|
+
import { assert } from "../../utils/mod.js";
|
|
3
|
+
const maxPacketId = 0xffff;
|
|
4
|
+
export class MemoryStore {
|
|
5
|
+
clientId;
|
|
6
|
+
packetId;
|
|
7
|
+
pendingIncoming;
|
|
8
|
+
pendingOutgoing;
|
|
9
|
+
pendingAckOutgoing;
|
|
10
|
+
subscriptions;
|
|
11
|
+
constructor(clientId) {
|
|
12
|
+
this.packetId = 0;
|
|
13
|
+
this.pendingIncoming = new Map();
|
|
14
|
+
this.pendingOutgoing = new Map();
|
|
15
|
+
this.pendingAckOutgoing = new Set();
|
|
16
|
+
this.subscriptions = new Map();
|
|
17
|
+
this.clientId = clientId;
|
|
18
|
+
}
|
|
19
|
+
nextId() {
|
|
20
|
+
const currentId = this.packetId;
|
|
21
|
+
do {
|
|
22
|
+
this.packetId++;
|
|
23
|
+
if (this.packetId > maxPacketId) {
|
|
24
|
+
this.packetId = 0;
|
|
25
|
+
}
|
|
26
|
+
} while ((this.pendingOutgoing.has(this.packetId) ||
|
|
27
|
+
this.pendingAckOutgoing.has(this.packetId)) &&
|
|
28
|
+
this.packetId !== currentId);
|
|
29
|
+
assert(this.packetId !== currentId, "No unused packetId available");
|
|
30
|
+
return this.packetId;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export class MemoryPersistence {
|
|
34
|
+
clientList;
|
|
35
|
+
retained;
|
|
36
|
+
trie;
|
|
37
|
+
constructor() {
|
|
38
|
+
this.clientList = new Map();
|
|
39
|
+
this.retained = new Map();
|
|
40
|
+
this.trie = new Trie(true);
|
|
41
|
+
}
|
|
42
|
+
registerClient(clientId, handler, clean) {
|
|
43
|
+
const existingClient = this.clientList.get(clientId);
|
|
44
|
+
const store = !clean && existingClient
|
|
45
|
+
? existingClient.store
|
|
46
|
+
: new MemoryStore(clientId);
|
|
47
|
+
this.clientList.set(clientId, { store, handler });
|
|
48
|
+
return store;
|
|
49
|
+
}
|
|
50
|
+
deregisterClient(clientId) {
|
|
51
|
+
const client = this.clientList.get(clientId);
|
|
52
|
+
if (client) {
|
|
53
|
+
this.unsubscribeAll(client.store);
|
|
54
|
+
this.clientList.delete(clientId);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
subscribe(store, topicFilter, qos) {
|
|
58
|
+
const clientId = store.clientId;
|
|
59
|
+
if (!store.subscriptions.has(topicFilter)) {
|
|
60
|
+
store.subscriptions.set(topicFilter, qos);
|
|
61
|
+
this.trie.add(topicFilter, { clientId, qos });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
unsubscribe(store, topicFilter) {
|
|
65
|
+
const clientId = store.clientId;
|
|
66
|
+
const qos = store.subscriptions.get(topicFilter);
|
|
67
|
+
if (qos) {
|
|
68
|
+
store.subscriptions.delete(topicFilter);
|
|
69
|
+
this.trie.remove(topicFilter, { clientId, qos });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
unsubscribeAll(store) {
|
|
73
|
+
for (const [topicFilter, _qos] of store.subscriptions) {
|
|
74
|
+
this.unsubscribe(store, topicFilter);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
publish(topic, packet) {
|
|
78
|
+
if (packet.retain) {
|
|
79
|
+
this.retained.set(packet.topic, packet);
|
|
80
|
+
if (packet.payload === undefined) {
|
|
81
|
+
this.retained.delete(packet.topic);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// dedup clients
|
|
85
|
+
const clients = new Map();
|
|
86
|
+
for (const { clientId, qos } of this.trie.match(topic)) {
|
|
87
|
+
const prevQos = clients.get(clientId);
|
|
88
|
+
if (!prevQos || prevQos < qos) {
|
|
89
|
+
clients.set(clientId, qos);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// publish the message to all clients
|
|
93
|
+
for (const [clientId, qos] of clients) {
|
|
94
|
+
const newPacket = Object.assign({}, packet);
|
|
95
|
+
newPacket.retain = false;
|
|
96
|
+
newPacket.qos = qos;
|
|
97
|
+
// logger.debug(`publish ${topic} to client ${clientId}`);
|
|
98
|
+
const client = this.clientList.get(clientId);
|
|
99
|
+
client?.handler(packet);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
handleRetained(clientId) {
|
|
103
|
+
const retainedTrie = new Trie();
|
|
104
|
+
const client = this.clientList.get(clientId);
|
|
105
|
+
const store = client?.store;
|
|
106
|
+
if (store) {
|
|
107
|
+
for (const [topicFilter, _qos] of store.subscriptions) {
|
|
108
|
+
retainedTrie.add(topicFilter, clientId);
|
|
109
|
+
}
|
|
110
|
+
for (const [topic, packet] of this.retained) {
|
|
111
|
+
if (retainedTrie.match(topic).length > 0) {
|
|
112
|
+
client?.handler(packet);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,100 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
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";
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { AuthenticationResult, logger, PacketType, Timer, } from "../deps.js";
|
|
2
|
+
function isAuthenticated(ctx, packet) {
|
|
3
|
+
if (ctx.handlers.isAuthenticated) {
|
|
4
|
+
return ctx.handlers.isAuthenticated(ctx, packet.clientId || "", packet.username || "", packet.password || new Uint8Array(0));
|
|
5
|
+
}
|
|
6
|
+
return AuthenticationResult.ok;
|
|
7
|
+
}
|
|
8
|
+
function validateConnect(ctx, packet) {
|
|
9
|
+
if (packet.protocolLevel !== 4) {
|
|
10
|
+
return AuthenticationResult.unacceptableProtocol;
|
|
11
|
+
}
|
|
12
|
+
return isAuthenticated(ctx, packet);
|
|
13
|
+
}
|
|
14
|
+
export function handleConnect(ctx, packet) {
|
|
15
|
+
const clientId = packet.clientId || `Opifex-${crypto.randomUUID()}`;
|
|
16
|
+
const returnCode = validateConnect(ctx, packet);
|
|
17
|
+
// connect is ok
|
|
18
|
+
if (returnCode === AuthenticationResult.ok) {
|
|
19
|
+
if (packet.will) {
|
|
20
|
+
ctx.will = {
|
|
21
|
+
type: PacketType.publish,
|
|
22
|
+
qos: packet.will.qos,
|
|
23
|
+
retain: packet.will.retain,
|
|
24
|
+
topic: packet.will.topic,
|
|
25
|
+
payload: packet.will.payload,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
ctx.connect(clientId, packet.clean || false);
|
|
29
|
+
const keepAlive = packet.keepAlive || 0;
|
|
30
|
+
if (keepAlive > 0) {
|
|
31
|
+
logger.debug(`Setting keepalive to ${keepAlive * 1500} ms`);
|
|
32
|
+
ctx.timer = new Timer(() => {
|
|
33
|
+
ctx.close();
|
|
34
|
+
}, Math.floor(keepAlive * 1500));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const sessionPresent = false;
|
|
38
|
+
ctx.send({
|
|
39
|
+
type: PacketType.connack,
|
|
40
|
+
sessionPresent,
|
|
41
|
+
returnCode,
|
|
42
|
+
});
|
|
43
|
+
if (returnCode !== AuthenticationResult.ok) {
|
|
44
|
+
ctx.close();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
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
|
+
export async function handlePacket(ctx, packet) {
|
|
14
|
+
logger.debug("handling", PacketNameByType[packet.type]);
|
|
15
|
+
logger.debug(JSON.stringify(packet, null, 2));
|
|
16
|
+
if (!ctx.connected) {
|
|
17
|
+
if (packet.type === PacketType.connect) {
|
|
18
|
+
handleConnect(ctx, packet);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
throw new Error(`Received ${PacketNameByType[packet.type]} packet before connect`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
switch (packet.type) {
|
|
26
|
+
case PacketType.pingreq:
|
|
27
|
+
await handlePingreq(ctx);
|
|
28
|
+
break;
|
|
29
|
+
case PacketType.publish:
|
|
30
|
+
await handlePublish(ctx, packet);
|
|
31
|
+
break;
|
|
32
|
+
case PacketType.puback:
|
|
33
|
+
handlePuback(ctx, packet);
|
|
34
|
+
break;
|
|
35
|
+
case PacketType.pubrel:
|
|
36
|
+
await handlePubrel(ctx, packet);
|
|
37
|
+
break;
|
|
38
|
+
case PacketType.pubrec:
|
|
39
|
+
await handlePubrec(ctx, packet);
|
|
40
|
+
break;
|
|
41
|
+
case PacketType.pubcomp:
|
|
42
|
+
handlePubcomp(ctx, packet);
|
|
43
|
+
break;
|
|
44
|
+
case PacketType.subscribe:
|
|
45
|
+
await handleSubscribe(ctx, packet);
|
|
46
|
+
break;
|
|
47
|
+
case PacketType.unsubscribe:
|
|
48
|
+
await handleUnsubscribe(ctx, packet);
|
|
49
|
+
break;
|
|
50
|
+
case PacketType.disconnect:
|
|
51
|
+
handleDisconnect(ctx);
|
|
52
|
+
break;
|
|
53
|
+
default:
|
|
54
|
+
throw new Error(`Received unexpected ${PacketNameByType[packet.type]} packet after connect`);
|
|
55
|
+
}
|
|
56
|
+
ctx.timer?.reset();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// The PUBCOMP Packet is the response to a PUBREL Packet.
|
|
2
|
+
// It is the fourth and final packet of the QoS 2 protocol exchange.
|
|
3
|
+
export function handlePubcomp(ctx, packet) {
|
|
4
|
+
const id = packet.id;
|
|
5
|
+
if (ctx.store?.pendingAckOutgoing.has(id)) {
|
|
6
|
+
ctx.store.pendingAckOutgoing.delete(id);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { SysPrefix } from "../context.js";
|
|
2
|
+
import { PacketType } from "../deps.js";
|
|
3
|
+
function authorizedToPublish(ctx, topic) {
|
|
4
|
+
if (topic.startsWith(SysPrefix)) {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
if (ctx.handlers.isAuthorizedToPublish) {
|
|
8
|
+
return ctx.handlers.isAuthorizedToPublish(ctx, topic);
|
|
9
|
+
}
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
export async function handlePublish(ctx, packet) {
|
|
13
|
+
if (!authorizedToPublish(ctx, packet.topic)) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const qos = packet.qos || 0;
|
|
17
|
+
if (qos === 0) {
|
|
18
|
+
ctx.persistence.publish(packet.topic, packet);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (packet.id !== undefined) {
|
|
22
|
+
// qos 1
|
|
23
|
+
if (qos === 1) {
|
|
24
|
+
ctx.persistence.publish(packet.topic, packet);
|
|
25
|
+
await ctx.send({
|
|
26
|
+
type: PacketType.puback,
|
|
27
|
+
id: packet.id,
|
|
28
|
+
});
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
// qos 2
|
|
32
|
+
if (ctx.store) {
|
|
33
|
+
ctx.store.pendingIncoming.set(packet.id, packet);
|
|
34
|
+
await ctx.send({
|
|
35
|
+
type: PacketType.pubrec,
|
|
36
|
+
id: packet.id,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { PacketType } from "../deps.js";
|
|
2
|
+
// qos 2
|
|
3
|
+
// Discard message, Store PUBREC received <Packet Identifier>
|
|
4
|
+
// send PUBREL <Packet Identifier>
|
|
5
|
+
export async function handlePubrec(ctx, packet) {
|
|
6
|
+
const id = packet.id;
|
|
7
|
+
if (ctx.store?.pendingOutgoing.has(id)) {
|
|
8
|
+
ctx.store.pendingOutgoing.delete(id);
|
|
9
|
+
ctx.store.pendingAckOutgoing.add(id);
|
|
10
|
+
await ctx.send({
|
|
11
|
+
type: PacketType.pubrel,
|
|
12
|
+
id,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { PacketType } from "../deps.js";
|
|
2
|
+
// qos 2 only
|
|
3
|
+
// Method A, Initiate onward delivery of the Application Message1 then discard message
|
|
4
|
+
// Send PUBCOMP <Packet Identifier>
|
|
5
|
+
export async function handlePubrel(ctx, packet) {
|
|
6
|
+
const id = packet.id;
|
|
7
|
+
if (ctx.store?.pendingIncoming.has(id)) {
|
|
8
|
+
const storedPacket = ctx.store.pendingIncoming.get(id);
|
|
9
|
+
if (storedPacket) {
|
|
10
|
+
ctx.persistence.publish(storedPacket.topic, storedPacket);
|
|
11
|
+
ctx.store.pendingIncoming.delete(id);
|
|
12
|
+
await ctx.send({
|
|
13
|
+
type: PacketType.pubcomp,
|
|
14
|
+
id,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { PacketType, } from "../deps.js";
|
|
2
|
+
const SubscriptionFailure = 0x80;
|
|
3
|
+
function authorizedToSubscribe(ctx, topicFilter) {
|
|
4
|
+
if (ctx.handlers.isAuthorizedToSubscribe) {
|
|
5
|
+
return ctx.handlers.isAuthorizedToSubscribe(ctx, topicFilter);
|
|
6
|
+
}
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
export async function handleSubscribe(ctx, packet) {
|
|
10
|
+
// The order of return codes in the SUBACK Packet MUST match the order of
|
|
11
|
+
// Topic Filters in the SUBSCRIBE Packet [MQTT-3.9.3-1].
|
|
12
|
+
const validSubscriptions = [];
|
|
13
|
+
const returnCodes = packet.subscriptions.map((sub) => {
|
|
14
|
+
if (ctx.store) {
|
|
15
|
+
if (!authorizedToSubscribe(ctx, sub.topicFilter)) {
|
|
16
|
+
return SubscriptionFailure;
|
|
17
|
+
}
|
|
18
|
+
ctx.persistence.subscribe(ctx.store, sub.topicFilter, sub.qos);
|
|
19
|
+
validSubscriptions.push(sub);
|
|
20
|
+
return sub.qos;
|
|
21
|
+
}
|
|
22
|
+
return SubscriptionFailure; // failure
|
|
23
|
+
});
|
|
24
|
+
await ctx.send({
|
|
25
|
+
type: PacketType.suback,
|
|
26
|
+
id: packet.id,
|
|
27
|
+
returnCodes: returnCodes,
|
|
28
|
+
});
|
|
29
|
+
// send any retained messages that match these subscriptions
|
|
30
|
+
if (ctx.store) {
|
|
31
|
+
ctx.persistence.handleRetained(ctx.store.clientId);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { PacketType } from "../deps.js";
|
|
2
|
+
export async function handleUnsubscribe(ctx, packet) {
|
|
3
|
+
for (const topic of packet.topicFilters) {
|
|
4
|
+
if (ctx.store) {
|
|
5
|
+
ctx.persistence.unsubscribe(ctx.store, topic);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
await ctx.send({
|
|
9
|
+
type: PacketType.unsuback,
|
|
10
|
+
id: packet.id,
|
|
11
|
+
});
|
|
12
|
+
}
|