@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.
Files changed (64) hide show
  1. package/README.md +399 -0
  2. package/android/NGTCP2_BUILD_INSTRUCTIONS.md +319 -0
  3. package/android/build-nghttp3.sh +182 -0
  4. package/android/build-ngtcp2.sh +289 -0
  5. package/android/build-openssl.sh +302 -0
  6. package/android/build.gradle +75 -0
  7. package/android/gradle.properties +3 -0
  8. package/android/proguard-rules.pro +2 -0
  9. package/android/settings.gradle +1 -0
  10. package/android/src/main/AndroidManifest.xml +1 -0
  11. package/android/src/main/assets/mqttquic_ca.pem +5 -0
  12. package/android/src/main/cpp/CMakeLists.txt +157 -0
  13. package/android/src/main/cpp/ngtcp2_jni.cpp +928 -0
  14. package/android/src/main/kotlin/ai/annadata/mqttquic/MqttQuicPlugin.kt +232 -0
  15. package/android/src/main/kotlin/ai/annadata/mqttquic/client/MQTTClient.kt +339 -0
  16. package/android/src/main/kotlin/ai/annadata/mqttquic/mqtt/MQTT5Properties.kt +250 -0
  17. package/android/src/main/kotlin/ai/annadata/mqttquic/mqtt/MQTT5Protocol.kt +281 -0
  18. package/android/src/main/kotlin/ai/annadata/mqttquic/mqtt/MQTT5ReasonCodes.kt +109 -0
  19. package/android/src/main/kotlin/ai/annadata/mqttquic/mqtt/MQTTProtocol.kt +249 -0
  20. package/android/src/main/kotlin/ai/annadata/mqttquic/mqtt/MQTTTypes.kt +47 -0
  21. package/android/src/main/kotlin/ai/annadata/mqttquic/quic/NGTCP2Client.kt +184 -0
  22. package/android/src/main/kotlin/ai/annadata/mqttquic/quic/QuicClientStub.kt +54 -0
  23. package/android/src/main/kotlin/ai/annadata/mqttquic/quic/QuicTypes.kt +21 -0
  24. package/android/src/main/kotlin/ai/annadata/mqttquic/transport/QUICStreamAdapter.kt +37 -0
  25. package/android/src/main/kotlin/ai/annadata/mqttquic/transport/StreamTransport.kt +91 -0
  26. package/android/src/test/kotlin/ai/annadata/mqttquic/mqtt/MQTTProtocolTest.kt +92 -0
  27. package/dist/esm/definitions.d.ts +66 -0
  28. package/dist/esm/definitions.d.ts.map +1 -0
  29. package/dist/esm/definitions.js +2 -0
  30. package/dist/esm/definitions.js.map +1 -0
  31. package/dist/esm/index.d.ts +5 -0
  32. package/dist/esm/index.d.ts.map +1 -0
  33. package/dist/esm/index.js +7 -0
  34. package/dist/esm/index.js.map +1 -0
  35. package/dist/esm/web.d.ts +28 -0
  36. package/dist/esm/web.d.ts.map +1 -0
  37. package/dist/esm/web.js +183 -0
  38. package/dist/esm/web.js.map +1 -0
  39. package/dist/plugin.cjs.js +217 -0
  40. package/dist/plugin.cjs.js.map +1 -0
  41. package/dist/plugin.js +215 -0
  42. package/dist/plugin.js.map +1 -0
  43. package/ios/MqttQuicPlugin.podspec +34 -0
  44. package/ios/NGTCP2_BUILD_INSTRUCTIONS.md +302 -0
  45. package/ios/Sources/MqttQuicPlugin/Client/MQTTClient.swift +343 -0
  46. package/ios/Sources/MqttQuicPlugin/MQTT/MQTT5Properties.swift +280 -0
  47. package/ios/Sources/MqttQuicPlugin/MQTT/MQTT5Protocol.swift +333 -0
  48. package/ios/Sources/MqttQuicPlugin/MQTT/MQTT5ReasonCodes.swift +113 -0
  49. package/ios/Sources/MqttQuicPlugin/MQTT/MQTTProtocol.swift +322 -0
  50. package/ios/Sources/MqttQuicPlugin/MQTT/MQTTTypes.swift +54 -0
  51. package/ios/Sources/MqttQuicPlugin/MqttQuicPlugin.swift +229 -0
  52. package/ios/Sources/MqttQuicPlugin/QUIC/NGTCP2Bridge.h +29 -0
  53. package/ios/Sources/MqttQuicPlugin/QUIC/NGTCP2Bridge.mm +865 -0
  54. package/ios/Sources/MqttQuicPlugin/QUIC/NGTCP2Client.swift +262 -0
  55. package/ios/Sources/MqttQuicPlugin/QUIC/QuicClientStub.swift +66 -0
  56. package/ios/Sources/MqttQuicPlugin/QUIC/QuicTypes.swift +23 -0
  57. package/ios/Sources/MqttQuicPlugin/Resources/mqttquic_ca.pem +5 -0
  58. package/ios/Sources/MqttQuicPlugin/Transport/QUICStreamAdapter.swift +50 -0
  59. package/ios/Sources/MqttQuicPlugin/Transport/StreamTransport.swift +105 -0
  60. package/ios/Tests/MQTTProtocolTests.swift +82 -0
  61. package/ios/build-nghttp3.sh +173 -0
  62. package/ios/build-ngtcp2.sh +278 -0
  63. package/ios/build-openssl.sh +405 -0
  64. 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