@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
package/dist/bin/mqtt.js
CHANGED
|
@@ -1,54 +1,8 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
p as parseArgs,
|
|
7
|
-
w as wrapNodeSocket,
|
|
8
|
-
} from "../wrapNodeSocket-BAJm059T.js";
|
|
9
|
-
import "node:process";
|
|
10
|
-
import { readFile } from "node:fs/promises";
|
|
11
|
-
import { connect } from "node:net";
|
|
12
|
-
import * as tls from "node:tls";
|
|
13
|
-
import "node:stream";
|
|
14
|
-
|
|
15
|
-
async function getCaCerts(filename) {
|
|
16
|
-
if (!filename) {
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
const caCerts = await readFile(filename, { encoding: "utf-8" });
|
|
20
|
-
if (caCerts === "") {
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
return [caCerts];
|
|
24
|
-
}
|
|
25
|
-
class TcpClient extends Client {
|
|
26
|
-
async connectMQTT(hostname, port = 1883) {
|
|
27
|
-
logger.debug({ hostname, port });
|
|
28
|
-
return wrapNodeSocket(await connect({ host: hostname, port }));
|
|
29
|
-
}
|
|
30
|
-
async connectMQTTS(hostname, port = 8883, caCerts) {
|
|
31
|
-
const opts = {
|
|
32
|
-
host: hostname,
|
|
33
|
-
port,
|
|
34
|
-
secureContext: caCerts
|
|
35
|
-
? tls.createSecureContext({ cert: caCerts })
|
|
36
|
-
: void 0,
|
|
37
|
-
};
|
|
38
|
-
logger.debug({ hostname, port, caCerts });
|
|
39
|
-
return wrapNodeSocket(await tls.connect(opts));
|
|
40
|
-
}
|
|
41
|
-
createConn(protocol, hostname, port, caCerts) {
|
|
42
|
-
if (protocol === "mqtts:") {
|
|
43
|
-
return this.connectMQTTS(hostname, port, caCerts);
|
|
44
|
-
}
|
|
45
|
-
if (protocol === "mqtt:") {
|
|
46
|
-
return this.connectMQTT(hostname, port);
|
|
47
|
-
}
|
|
48
|
-
throw `Unsupported protocol: ${protocol}`;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
1
|
+
#!/usr/bin/env -S node --experimental-strip-types
|
|
2
|
+
import { DEFAULT_URL } from "../client/mod.js";
|
|
3
|
+
import { logger, LogLevel } from "../utils/mod.js";
|
|
4
|
+
import { getArgs, parseArgs } from "../utils/mod.js";
|
|
5
|
+
import { getCaCerts, TcpClient } from "../node/client.js";
|
|
52
6
|
const client = new TcpClient();
|
|
53
7
|
const encoder = new TextEncoder();
|
|
54
8
|
const decoder = new TextDecoder();
|
|
@@ -69,20 +23,21 @@ const ConnectHelp = `
|
|
|
69
23
|
-h/--help this text
|
|
70
24
|
`;
|
|
71
25
|
const connectOpts = {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
26
|
+
string: ["url", "username", "password", "certFile", "clientId"],
|
|
27
|
+
alias: {
|
|
28
|
+
u: "url",
|
|
29
|
+
U: "username",
|
|
30
|
+
P: "password",
|
|
31
|
+
c: "certFile",
|
|
32
|
+
i: "clientId",
|
|
33
|
+
n: "noClean",
|
|
34
|
+
h: "help",
|
|
35
|
+
},
|
|
36
|
+
boolean: ["noClean", "help"],
|
|
37
|
+
default: {
|
|
38
|
+
noClean: false,
|
|
39
|
+
clientId: `Opifex-${crypto.randomUUID()}`,
|
|
40
|
+
},
|
|
86
41
|
};
|
|
87
42
|
const SubscribeHelp = `Usage: mqtt subscribe <options>
|
|
88
43
|
|
|
@@ -92,71 +47,75 @@ Where options are:
|
|
|
92
47
|
${ConnectHelp}
|
|
93
48
|
Example: mqtt subscribe -t hello`;
|
|
94
49
|
const subscribeOpts = {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
50
|
+
string: ["topic"],
|
|
51
|
+
alias: {
|
|
52
|
+
t: "topic",
|
|
53
|
+
q: "qos",
|
|
54
|
+
},
|
|
55
|
+
default: {
|
|
56
|
+
qos: 0,
|
|
57
|
+
topic: "",
|
|
58
|
+
},
|
|
104
59
|
};
|
|
105
60
|
function parseQos(qosArg) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
61
|
+
const qos = Number(qosArg);
|
|
62
|
+
switch (qos) {
|
|
63
|
+
case 0:
|
|
64
|
+
return 0;
|
|
65
|
+
case 1:
|
|
66
|
+
return 1;
|
|
67
|
+
case 2:
|
|
68
|
+
return 2;
|
|
69
|
+
default:
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
console.log("QoS must be between 0 and 2");
|
|
73
|
+
return 0;
|
|
117
74
|
}
|
|
118
75
|
async function subscribe() {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
if (subscribeArgs.topic === void 0) {
|
|
127
|
-
console.log("Missing `topic`");
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
try {
|
|
131
|
-
await client.connect({
|
|
132
|
-
url: connectArgs.url,
|
|
133
|
-
caCerts,
|
|
134
|
-
options: {
|
|
135
|
-
username: connectArgs.username,
|
|
136
|
-
password: encoder.encode(connectArgs.password),
|
|
137
|
-
clientId: connectArgs.clientId,
|
|
138
|
-
clean: !connectArgs.noClean,
|
|
139
|
-
keepAlive: 60,
|
|
140
|
-
},
|
|
141
|
-
});
|
|
142
|
-
logger.debug("Connected !");
|
|
143
|
-
client.subscribe({
|
|
144
|
-
subscriptions: [
|
|
145
|
-
{
|
|
146
|
-
topicFilter: subscribeArgs.topic,
|
|
147
|
-
qos: parseQos(subscribeArgs.qos),
|
|
148
|
-
},
|
|
149
|
-
],
|
|
150
|
-
});
|
|
151
|
-
logger.debug("Subscribed!");
|
|
152
|
-
for await (const message of client.messages()) {
|
|
153
|
-
console.log(decoder.decode(message.payload));
|
|
76
|
+
const connectArgs = parseArgs(getArgs(), connectOpts);
|
|
77
|
+
const caCerts = await getCaCerts(connectArgs.certFile);
|
|
78
|
+
const subscribeArgs = parseArgs(getArgs(), subscribeOpts);
|
|
79
|
+
if (connectArgs.help) {
|
|
80
|
+
console.log(SubscribeHelp);
|
|
81
|
+
return;
|
|
154
82
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
83
|
+
if (subscribeArgs.topic === undefined) {
|
|
84
|
+
console.log("Missing `topic`");
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
await client.connect({
|
|
89
|
+
url: connectArgs.url,
|
|
90
|
+
caCerts,
|
|
91
|
+
options: {
|
|
92
|
+
username: connectArgs.username,
|
|
93
|
+
password: encoder.encode(connectArgs.password),
|
|
94
|
+
clientId: connectArgs.clientId,
|
|
95
|
+
clean: !connectArgs.noClean,
|
|
96
|
+
keepAlive: 60,
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
logger.debug("Connected !");
|
|
100
|
+
client.subscribe({
|
|
101
|
+
subscriptions: [
|
|
102
|
+
{
|
|
103
|
+
topicFilter: subscribeArgs.topic,
|
|
104
|
+
qos: parseQos(subscribeArgs.qos),
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
});
|
|
108
|
+
logger.debug("Subscribed!");
|
|
109
|
+
for await (const message of client.messages()) {
|
|
110
|
+
console.log(decoder.decode(message.payload));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
if (err instanceof Error) {
|
|
115
|
+
// @ts-ignore the type spec of err is missing err.code
|
|
116
|
+
logger.info(`Error: ${err.message || err.code}`);
|
|
117
|
+
}
|
|
158
118
|
}
|
|
159
|
-
}
|
|
160
119
|
}
|
|
161
120
|
const PublishHelp = `Usage: mqtt publish <options>
|
|
162
121
|
|
|
@@ -168,73 +127,75 @@ Where options are:
|
|
|
168
127
|
${ConnectHelp}
|
|
169
128
|
Example: mqtt publish -t hello -m world`;
|
|
170
129
|
const publishOpts = {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
130
|
+
string: ["topic", "message"],
|
|
131
|
+
boolean: ["retain"],
|
|
132
|
+
alias: {
|
|
133
|
+
t: "topic",
|
|
134
|
+
m: "message",
|
|
135
|
+
q: "qos",
|
|
136
|
+
r: "retain",
|
|
137
|
+
},
|
|
138
|
+
default: {
|
|
139
|
+
qos: 0,
|
|
140
|
+
dup: false,
|
|
141
|
+
retain: false,
|
|
142
|
+
topic: undefined,
|
|
143
|
+
message: "",
|
|
144
|
+
},
|
|
186
145
|
};
|
|
187
146
|
async function publish() {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
147
|
+
const connectArgs = parseArgs(getArgs(), connectOpts);
|
|
148
|
+
const caCerts = await getCaCerts(connectArgs.certFile);
|
|
149
|
+
const publishArgs = parseArgs(getArgs(), publishOpts);
|
|
150
|
+
if (connectArgs.help) {
|
|
151
|
+
console.log(PublishHelp);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (publishArgs.topic === undefined) {
|
|
155
|
+
console.log("Missing `topic`");
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
await client.connect({
|
|
160
|
+
url: connectArgs.url,
|
|
161
|
+
caCerts,
|
|
162
|
+
options: {
|
|
163
|
+
username: connectArgs.username,
|
|
164
|
+
password: encoder.encode(connectArgs.password),
|
|
165
|
+
clientId: connectArgs.clientId,
|
|
166
|
+
clean: !connectArgs.noClean,
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
logger.debug("Connected !");
|
|
170
|
+
await client.publish({
|
|
171
|
+
topic: publishArgs.topic,
|
|
172
|
+
payload: encoder.encode(publishArgs.message),
|
|
173
|
+
retain: publishArgs.retain,
|
|
174
|
+
qos: parseQos(publishArgs.qos),
|
|
175
|
+
});
|
|
176
|
+
logger.debug("Published!");
|
|
177
|
+
client.disconnect();
|
|
178
|
+
logger.debug("Disconnected !");
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
if (err instanceof Error) {
|
|
182
|
+
// @ts-ignore the type spec of err is missing err.code
|
|
183
|
+
logger.info(`Error: ${err.message || err.code}`);
|
|
184
|
+
}
|
|
223
185
|
}
|
|
224
|
-
}
|
|
225
186
|
}
|
|
226
187
|
function processArgs() {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
188
|
+
const { _: [cmd] } = parseArgs(getArgs());
|
|
189
|
+
switch (cmd) {
|
|
190
|
+
case "publish":
|
|
191
|
+
publish();
|
|
192
|
+
break;
|
|
193
|
+
case "subscribe":
|
|
194
|
+
subscribe();
|
|
195
|
+
break;
|
|
196
|
+
default:
|
|
197
|
+
console.log(MQTTHelp);
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
239
200
|
}
|
|
240
201
|
processArgs();
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { Deferred, logger, MemoryStore, PacketType, } from "./deps.js";
|
|
2
|
+
import { Context } from "./context.js";
|
|
3
|
+
function generateClientId(prefix) {
|
|
4
|
+
return `${prefix}-${Math.random().toString().slice(-10)}`;
|
|
5
|
+
}
|
|
6
|
+
function backOffSleep(random, attempt) {
|
|
7
|
+
// based on https://dthain.blogspot.com/2009/02/exponential-backoff-in-distributed.html
|
|
8
|
+
const factor = 1.5;
|
|
9
|
+
const min = 1000;
|
|
10
|
+
const max = 5000;
|
|
11
|
+
const randomness = 1 + (random ? Math.random() : 0);
|
|
12
|
+
const delay = Math.floor(Math.min(randomness * min * (factor ** attempt), max));
|
|
13
|
+
logger.debug({ delay });
|
|
14
|
+
return new Promise((resolve) => setTimeout(resolve, delay));
|
|
15
|
+
}
|
|
16
|
+
/** the default MQTT URL to connect to */
|
|
17
|
+
export const DEFAULT_URL = "mqtt://localhost:1883/";
|
|
18
|
+
const DEFAULT_KEEPALIVE = 60; // 60 seconds
|
|
19
|
+
const DEFAULT_RETRIES = 3; // on first connect
|
|
20
|
+
const CLIENTID_PREFIX = "opifex"; // on first connect
|
|
21
|
+
/**
|
|
22
|
+
* The Client class provides an MQTT Client that can be used to connect to
|
|
23
|
+
* a MQTT broker and publish/subscribe messages.
|
|
24
|
+
*
|
|
25
|
+
* The Client class is not meant to be used directly, but
|
|
26
|
+
* instead should be subclassed and the subclass should
|
|
27
|
+
* override the createConn() method to provide a
|
|
28
|
+
* connection type that is supported by the subclass.
|
|
29
|
+
*/
|
|
30
|
+
export class Client {
|
|
31
|
+
clientIdPrefix = CLIENTID_PREFIX;
|
|
32
|
+
numberOfRetries = DEFAULT_RETRIES;
|
|
33
|
+
url = new URL(DEFAULT_URL);
|
|
34
|
+
keepAlive = DEFAULT_KEEPALIVE;
|
|
35
|
+
autoReconnect = true;
|
|
36
|
+
caCerts;
|
|
37
|
+
cert;
|
|
38
|
+
key;
|
|
39
|
+
clientId;
|
|
40
|
+
ctx = new Context(new MemoryStore());
|
|
41
|
+
connectPacket;
|
|
42
|
+
constructor() {
|
|
43
|
+
this.clientId = generateClientId(this.clientIdPrefix);
|
|
44
|
+
this.numberOfRetries = DEFAULT_RETRIES;
|
|
45
|
+
}
|
|
46
|
+
createConn(protocol, _hostname, _port, _caCerts, _cert, _key) {
|
|
47
|
+
// if you need to support alternative connection types just
|
|
48
|
+
// overload this method in your subclass
|
|
49
|
+
throw `Unsupported protocol: ${protocol}`;
|
|
50
|
+
}
|
|
51
|
+
async doConnect() {
|
|
52
|
+
if (!this.connectPacket) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
let isReconnect = false;
|
|
56
|
+
let attempt = 1;
|
|
57
|
+
let lastMessage = new Error();
|
|
58
|
+
let tryConnect = true;
|
|
59
|
+
while (tryConnect) {
|
|
60
|
+
logger.debug(`${isReconnect ? "re" : ""}connecting`);
|
|
61
|
+
try {
|
|
62
|
+
const conn = await this.createConn(this.url.protocol, this.url.hostname, Number(this.url.port) ?? undefined, this.caCerts, this.cert, this.key);
|
|
63
|
+
// if we get this far we have a connection
|
|
64
|
+
tryConnect =
|
|
65
|
+
(await this.ctx.handleConnection(conn, this.connectPacket)) &&
|
|
66
|
+
this.autoReconnect;
|
|
67
|
+
logger.debug({ tryConnect });
|
|
68
|
+
isReconnect = true;
|
|
69
|
+
this.connectPacket.clean = false;
|
|
70
|
+
this.ctx.close();
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
if (err instanceof Error) {
|
|
74
|
+
lastMessage = err;
|
|
75
|
+
}
|
|
76
|
+
logger.debug(lastMessage);
|
|
77
|
+
if (!isReconnect && attempt > this.numberOfRetries) {
|
|
78
|
+
tryConnect = false;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
await backOffSleep(true, attempt++);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (isReconnect === false) {
|
|
86
|
+
this.ctx.unresolvedConnect?.reject(lastMessage);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
connect(params = {}) {
|
|
90
|
+
this.url = params?.url || this.url;
|
|
91
|
+
this.numberOfRetries = params.numberOfRetries || this.numberOfRetries;
|
|
92
|
+
this.caCerts = params?.caCerts;
|
|
93
|
+
this.cert = params?.cert;
|
|
94
|
+
this.key = params?.key;
|
|
95
|
+
const options = Object.assign({
|
|
96
|
+
keepAlive: this.keepAlive,
|
|
97
|
+
clientId: this.clientId,
|
|
98
|
+
}, params?.options);
|
|
99
|
+
this.connectPacket = {
|
|
100
|
+
type: PacketType.connect,
|
|
101
|
+
...options,
|
|
102
|
+
};
|
|
103
|
+
const deferred = new Deferred();
|
|
104
|
+
this.ctx.unresolvedConnect = deferred;
|
|
105
|
+
this.doConnect();
|
|
106
|
+
return deferred.promise;
|
|
107
|
+
}
|
|
108
|
+
async disconnect() {
|
|
109
|
+
await this.ctx.disconnect();
|
|
110
|
+
}
|
|
111
|
+
async publish(params) {
|
|
112
|
+
const packet = {
|
|
113
|
+
type: PacketType.publish,
|
|
114
|
+
...params,
|
|
115
|
+
};
|
|
116
|
+
await this.ctx.send(packet);
|
|
117
|
+
}
|
|
118
|
+
async subscribe(params) {
|
|
119
|
+
const packet = {
|
|
120
|
+
type: PacketType.subscribe,
|
|
121
|
+
id: this.ctx.store.nextId(),
|
|
122
|
+
...params,
|
|
123
|
+
};
|
|
124
|
+
await this.ctx.send(packet);
|
|
125
|
+
}
|
|
126
|
+
async *messages() {
|
|
127
|
+
yield* this.ctx.incoming;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { AsyncQueue, Deferred, logger, MqttConn, PacketType, Timer, } from "./deps.js";
|
|
2
|
+
import { handlePacket } from "./handlers/handlePacket.js";
|
|
3
|
+
import { ConnectionState } from "./ConnectionState.js";
|
|
4
|
+
export class Context {
|
|
5
|
+
mqttConn;
|
|
6
|
+
connectionState;
|
|
7
|
+
pingTimer;
|
|
8
|
+
unresolvedConnect;
|
|
9
|
+
unresolvedPublish;
|
|
10
|
+
unresolvedSubscribe;
|
|
11
|
+
unresolvedUnSubscribe;
|
|
12
|
+
store;
|
|
13
|
+
incoming;
|
|
14
|
+
constructor(store) {
|
|
15
|
+
this.store = store;
|
|
16
|
+
this.connectionState = ConnectionState.offline;
|
|
17
|
+
this.incoming = new AsyncQueue();
|
|
18
|
+
this.unresolvedPublish = new Map();
|
|
19
|
+
this.unresolvedSubscribe = new Map();
|
|
20
|
+
this.unresolvedUnSubscribe = new Map();
|
|
21
|
+
}
|
|
22
|
+
async connect(packet) {
|
|
23
|
+
this.connectionState = ConnectionState.connecting;
|
|
24
|
+
await this.mqttConn?.send(packet);
|
|
25
|
+
const keepAlive = packet.keepAlive || 0;
|
|
26
|
+
if (keepAlive > 0) {
|
|
27
|
+
this.pingTimer = new Timer(this.sendPing.bind(this), keepAlive * 1000, true);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async disconnect() {
|
|
31
|
+
if (this.connectionState !== ConnectionState.connected) {
|
|
32
|
+
throw "Not connected";
|
|
33
|
+
}
|
|
34
|
+
if (this.mqttConn) {
|
|
35
|
+
this.connectionState = ConnectionState.disconnecting;
|
|
36
|
+
await this.mqttConn.send({ type: PacketType.disconnect });
|
|
37
|
+
this.mqttConn.close();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async send(packet) {
|
|
41
|
+
logger.debug({ send: packet });
|
|
42
|
+
if (this.connectionState === ConnectionState.connected &&
|
|
43
|
+
!this.mqttConn?.isClosed) {
|
|
44
|
+
await this.mqttConn?.send(packet);
|
|
45
|
+
this.pingTimer?.reset();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
logger.debug("not connected");
|
|
49
|
+
this.pingTimer?.clear();
|
|
50
|
+
}
|
|
51
|
+
sendPing() {
|
|
52
|
+
this.send({ type: PacketType.pingreq });
|
|
53
|
+
}
|
|
54
|
+
async handleConnection(conn, connectPacket) {
|
|
55
|
+
this.mqttConn = new MqttConn({ conn });
|
|
56
|
+
if (this.mqttConn === undefined) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
logger.debug("Send connect packet");
|
|
60
|
+
await this.connect(connectPacket);
|
|
61
|
+
try {
|
|
62
|
+
logger.debug("Accepting packets");
|
|
63
|
+
for await (const packet of this.mqttConn) {
|
|
64
|
+
handlePacket(this, packet);
|
|
65
|
+
}
|
|
66
|
+
logger.debug("No more packets");
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
logger.debug(err);
|
|
70
|
+
if (this.mqttConn.isClosed) {
|
|
71
|
+
this.mqttConn.close();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (this.connectionState === ConnectionState.disconnecting) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
close() {
|
|
80
|
+
logger.debug("closing connection");
|
|
81
|
+
this.connectionState = ConnectionState.disconnected;
|
|
82
|
+
this.pingTimer?.clear();
|
|
83
|
+
}
|
|
84
|
+
receivePublish(packet) {
|
|
85
|
+
this.incoming.push(packet);
|
|
86
|
+
}
|
|
87
|
+
publish(packet) {
|
|
88
|
+
const qos = packet.qos || 0;
|
|
89
|
+
if (qos === 0) {
|
|
90
|
+
packet.id = 0;
|
|
91
|
+
this.send(packet);
|
|
92
|
+
// return empty promise
|
|
93
|
+
return Promise.resolve();
|
|
94
|
+
}
|
|
95
|
+
packet.id = this.store.nextId();
|
|
96
|
+
this.store.pendingOutgoing.set(packet.id, packet);
|
|
97
|
+
const deferred = new Deferred();
|
|
98
|
+
this.unresolvedPublish.set(packet.id, deferred);
|
|
99
|
+
this.send(packet);
|
|
100
|
+
return deferred.promise;
|
|
101
|
+
}
|
|
102
|
+
subscribe(packet) {
|
|
103
|
+
packet.id = this.store.nextId();
|
|
104
|
+
this.store.pendingOutgoing.set(packet.id, packet);
|
|
105
|
+
const deferred = new Deferred();
|
|
106
|
+
this.unresolvedSubscribe.set(packet.id, deferred);
|
|
107
|
+
this.send(packet);
|
|
108
|
+
return deferred.promise;
|
|
109
|
+
}
|
|
110
|
+
unsubscribe(packet) {
|
|
111
|
+
packet.id = this.store.nextId();
|
|
112
|
+
this.store.pendingOutgoing.set(packet.id, packet);
|
|
113
|
+
const deferred = new Deferred();
|
|
114
|
+
this.unresolvedUnSubscribe.set(packet.id, deferred);
|
|
115
|
+
this.send(packet);
|
|
116
|
+
return deferred.promise;
|
|
117
|
+
}
|
|
118
|
+
receivePuback(id) {
|
|
119
|
+
const unresolvedMap = this.unresolvedPublish;
|
|
120
|
+
if (unresolvedMap.has(id)) {
|
|
121
|
+
const deferred = unresolvedMap.get(id);
|
|
122
|
+
unresolvedMap.delete(id);
|
|
123
|
+
deferred?.resolve();
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
receiveSuback(id, returnCodes) {
|
|
129
|
+
const unresolvedMap = this.unresolvedSubscribe;
|
|
130
|
+
if (unresolvedMap.has(id)) {
|
|
131
|
+
const deferred = unresolvedMap.get(id);
|
|
132
|
+
unresolvedMap.delete(id);
|
|
133
|
+
deferred?.resolve(returnCodes);
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
receiveUnsuback(id) {
|
|
139
|
+
const unresolvedMap = this.unresolvedUnSubscribe;
|
|
140
|
+
if (unresolvedMap.has(id)) {
|
|
141
|
+
const deferred = unresolvedMap.get(id);
|
|
142
|
+
unresolvedMap.delete(id);
|
|
143
|
+
deferred?.resolve();
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { AuthenticationResult, AuthenticationResultByNumber, decodePayload, encode, PacketNameByType, PacketType, } from "../mqttPacket/mod.js";
|
|
2
|
+
export { MqttConn } from "../mqttConn/mqttConn.js";
|
|
3
|
+
export { Timer } from "../timer/timer.js";
|
|
4
|
+
export { AsyncQueue, Deferred, logger } from "../utils/mod.js";
|
|
5
|
+
export { MemoryStore } from "./store/memoryStore.js";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ConnectionState } from "../ConnectionState.js";
|
|
2
|
+
import { AuthenticationResultByNumber } from "../deps.js";
|
|
3
|
+
export async function handleConnack(packet, ctx) {
|
|
4
|
+
if (packet.returnCode === 0) {
|
|
5
|
+
ctx.connectionState = ConnectionState.connected;
|
|
6
|
+
ctx.pingTimer?.reset();
|
|
7
|
+
ctx.unresolvedConnect?.resolve(packet.returnCode);
|
|
8
|
+
// start transmitting packets that were queued before
|
|
9
|
+
for await (const packet of ctx.store.pendingOutgoingPackets()) {
|
|
10
|
+
ctx.send(packet);
|
|
11
|
+
}
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const err = new Error(`Connect failed: ${AuthenticationResultByNumber[packet.returnCode]}`);
|
|
15
|
+
ctx.connectionState = ConnectionState.disconnecting;
|
|
16
|
+
ctx.pingTimer?.clear();
|
|
17
|
+
ctx.unresolvedConnect?.reject(err);
|
|
18
|
+
}
|