@annadata/capacitor-mqtt-quic 0.1.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/README.md +399 -0
- package/android/NGTCP2_BUILD_INSTRUCTIONS.md +319 -0
- package/android/build-nghttp3.sh +182 -0
- package/android/build-ngtcp2.sh +289 -0
- package/android/build-openssl.sh +302 -0
- package/android/build.gradle +75 -0
- package/android/gradle.properties +3 -0
- package/android/proguard-rules.pro +2 -0
- package/android/settings.gradle +1 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/assets/mqttquic_ca.pem +5 -0
- package/android/src/main/cpp/CMakeLists.txt +157 -0
- package/android/src/main/cpp/ngtcp2_jni.cpp +928 -0
- package/android/src/main/kotlin/ai/annadata/mqttquic/MqttQuicPlugin.kt +232 -0
- package/android/src/main/kotlin/ai/annadata/mqttquic/client/MQTTClient.kt +339 -0
- package/android/src/main/kotlin/ai/annadata/mqttquic/mqtt/MQTT5Properties.kt +250 -0
- package/android/src/main/kotlin/ai/annadata/mqttquic/mqtt/MQTT5Protocol.kt +281 -0
- package/android/src/main/kotlin/ai/annadata/mqttquic/mqtt/MQTT5ReasonCodes.kt +109 -0
- package/android/src/main/kotlin/ai/annadata/mqttquic/mqtt/MQTTProtocol.kt +249 -0
- package/android/src/main/kotlin/ai/annadata/mqttquic/mqtt/MQTTTypes.kt +47 -0
- package/android/src/main/kotlin/ai/annadata/mqttquic/quic/NGTCP2Client.kt +184 -0
- package/android/src/main/kotlin/ai/annadata/mqttquic/quic/QuicClientStub.kt +54 -0
- package/android/src/main/kotlin/ai/annadata/mqttquic/quic/QuicTypes.kt +21 -0
- package/android/src/main/kotlin/ai/annadata/mqttquic/transport/QUICStreamAdapter.kt +37 -0
- package/android/src/main/kotlin/ai/annadata/mqttquic/transport/StreamTransport.kt +91 -0
- package/android/src/test/kotlin/ai/annadata/mqttquic/mqtt/MQTTProtocolTest.kt +92 -0
- package/dist/esm/definitions.d.ts +66 -0
- package/dist/esm/definitions.d.ts.map +1 -0
- package/dist/esm/definitions.js +2 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +28 -0
- package/dist/esm/web.d.ts.map +1 -0
- package/dist/esm/web.js +183 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +217 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +215 -0
- package/dist/plugin.js.map +1 -0
- package/ios/MqttQuicPlugin.podspec +34 -0
- package/ios/NGTCP2_BUILD_INSTRUCTIONS.md +302 -0
- package/ios/Sources/MqttQuicPlugin/Client/MQTTClient.swift +343 -0
- package/ios/Sources/MqttQuicPlugin/MQTT/MQTT5Properties.swift +280 -0
- package/ios/Sources/MqttQuicPlugin/MQTT/MQTT5Protocol.swift +333 -0
- package/ios/Sources/MqttQuicPlugin/MQTT/MQTT5ReasonCodes.swift +113 -0
- package/ios/Sources/MqttQuicPlugin/MQTT/MQTTProtocol.swift +322 -0
- package/ios/Sources/MqttQuicPlugin/MQTT/MQTTTypes.swift +54 -0
- package/ios/Sources/MqttQuicPlugin/MqttQuicPlugin.swift +229 -0
- package/ios/Sources/MqttQuicPlugin/QUIC/NGTCP2Bridge.h +29 -0
- package/ios/Sources/MqttQuicPlugin/QUIC/NGTCP2Bridge.mm +865 -0
- package/ios/Sources/MqttQuicPlugin/QUIC/NGTCP2Client.swift +262 -0
- package/ios/Sources/MqttQuicPlugin/QUIC/QuicClientStub.swift +66 -0
- package/ios/Sources/MqttQuicPlugin/QUIC/QuicTypes.swift +23 -0
- package/ios/Sources/MqttQuicPlugin/Resources/mqttquic_ca.pem +5 -0
- package/ios/Sources/MqttQuicPlugin/Transport/QUICStreamAdapter.swift +50 -0
- package/ios/Sources/MqttQuicPlugin/Transport/StreamTransport.swift +105 -0
- package/ios/Tests/MQTTProtocolTests.swift +82 -0
- package/ios/build-nghttp3.sh +173 -0
- package/ios/build-ngtcp2.sh +278 -0
- package/ios/build-openssl.sh +405 -0
- package/package.json +63 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
//
|
|
2
|
+
// MqttQuicPlugin.swift
|
|
3
|
+
// MqttQuicPlugin
|
|
4
|
+
//
|
|
5
|
+
// Capacitor plugin bridge. Phase 3: connect/publish/subscribe call into MQTTClient.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
import Capacitor
|
|
10
|
+
|
|
11
|
+
@objc(MqttQuicPlugin)
|
|
12
|
+
public class MqttQuicPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
13
|
+
|
|
14
|
+
private var client = MQTTClient(protocolVersion: .auto)
|
|
15
|
+
|
|
16
|
+
@objc override public func load() {}
|
|
17
|
+
|
|
18
|
+
private func bundledCaPath() -> String? {
|
|
19
|
+
let bundle = Bundle(for: MqttQuicPlugin.self)
|
|
20
|
+
guard let path = bundle.path(forResource: "mqttquic_ca", ofType: "pem") else {
|
|
21
|
+
return nil
|
|
22
|
+
}
|
|
23
|
+
if let contents = try? String(contentsOfFile: path),
|
|
24
|
+
contents.contains("BEGIN CERTIFICATE") {
|
|
25
|
+
return path
|
|
26
|
+
}
|
|
27
|
+
return nil
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@objc func connect(_ call: CAPPluginCall) {
|
|
31
|
+
let host = call.getString("host") ?? ""
|
|
32
|
+
let port = call.getInt("port") ?? 1884
|
|
33
|
+
let clientId = call.getString("clientId") ?? ""
|
|
34
|
+
let username = call.getString("username")
|
|
35
|
+
let password = call.getString("password")
|
|
36
|
+
let cleanSession = call.getBool("cleanSession") ?? true
|
|
37
|
+
let keepalive = call.getInt("keepalive") ?? 60
|
|
38
|
+
let protocolVersionStr = call.getString("protocolVersion") ?? "auto"
|
|
39
|
+
let sessionExpiryInterval = call.getInt("sessionExpiryInterval")
|
|
40
|
+
let caFile = call.getString("caFile")
|
|
41
|
+
let caPath = call.getString("caPath")
|
|
42
|
+
|
|
43
|
+
let protocolVersion: MQTTClient.ProtocolVersion
|
|
44
|
+
switch protocolVersionStr {
|
|
45
|
+
case "5.0": protocolVersion = .v5
|
|
46
|
+
case "3.1.1": protocolVersion = .v311
|
|
47
|
+
default: protocolVersion = .auto
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
guard !host.isEmpty, !clientId.isEmpty else {
|
|
51
|
+
call.reject("host and clientId are required")
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
Task {
|
|
56
|
+
do {
|
|
57
|
+
if let caFile = caFile {
|
|
58
|
+
setenv("MQTT_QUIC_CA_FILE", caFile, 1)
|
|
59
|
+
} else if let bundled = bundledCaPath() {
|
|
60
|
+
setenv("MQTT_QUIC_CA_FILE", bundled, 1)
|
|
61
|
+
} else {
|
|
62
|
+
unsetenv("MQTT_QUIC_CA_FILE")
|
|
63
|
+
}
|
|
64
|
+
if let caPath = caPath {
|
|
65
|
+
setenv("MQTT_QUIC_CA_PATH", caPath, 1)
|
|
66
|
+
} else {
|
|
67
|
+
unsetenv("MQTT_QUIC_CA_PATH")
|
|
68
|
+
}
|
|
69
|
+
if case .connected = client.getState() {
|
|
70
|
+
try? await client.disconnect()
|
|
71
|
+
}
|
|
72
|
+
client = MQTTClient(protocolVersion: protocolVersion)
|
|
73
|
+
try await client.connect(
|
|
74
|
+
host: host,
|
|
75
|
+
port: UInt16(port),
|
|
76
|
+
clientId: clientId,
|
|
77
|
+
username: username,
|
|
78
|
+
password: password,
|
|
79
|
+
cleanSession: cleanSession,
|
|
80
|
+
keepalive: UInt16(keepalive),
|
|
81
|
+
sessionExpiryInterval: sessionExpiryInterval != nil ? UInt32(sessionExpiryInterval!) : nil
|
|
82
|
+
)
|
|
83
|
+
call.resolve(["connected": true])
|
|
84
|
+
} catch {
|
|
85
|
+
call.reject("\(error)")
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@objc func testHarness(_ call: CAPPluginCall) {
|
|
91
|
+
let host = call.getString("host") ?? ""
|
|
92
|
+
let port = call.getInt("port") ?? 1884
|
|
93
|
+
let clientId = call.getString("clientId") ?? "mqttquic_test_client"
|
|
94
|
+
let topic = call.getString("topic") ?? "test/topic"
|
|
95
|
+
let payload = call.getString("payload") ?? "Hello QUIC!"
|
|
96
|
+
let caFile = call.getString("caFile")
|
|
97
|
+
let caPath = call.getString("caPath")
|
|
98
|
+
|
|
99
|
+
if host.isEmpty {
|
|
100
|
+
call.reject("host is required")
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
Task {
|
|
105
|
+
do {
|
|
106
|
+
if let caFile = caFile {
|
|
107
|
+
setenv("MQTT_QUIC_CA_FILE", caFile, 1)
|
|
108
|
+
} else if let bundled = bundledCaPath() {
|
|
109
|
+
setenv("MQTT_QUIC_CA_FILE", bundled, 1)
|
|
110
|
+
} else {
|
|
111
|
+
unsetenv("MQTT_QUIC_CA_FILE")
|
|
112
|
+
}
|
|
113
|
+
if let caPath = caPath {
|
|
114
|
+
setenv("MQTT_QUIC_CA_PATH", caPath, 1)
|
|
115
|
+
} else {
|
|
116
|
+
unsetenv("MQTT_QUIC_CA_PATH")
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
client = MQTTClient(protocolVersion: .auto)
|
|
120
|
+
try await client.connect(
|
|
121
|
+
host: host,
|
|
122
|
+
port: UInt16(port),
|
|
123
|
+
clientId: clientId,
|
|
124
|
+
username: nil,
|
|
125
|
+
password: nil,
|
|
126
|
+
cleanSession: true,
|
|
127
|
+
keepalive: 60,
|
|
128
|
+
sessionExpiryInterval: nil
|
|
129
|
+
)
|
|
130
|
+
try await client.subscribe(topic: topic, qos: 0, subscriptionIdentifier: nil)
|
|
131
|
+
try await client.publish(topic: topic, payload: Data(payload.utf8), qos: 0)
|
|
132
|
+
try await client.disconnect()
|
|
133
|
+
call.resolve(["success": true])
|
|
134
|
+
} catch {
|
|
135
|
+
call.reject("\(error)")
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@objc func disconnect(_ call: CAPPluginCall) {
|
|
141
|
+
Task {
|
|
142
|
+
do {
|
|
143
|
+
try await client.disconnect()
|
|
144
|
+
call.resolve()
|
|
145
|
+
} catch {
|
|
146
|
+
call.reject("\(error)")
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
@objc func publish(_ call: CAPPluginCall) {
|
|
152
|
+
let topic = call.getString("topic") ?? ""
|
|
153
|
+
let qos = call.getInt("qos") ?? 0
|
|
154
|
+
let messageExpiryInterval = call.getInt("messageExpiryInterval")
|
|
155
|
+
let contentType = call.getString("contentType")
|
|
156
|
+
|
|
157
|
+
guard !topic.isEmpty else {
|
|
158
|
+
call.reject("topic is required")
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let data: Data
|
|
163
|
+
if let payloadStr = call.getString("payload") {
|
|
164
|
+
data = Data(payloadStr.utf8)
|
|
165
|
+
} else if let arr = call.getArray("payload") as? [NSNumber] {
|
|
166
|
+
data = Data(arr.map { UInt8(truncating: $0) })
|
|
167
|
+
} else {
|
|
168
|
+
call.reject("payload must be string or number array")
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
Task {
|
|
173
|
+
do {
|
|
174
|
+
var properties: [UInt8: Any]? = nil
|
|
175
|
+
if messageExpiryInterval != nil || contentType != nil {
|
|
176
|
+
properties = [:]
|
|
177
|
+
if let mei = messageExpiryInterval {
|
|
178
|
+
properties![MQTT5PropertyType.messageExpiryInterval.rawValue] = UInt32(mei)
|
|
179
|
+
}
|
|
180
|
+
if let ct = contentType {
|
|
181
|
+
properties![MQTT5PropertyType.contentType.rawValue] = ct
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
try await client.publish(topic: topic, payload: data, qos: UInt8(min(qos, 2)), properties: properties)
|
|
185
|
+
call.resolve(["success": true])
|
|
186
|
+
} catch {
|
|
187
|
+
call.reject("\(error)")
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
@objc func subscribe(_ call: CAPPluginCall) {
|
|
193
|
+
let topic = call.getString("topic") ?? ""
|
|
194
|
+
let qos = call.getInt("qos") ?? 0
|
|
195
|
+
let subscriptionIdentifier = call.getInt("subscriptionIdentifier")
|
|
196
|
+
|
|
197
|
+
guard !topic.isEmpty else {
|
|
198
|
+
call.reject("topic is required")
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
Task {
|
|
203
|
+
do {
|
|
204
|
+
try await client.subscribe(topic: topic, qos: UInt8(min(qos, 2)), subscriptionIdentifier: subscriptionIdentifier)
|
|
205
|
+
call.resolve(["success": true])
|
|
206
|
+
} catch {
|
|
207
|
+
call.reject("\(error)")
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
@objc func unsubscribe(_ call: CAPPluginCall) {
|
|
213
|
+
let topic = call.getString("topic") ?? ""
|
|
214
|
+
|
|
215
|
+
guard !topic.isEmpty else {
|
|
216
|
+
call.reject("topic is required")
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
Task {
|
|
221
|
+
do {
|
|
222
|
+
try await client.unsubscribe(topic: topic)
|
|
223
|
+
call.resolve(["success": true])
|
|
224
|
+
} catch {
|
|
225
|
+
call.reject("\(error)")
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <stddef.h>
|
|
4
|
+
#include <stdint.h>
|
|
5
|
+
#include <sys/types.h>
|
|
6
|
+
|
|
7
|
+
#ifdef __cplusplus
|
|
8
|
+
extern "C" {
|
|
9
|
+
#endif
|
|
10
|
+
|
|
11
|
+
typedef void *NGTCP2ClientHandle;
|
|
12
|
+
|
|
13
|
+
NGTCP2ClientHandle ngtcp2_client_create(void);
|
|
14
|
+
void ngtcp2_client_destroy(NGTCP2ClientHandle handle);
|
|
15
|
+
|
|
16
|
+
int ngtcp2_client_connect(NGTCP2ClientHandle handle, const char *host, uint16_t port, const char *alpn);
|
|
17
|
+
int64_t ngtcp2_client_open_stream(NGTCP2ClientHandle handle);
|
|
18
|
+
int ngtcp2_client_write_stream(NGTCP2ClientHandle handle, int64_t stream_id,
|
|
19
|
+
const uint8_t *data, size_t datalen, int fin);
|
|
20
|
+
ssize_t ngtcp2_client_read_stream(NGTCP2ClientHandle handle, int64_t stream_id,
|
|
21
|
+
uint8_t *buffer, size_t maxlen);
|
|
22
|
+
int ngtcp2_client_close_stream(NGTCP2ClientHandle handle, int64_t stream_id);
|
|
23
|
+
int ngtcp2_client_close(NGTCP2ClientHandle handle);
|
|
24
|
+
int ngtcp2_client_is_connected(NGTCP2ClientHandle handle);
|
|
25
|
+
const char *ngtcp2_client_last_error(NGTCP2ClientHandle handle);
|
|
26
|
+
|
|
27
|
+
#ifdef __cplusplus
|
|
28
|
+
}
|
|
29
|
+
#endif
|