@seriousme/opifex 1.2.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/.github/workflows/update-license-year.sh +14 -0
  2. package/.github/workflows/update-license-year.yml +13 -0
  3. package/CHANGELOG.md +16 -0
  4. package/LICENSE.txt +1 -1
  5. package/README.md +72 -4
  6. package/bin/conformanceServer.ts +1 -1
  7. package/bin/mqtt.ts +5 -2
  8. package/client/client.ts +13 -3
  9. package/client/context.ts +2 -0
  10. package/client/mod.ts +1 -2
  11. package/deno/client.ts +10 -5
  12. package/deno/server.ts +3 -2
  13. package/deno.json +4 -1
  14. package/deno.lock +11 -8
  15. package/dev_utils/dummyConn.ts +4 -4
  16. package/dist/bin/conformanceServer.js +52 -0
  17. package/dist/bin/demoServer.js +29 -57
  18. package/dist/bin/mqtt.js +148 -187
  19. package/dist/client/ConnectionState.js +7 -0
  20. package/dist/client/client.js +129 -0
  21. package/dist/client/context.js +148 -0
  22. package/dist/client/deps.js +5 -0
  23. package/dist/client/handlers/handleConnack.js +18 -0
  24. package/dist/client/handlers/handlePacket.js +50 -0
  25. package/dist/client/handlers/handlePuback.js +6 -0
  26. package/dist/client/handlers/handlePubcomp.js +9 -0
  27. package/dist/client/handlers/handlePublish.js +28 -0
  28. package/dist/client/handlers/handlePubrec.js +17 -0
  29. package/dist/client/handlers/handlePubrel.js +19 -0
  30. package/dist/client/handlers/handleSuback.js +9 -0
  31. package/dist/client/handlers/handleUnsuback.js +7 -0
  32. package/dist/client/mod.js +5 -3
  33. package/dist/client/store/deps.js +1 -0
  34. package/dist/client/store/memoryStore.js +36 -0
  35. package/dist/client/store/store.js +1 -0
  36. package/dist/mqttConn/mqttConn.js +104 -0
  37. package/dist/mqttPacket/AuthenticationResult.js +12 -0
  38. package/dist/mqttPacket/BitMask.js +10 -0
  39. package/dist/mqttPacket/PacketType.js +18 -0
  40. package/dist/mqttPacket/connack.js +27 -0
  41. package/dist/mqttPacket/connect.js +115 -0
  42. package/dist/mqttPacket/decoder.js +89 -0
  43. package/dist/mqttPacket/disconnect.js +14 -0
  44. package/dist/mqttPacket/encoder.js +56 -0
  45. package/dist/mqttPacket/length.js +43 -0
  46. package/dist/mqttPacket/mod.js +71 -0
  47. package/dist/mqttPacket/pingreq.js +15 -0
  48. package/dist/mqttPacket/pingres.js +14 -0
  49. package/dist/mqttPacket/puback.js +20 -0
  50. package/dist/mqttPacket/pubcomp.js +20 -0
  51. package/dist/mqttPacket/publish.js +51 -0
  52. package/dist/mqttPacket/pubrec.js +20 -0
  53. package/dist/mqttPacket/pubrel.js +20 -0
  54. package/dist/mqttPacket/suback.js +29 -0
  55. package/dist/mqttPacket/subscribe.js +52 -0
  56. package/dist/mqttPacket/types.js +1 -0
  57. package/dist/mqttPacket/unsuback.js +20 -0
  58. package/dist/mqttPacket/unsubscribe.js +36 -0
  59. package/dist/mqttPacket/validators.js +15 -0
  60. package/dist/node/client.js +64 -0
  61. package/dist/node/server.js +22 -0
  62. package/dist/node/wrapNodeSocket.js +47 -0
  63. package/dist/persistence/deps.js +3 -0
  64. package/dist/persistence/memory/memoryPersistence.js +117 -0
  65. package/dist/persistence/persistence.js +1 -0
  66. package/dist/persistence/store.js +1 -0
  67. package/dist/server/context.js +100 -0
  68. package/dist/server/deps.js +5 -0
  69. package/dist/server/handlers/handleConnect.js +46 -0
  70. package/dist/server/handlers/handleDisconnect.js +4 -0
  71. package/dist/server/handlers/handlePacket.js +58 -0
  72. package/dist/server/handlers/handlePingreq.js +6 -0
  73. package/dist/server/handlers/handlePuback.js +8 -0
  74. package/dist/server/handlers/handlePubcomp.js +8 -0
  75. package/dist/server/handlers/handlePublish.js +40 -0
  76. package/dist/server/handlers/handlePubrec.js +15 -0
  77. package/dist/server/handlers/handlePubrel.js +18 -0
  78. package/dist/server/handlers/handleSubscribe.js +33 -0
  79. package/dist/server/handlers/handleUnsubscribe.js +12 -0
  80. package/dist/server/mod.js +7 -589
  81. package/dist/server/server.js +47 -0
  82. package/dist/socket/socket.js +36 -0
  83. package/dist/timer/timer.js +40 -0
  84. package/dist/trie/trie.js +95 -0
  85. package/dist/tsconfig.tsbuildinfo +1 -0
  86. package/dist/utils/args.js +61 -0
  87. package/dist/utils/assert.js +19 -0
  88. package/dist/utils/asyncQueue.js +56 -0
  89. package/dist/utils/deferred.js +11 -0
  90. package/dist/utils/logger.js +29 -0
  91. package/dist/utils/mod.js +7 -0
  92. package/dist/utils/nextTick.js +3 -0
  93. package/dist/utils/platform.js +4 -0
  94. package/mqttConn/mqttConn.ts +4 -1
  95. package/node/client.ts +29 -6
  96. package/node/server.ts +2 -0
  97. package/node/wrapNodeSocket.ts +1 -1
  98. package/package.json +17 -4
  99. package/server/mod.ts +1 -2
  100. package/socket/socket.ts +3 -2
  101. package/timer/timer.ts +1 -1
  102. package/tsconfig.json +3 -0
  103. package/.vscode/extensions.json +0 -10
  104. package/.vscode/settings.json +0 -12
  105. package/deno/wrapDenoConn.ts +0 -12
  106. package/dist/client-DqLTBVC2.js +0 -541
  107. package/dist/timer-DDWVNsyG.js +0 -924
  108. package/dist/wrapNodeSocket-BAJm059T.js +0 -115
package/dist/bin/mqtt.js CHANGED
@@ -1,54 +1,8 @@
1
- #!/usr/bin/env node
2
- import { C as Client, D as DEFAULT_URL } from "../client-DqLTBVC2.js";
3
- import { L as LogLevel, l as logger } from "../timer-DDWVNsyG.js";
4
- import {
5
- g as getArgs,
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
- string: ["url", "username", "password", "certFile", "clientId"],
73
- alias: {
74
- u: "url",
75
- U: "username",
76
- P: "password",
77
- c: "certFile",
78
- i: "clientId",
79
- n: "noClean",
80
- h: "help",
81
- },
82
- boolean: ["noClean", "help"],
83
- default: {
84
- noClean: false,
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
- string: ["topic"],
96
- alias: {
97
- t: "topic",
98
- q: "qos",
99
- },
100
- default: {
101
- qos: 0,
102
- topic: "",
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
- const qos = Number(qosArg);
107
- switch (qos) {
108
- case 0:
109
- return 0;
110
- case 1:
111
- return 1;
112
- case 2:
113
- return 2;
114
- }
115
- console.log("QoS must be between 0 and 2");
116
- return 0;
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
- const connectArgs = parseArgs(getArgs(), connectOpts);
120
- const caCerts = await getCaCerts(connectArgs.certFile);
121
- const subscribeArgs = parseArgs(getArgs(), subscribeOpts);
122
- if (connectArgs.help) {
123
- console.log(SubscribeHelp);
124
- return;
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
- } catch (err) {
156
- if (err instanceof Error) {
157
- logger.info(err.message);
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
- string: ["topic", "message"],
172
- boolean: ["retain"],
173
- alias: {
174
- t: "topic",
175
- m: "message",
176
- q: "qos",
177
- r: "retain",
178
- },
179
- default: {
180
- qos: 0,
181
- dup: false,
182
- retain: false,
183
- topic: void 0,
184
- message: "",
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
- const connectArgs = parseArgs(getArgs(), connectOpts);
189
- const caCerts = await getCaCerts(connectArgs.certFile);
190
- const publishArgs = parseArgs(getArgs(), publishOpts);
191
- if (connectArgs.help) {
192
- console.log(PublishHelp);
193
- return;
194
- }
195
- if (publishArgs.topic === void 0) {
196
- console.log("Missing `topic`");
197
- return;
198
- }
199
- try {
200
- await client.connect({
201
- url: connectArgs.url,
202
- caCerts,
203
- options: {
204
- username: connectArgs.username,
205
- password: encoder.encode(connectArgs.password),
206
- clientId: connectArgs.clientId,
207
- clean: !connectArgs.noClean,
208
- },
209
- });
210
- logger.debug("Connected !");
211
- await client.publish({
212
- topic: publishArgs.topic,
213
- payload: encoder.encode(publishArgs.message),
214
- retain: publishArgs.retain,
215
- qos: parseQos(publishArgs.qos),
216
- });
217
- logger.debug("Published!");
218
- client.disconnect();
219
- logger.debug("Disconnected !");
220
- } catch (err) {
221
- if (err instanceof Error) {
222
- logger.info(err.message);
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
- const { _: [cmd] } = parseArgs(getArgs());
228
- switch (cmd) {
229
- case "publish":
230
- publish();
231
- break;
232
- case "subscribe":
233
- subscribe();
234
- break;
235
- default:
236
- console.log(MQTTHelp);
237
- break;
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,7 @@
1
+ export const ConnectionState = {
2
+ offline: "offline",
3
+ connecting: "connecting",
4
+ connected: "connected",
5
+ disconnecting: "disconnecting",
6
+ disconnected: "disconnected",
7
+ };
@@ -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
+ }