@annadata/capacitor-mqtt-quic 0.1.7 → 0.1.8

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 (37) hide show
  1. package/AnnadataCapacitorMqttQuic.podspec +2 -2
  2. package/Package.swift +59 -0
  3. package/README.md +57 -17
  4. package/android/NGTCP2_BUILD_INSTRUCTIONS.md +1 -1
  5. package/android/app/src/main/assets/capacitor.config.json +1 -1
  6. package/android/build.gradle +4 -1
  7. package/android/install/wolfssl-android/arm64-v8a/lib/libwolfssl.a +0 -0
  8. package/android/install/wolfssl-android/armeabi-v7a/lib/libwolfssl.a +0 -0
  9. package/android/install/wolfssl-android/x86_64/lib/libwolfssl.a +0 -0
  10. package/android/src/main/cpp/CMakeLists.txt +19 -5
  11. package/android/src/main/cpp/ngtcp2_jni.cpp +28 -16
  12. package/android/src/main/kotlin/ai/annadata/mqttquic/MqttQuicPlugin.kt +3 -3
  13. package/android/src/main/kotlin/ai/annadata/mqttquic/client/MQTTClient.kt +3 -4
  14. package/android/src/main/kotlin/ai/annadata/mqttquic/mqtt/MQTTProtocol.kt +3 -3
  15. package/android/src/main/kotlin/ai/annadata/mqttquic/mqtt/MQTTTypes.kt +15 -15
  16. package/android/src/main/kotlin/ai/annadata/mqttquic/quic/NGTCP2Client.kt +1 -0
  17. package/docs/IMPLEMENTATION_SUMMARY.md +1 -1
  18. package/docs/NGTCP2_IMPLEMENTATION_STATUS.md +1 -1
  19. package/docs/PRODUCTION_PUBLISH_STEPS.md +9 -3
  20. package/ios/{MqttQuicPlugin.podspec → AnnadataCapacitorMqttQuic.podspec} +4 -4
  21. package/ios/App/App/capacitor.config.json +1 -1
  22. package/ios/NGTCP2_BUILD_INSTRUCTIONS.md +3 -3
  23. package/ios/Package.swift +7 -8
  24. package/ios/Tests/MQTTProtocolTests.swift +1 -1
  25. package/ios/libs/libnghttp3.a +0 -0
  26. package/ios/libs/libngtcp2.a +0 -0
  27. package/ios/libs/libngtcp2_crypto_wolfssl.a +0 -0
  28. package/ios/libs/libwolfssl.a +0 -0
  29. package/ios/libs-simulator/libnghttp3.a +0 -0
  30. package/ios/libs-simulator/libngtcp2.a +0 -0
  31. package/ios/libs-simulator/libngtcp2_crypto_wolfssl.a +0 -0
  32. package/ios/libs-simulator/libwolfssl.a +0 -0
  33. package/ios/libs-simulator-x86_64/libnghttp3.a +0 -0
  34. package/ios/libs-simulator-x86_64/libngtcp2.a +0 -0
  35. package/ios/libs-simulator-x86_64/libngtcp2_crypto_wolfssl.a +0 -0
  36. package/ios/libs-simulator-x86_64/libwolfssl.a +0 -0
  37. package/package.json +5 -2
@@ -8,8 +8,8 @@ Pod::Spec.new do |s|
8
8
  s.summary = 'MQTT-over-QUIC Capacitor plugin (iOS)'
9
9
  s.license = package['license']
10
10
  s.homepage = 'https://github.com/annadata/capacitor-mqtt-quic'
11
- s.author = 'Yakub Mohammad'
12
- s.authors = { 'Yakub Mohammad' => 'yakub@annadata.ai' }
11
+ s.author = 'Mr. Yakub Mohammad'
12
+ s.authors = { 'Mr. Yakub Mohammad' => 'yakub@annadata.ai' }
13
13
  s.source = { :git => 'https://github.com/annadata/capacitor-mqtt-quic', :tag => s.version.to_s }
14
14
  s.source_files = 'ios/Sources/**/*.{swift,h,m,c,cc,mm,cpp}'
15
15
  s.resources = ['ios/Sources/MqttQuicPlugin/Resources/*.pem']
package/Package.swift ADDED
@@ -0,0 +1,59 @@
1
+ // swift-tools-version: 5.9
2
+ import PackageDescription
3
+
4
+ // AnnadataCapacitorMqttQuic – Swift Package for Capacitor 8 (SPM).
5
+ // At plugin root so Capacitor's SPM logic detects and adds it to CapApp-SPM automatically.
6
+ // Requires ios/libs/MqttQuicLibs.xcframework. After building (see build-native.sh), run:
7
+ // cd ios && ./create-xcframework.sh
8
+ let package = Package(
9
+ name: "AnnadataCapacitorMqttQuic",
10
+ platforms: [.iOS(.v15)],
11
+ products: [
12
+ .library(
13
+ name: "AnnadataCapacitorMqttQuic",
14
+ targets: ["AnnadataCapacitorMqttQuic"]
15
+ )
16
+ ],
17
+ dependencies: [
18
+ .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", from: "8.0.0")
19
+ ],
20
+ targets: [
21
+ .binaryTarget(
22
+ name: "MqttQuicLibs",
23
+ path: "ios/libs/MqttQuicLibs.xcframework"
24
+ ),
25
+ .target(
26
+ name: "NGTCP2Bridge",
27
+ dependencies: ["MqttQuicLibs"],
28
+ path: "ios/Sources/MqttQuicPlugin/QUIC",
29
+ sources: ["NGTCP2Bridge.mm"],
30
+ publicHeadersPath: ".",
31
+ cxxSettings: [
32
+ .define("NGTCP2_ENABLED"),
33
+ .define("NGHTTP3_ENABLED"),
34
+ .headerSearchPath("../../../include")
35
+ ],
36
+ linkerSettings: [
37
+ .linkedLibrary("c++")
38
+ ]
39
+ ),
40
+ .target(
41
+ name: "AnnadataCapacitorMqttQuic",
42
+ dependencies: [
43
+ "NGTCP2Bridge",
44
+ .product(name: "Capacitor", package: "capacitor-swift-pm")
45
+ ],
46
+ path: "ios/Sources/MqttQuicPlugin",
47
+ exclude: [
48
+ "MQTT/MQTT5ReasonCodes.swift.rej",
49
+ "QUIC/NGTCP2Bridge.mm",
50
+ "QUIC/NGTCP2Bridge.h"
51
+ ],
52
+ resources: [.process("Resources")],
53
+ swiftSettings: [
54
+ .define("NGTCP2_ENABLED"),
55
+ .define("NGHTTP3_ENABLED")
56
+ ]
57
+ )
58
+ ]
59
+ )
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  MQTT-over-QUIC Capacitor plugin for **iOS**, **Android**, and **Web (browser/PWA)**. Native: ngtcp2 + WolfSSL for QUIC; Web: MQTT over WebSocket (WSS), same API and event listeners.
4
4
 
5
- **Capacitor:** Supports **Capacitor 7** and **Capacitor 8**.
5
+ **Capacitor:** **8.0+** (tested with Capacitor 8).
6
6
 
7
7
  **Features:**
8
8
  - ✅ **MQTT 5.0** support with full properties and reason codes
@@ -20,7 +20,7 @@ MQTT-over-QUIC Capacitor plugin for **iOS**, **Android**, and **Web (browser/PWA
20
20
  - Transport abstraction (StreamReader/StreamWriter)
21
21
  - **Phase 2**: QUIC transport (ngtcp2) + stream adapters - **In Progress** ⏳
22
22
  - Currently uses stub implementations for testing
23
- - See [NGTCP2_INTEGRATION_PLAN.md](./NGTCP2_INTEGRATION_PLAN.md) for build instructions
23
+ - See [NGTCP2_INTEGRATION_PLAN.md](./docs/NGTCP2_INTEGRATION_PLAN.md) for build instructions
24
24
  - **Phase 3**: MQTT client API + Capacitor plugin bridge - **Complete** ✅
25
25
  - **Phase 4**: Platform integration in annadata-production - **Complete** ✅
26
26
 
@@ -274,7 +274,7 @@ await MqttQuic.publish({
274
274
  | **Reason Codes** | Detailed error information | Better debugging |
275
275
  | **Topic Aliases** | Reduce bandwidth | High-frequency publishing |
276
276
 
277
- See [MQTT5_IMPLEMENTATION_COMPLETE.md](./MQTT5_IMPLEMENTATION_COMPLETE.md) for full details.
277
+ See [MQTT5_IMPLEMENTATION_COMPLETE.md](./docs/MQTT5_IMPLEMENTATION_COMPLETE.md) for full details.
278
278
 
279
279
  ## TypeScript Interface
280
280
 
@@ -357,22 +357,25 @@ This script builds WolfSSL → nghttp3 → ngtcp2 in the correct order for both
357
357
  For detailed manual build instructions, see:
358
358
  - **iOS**: [ios/NGTCP2_BUILD_INSTRUCTIONS.md](./ios/NGTCP2_BUILD_INSTRUCTIONS.md)
359
359
  - **Android**: [android/NGTCP2_BUILD_INSTRUCTIONS.md](./android/NGTCP2_BUILD_INSTRUCTIONS.md)
360
- - **Full Plan**: [NGTCP2_INTEGRATION_PLAN.md](./NGTCP2_INTEGRATION_PLAN.md)
360
+ - **Full Plan**: [NGTCP2_INTEGRATION_PLAN.md](./docs/NGTCP2_INTEGRATION_PLAN.md)
361
361
 
362
362
  **Prerequisites:**
363
- - iOS: macOS with Xcode 14+
363
+ - iOS: macOS with Xcode 15+
364
364
  - Android: Android Studio with NDK r25+ (auto-detected from `$ANDROID_HOME`)
365
365
 
366
366
  ## Production / First-time build
367
367
 
368
- When you install the plugin from npm (`npm install @annadata/capacitor-mqtt-quic`), the published package may include **prebuilt native libs** (iOS and optionally Android, both using **WolfSSL**). In that case you only need:
368
+ When you install the plugin from npm (`npm install @annadata/capacitor-mqtt-quic`), the published package **may** include **prebuilt native libs** (iOS and optionally Android, both using **WolfSSL**). If it does (a "complete" / zero-config package), you only need:
369
369
 
370
370
  ```bash
371
371
  npm install @annadata/capacitor-mqtt-quic
372
372
  npx cap sync
373
+ ionic cap run android # or ios
373
374
  ```
374
375
 
375
- **If your Android build fails with "WolfSSL not found"**, run this **one-time** setup from your app project root (requires Android NDK r25+). Both iOS and Android use **WolfSSL** as the TLS backend (license/size/QUIC support):
376
+ **If your Android build fails with "WolfSSL not found"**, the package you installed does not include prebuilt Android libs. Do **one** of the following.
377
+
378
+ **Option A – One-time build (from your app project root, requires Android NDK r25+):** Both iOS and Android use **WolfSSL** as the TLS backend (license/size/QUIC support):
376
379
 
377
380
  ```bash
378
381
  cd node_modules/@annadata/capacitor-mqtt-quic
@@ -402,14 +405,51 @@ Or add to your app’s `package.json` and run once:
402
405
  "setup:wolfssl-android": "cd node_modules/@annadata/capacitor-mqtt-quic && ./build-native.sh --android-only --abi arm64-v8a && ./build-native.sh --android-only --abi armeabi-v7a && ./build-native.sh --android-only --abi x86_64"
403
406
  ```
404
407
 
405
- **iOS:** The plugin ships with vendored static libs (`ios/libs/`) using WolfSSL. If you built the plugin from source and those are missing, run from the plugin repo: `./build-native.sh --ios-only`, then pack/publish.
408
+ **Option B Use a complete package:** Reinstall a version of the plugin that was published with Android prebuilts (see *Publishing a complete package* below). Then no one-time build is needed.
409
+
410
+ **iOS:** The plugin typically ships with vendored static libs (`ios/libs/`) using WolfSSL. If you built the plugin from source and those are missing, run from the plugin repo: `./build-native.sh --ios-only`, then pack/publish.
411
+
412
+ ### Publishing a complete (zero-config) package
413
+
414
+ So that **clients have no native build step**, build Android (and iOS) prebuilts **before** publishing, then publish. The tarball will include `android/install/` and consumers can `npm install` and run the app without running `build-native.sh`.
415
+
416
+ From the **plugin repo** (capacitor-mqtt-quic), before `npm publish`:
417
+
418
+ ```bash
419
+ # 1) Android prebuilts for all ABIs (required for zero-config Android)
420
+ npm run build:android-prebuilts
421
+
422
+ # 2) iOS prebuilts (if not already present)
423
+ ./build-native.sh --ios-only
424
+
425
+ # 3) Build JS and publish (clean does not remove android/install or ios/libs)
426
+ npm run build
427
+ npm run clean:build-artifacts
428
+ npm version patch # or minor
429
+ npm publish --access public
430
+ ```
431
+
432
+ See **docs/PRODUCTION_PUBLISH_STEPS.md** for the full checklist.
433
+
434
+ ### Connection error: `{"code":"UNIMPLEMENTED"}`
435
+
436
+ Capacitor returns this when the **native plugin method is not found** on the current platform. Common causes and fixes:
437
+
438
+ | Platform | Check |
439
+ |----------|--------|
440
+ | **iOS** | Plugin must be linked. Run `npx cap sync ios` and `cd ios && pod install`, then rebuild in Xcode. Ensure `@annadata/capacitor-mqtt-quic` is in your app’s `package.json` and that the iOS project includes the plugin (Capacitor should auto-discover it). If you use a custom Podfile, ensure the AnnadataCapacitorMqttQuic plugin target is included. |
441
+ | **Android** | Run `npx cap sync android` and rebuild. If you see "WolfSSL not found", run the one-time native build (see above). |
442
+ | **Web / browser** | In browser, the plugin uses the **web** implementation (WSS or WebTransport). If you get UNIMPLEMENTED in the browser, the app may be resolving the native bridge instead of the web plugin—e.g. wrong `capacitor.config` or build. Ensure you’re opening the app as a web build (e.g. `ionic serve` or `cap run web`), not a native app with a WebView. |
443
+
444
+ After adding the plugin or changing native code, always run **`npx cap sync`** and on iOS **`pod install`**, then rebuild the native app.
406
445
 
407
446
  ## Development
408
447
 
409
448
  ### Build Plugin
410
449
 
411
450
  ```bash
412
- cd production/capacitor-mqtt-quic
451
+ git clone https://github.com/annadata/capacitor-mqtt-quic.git
452
+ cd capacitor-mqtt-quic
413
453
  npm install
414
454
  npm run build
415
455
  ```
@@ -417,8 +457,8 @@ npm run build
417
457
  ### Add to Capacitor App
418
458
 
419
459
  ```bash
420
- cd production/annadata-production
421
- npm i @annadata/capacitor-mqtt-quic
460
+ cd your-capacitor-app
461
+ npm install @annadata/capacitor-mqtt-quic
422
462
  npx cap sync
423
463
  ```
424
464
 
@@ -443,14 +483,14 @@ await MqttQuic.connect({
443
483
 
444
484
  ## Publishing (maintainers)
445
485
 
446
- To pack the plugin **with native libs** and publish to npm, follow **[PRODUCTION_PUBLISH_STEPS.md](./PRODUCTION_PUBLISH_STEPS.md)**.
486
+ To pack the plugin **with native libs** and publish to npm, follow **[PRODUCTION_PUBLISH_STEPS.md](./docs/PRODUCTION_PUBLISH_STEPS.md)**.
447
487
 
448
488
  ## Documentation
449
489
 
450
- - [Implementation Summary](./IMPLEMENTATION_SUMMARY.md) - Complete project overview
451
- - [MQTT 5.0 Implementation](./MQTT5_IMPLEMENTATION_COMPLETE.md) - MQTT 5.0 features and usage
452
- - [ngtcp2 Integration Plan](./NGTCP2_INTEGRATION_PLAN.md) - Build instructions for real QUIC
453
- - [MQTT Version Analysis](./MQTT_VERSION_ANALYSIS.md) - Why MQTT 5.0?
490
+ - [Implementation Summary](./docs/IMPLEMENTATION_SUMMARY.md) - Complete project overview
491
+ - [MQTT 5.0 Implementation](./docs/MQTT5_IMPLEMENTATION_COMPLETE.md) - MQTT 5.0 features and usage
492
+ - [ngtcp2 Integration Plan](./docs/NGTCP2_INTEGRATION_PLAN.md) - Build instructions for real QUIC
493
+ - [MQTT Version Analysis](./docs/MQTT_VERSION_ANALYSIS.md) - Why MQTT 5.0?
454
494
 
455
495
  ## Web / browser support
456
496
 
@@ -522,7 +562,7 @@ await MqttQuic.connect({
522
562
  - **iOS:** 15.0+
523
563
  - **Android:** API 21+ (Android 5.0+)
524
564
  - **Web:** Any modern browser; MQTT over WSS
525
- - **Capacitor:** 7.0+
565
+ - **Capacitor:** 8.0+
526
566
  - **QUIC:** ngtcp2 + WolfSSL on native; on web, optional WebTransport (browser's HTTP/3/QUIC) when server supports it
527
567
 
528
568
  ## Author
@@ -337,4 +337,4 @@ After building ngtcp2/nghttp3:
337
337
  2. Replace `QuicClientStub` with `NGTCP2Client` in `MQTTClient.kt`
338
338
  3. Test connection to MQTT server over QUIC
339
339
 
340
- See `NGTCP2_INTEGRATION_PLAN.md` for detailed implementation guide.
340
+ See [NGTCP2_INTEGRATION_PLAN.md](../docs/NGTCP2_INTEGRATION_PLAN.md) for detailed implementation guide.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "appId": "ai.annadata.mqttquic",
3
- "appName": "MqttQuicPlugin",
3
+ "appName": "AnnadataCapacitorMqttQuic",
4
4
  "webDir": "www"
5
5
  }
@@ -36,7 +36,10 @@ android {
36
36
  testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
37
37
  externalNativeBuild {
38
38
  cmake {
39
- arguments "-DANDROID_STL=c++_shared"
39
+ arguments "-DANDROID_STL=c++_shared",
40
+ "-DWOLFSSL_ROOT_DIR=${file('install/wolfssl-android').absolutePath}",
41
+ "-DNGTCP2_INSTALL_DIR=${file('install/ngtcp2-android').absolutePath}",
42
+ "-DNGHTTP3_INSTALL_DIR=${file('install/nghttp3-android').absolutePath}"
40
43
  cppFlags "-std=c++17"
41
44
  }
42
45
  }
@@ -14,9 +14,10 @@ endif()
14
14
  # WolfSSL path (same layout as iOS: TLS backend for ngtcp2). Override via -DWOLFSSL_ROOT_DIR=...
15
15
  if(NOT WOLFSSL_ROOT_DIR)
16
16
  set(WOLFSSL_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../install/wolfssl-android" CACHE PATH "Path to WolfSSL for Android")
17
- if(EXISTS "${WOLFSSL_ROOT_DIR}/${ANDROID_ABI}")
18
- set(WOLFSSL_ROOT_DIR "${WOLFSSL_ROOT_DIR}/${ANDROID_ABI}" CACHE PATH "Path to WolfSSL for Android (ABI)" FORCE)
19
- endif()
17
+ endif()
18
+ # Prefer ABI-specific subdir when present (e.g. when WOLFSSL_ROOT_DIR is passed as base path from Gradle)
19
+ if(EXISTS "${WOLFSSL_ROOT_DIR}/${ANDROID_ABI}")
20
+ set(WOLFSSL_ROOT_DIR "${WOLFSSL_ROOT_DIR}/${ANDROID_ABI}" CACHE PATH "Path to WolfSSL for Android (ABI)" FORCE)
20
21
  endif()
21
22
  if(NOT WOLFSSL_INCLUDE_DIR AND EXISTS "${WOLFSSL_ROOT_DIR}/include")
22
23
  set(WOLFSSL_INCLUDE_DIR "${WOLFSSL_ROOT_DIR}/include")
@@ -25,7 +26,12 @@ if(NOT WOLFSSL_LIBRARY AND EXISTS "${WOLFSSL_ROOT_DIR}/lib/libwolfssl.a")
25
26
  set(WOLFSSL_LIBRARY "${WOLFSSL_ROOT_DIR}/lib/libwolfssl.a")
26
27
  endif()
27
28
  if(NOT WOLFSSL_LIBRARY OR NOT WOLFSSL_INCLUDE_DIR)
28
- message(FATAL_ERROR "WolfSSL not found. Build it first: cd android && ./build-wolfssl.sh --abi ${ANDROID_ABI}. Then run ./build-ngtcp2.sh (default uses WolfSSL).")
29
+ message(FATAL_ERROR
30
+ "WolfSSL not found at ${WOLFSSL_ROOT_DIR}. "
31
+ "One-time fix (from your app project root): "
32
+ "cd node_modules/@annadata/capacitor-mqtt-quic && "
33
+ "./build-native.sh --android-only --abi ${ANDROID_ABI} "
34
+ "(repeat for armeabi-v7a, x86_64). See plugin README 'Production / First-time build'.")
29
35
  endif()
30
36
 
31
37
  # ngtcp2 install/source directories (adjust as needed)
@@ -46,6 +52,14 @@ if(NOT NGHTTP3_INSTALL_DIR)
46
52
  endif()
47
53
  set(NGHTTP3_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../../nghttp3" CACHE PATH "Path to nghttp3 source")
48
54
 
55
+ # Prefer ABI-specific subdir when install dirs are passed as base path from Gradle
56
+ if(EXISTS "${NGTCP2_INSTALL_DIR}/${ANDROID_ABI}")
57
+ set(NGTCP2_INSTALL_DIR "${NGTCP2_INSTALL_DIR}/${ANDROID_ABI}" CACHE PATH "Path to ngtcp2 install (ABI)" FORCE)
58
+ endif()
59
+ if(EXISTS "${NGHTTP3_INSTALL_DIR}/${ANDROID_ABI}")
60
+ set(NGHTTP3_INSTALL_DIR "${NGHTTP3_INSTALL_DIR}/${ANDROID_ABI}" CACHE PATH "Path to nghttp3 install (ABI)" FORCE)
61
+ endif()
62
+
49
63
  # Prefer installed libraries (WolfSSL backend: libngtcp2_crypto_wolfssl.a), fallback to source build
50
64
  set(NGTCP2_INCLUDE_DIR "")
51
65
  set(NGTCP2_LIBRARY "")
@@ -112,7 +126,7 @@ target_include_directories(ngtcp2_client PRIVATE
112
126
  )
113
127
 
114
128
  # Link libraries: WolfSSL + ngtcp2 (same TLS backend as iOS)
115
- target_link_libraries(ngtcp2_client
129
+ target_link_libraries(ngtcp2_client PRIVATE
116
130
  ${WOLFSSL_LIBRARY}
117
131
  log
118
132
  )
@@ -188,7 +188,7 @@ class QuicClient {
188
188
  if (!conn_) {
189
189
  return 0;
190
190
  }
191
- int rv = ngtcp2_conn_shutdown_stream_write(conn_, stream_id, 0);
191
+ int rv = ngtcp2_conn_shutdown_stream_write(conn_, 0, stream_id, 0);
192
192
  if (rv != 0) {
193
193
  setError(ngtcp2_strerror(rv));
194
194
  return -1;
@@ -266,12 +266,12 @@ class QuicClient {
266
266
  if (fd == -1) {
267
267
  continue;
268
268
  }
269
- if (connect(fd, rp->ai_addr, rp->ai_addrlen) == 0) {
269
+ if (::connect(fd, rp->ai_addr, rp->ai_addrlen) == 0) {
270
270
  memcpy(&remote_addr_, rp->ai_addr, rp->ai_addrlen);
271
271
  remote_addrlen_ = (socklen_t)rp->ai_addrlen;
272
272
  break;
273
273
  }
274
- close(fd);
274
+ ::close(fd);
275
275
  fd = -1;
276
276
  }
277
277
  freeaddrinfo(res);
@@ -284,7 +284,7 @@ class QuicClient {
284
284
  if (getsockname(fd, (struct sockaddr *)&local_addr_, &local_addrlen_) !=
285
285
  0) {
286
286
  setError("getsockname failed");
287
- close(fd);
287
+ ::close(fd);
288
288
  return -1;
289
289
  }
290
290
 
@@ -333,17 +333,21 @@ class QuicClient {
333
333
  bool ca_loaded = false;
334
334
  const char *ca_file = std::getenv("MQTT_QUIC_CA_FILE");
335
335
  const char *ca_path = std::getenv("MQTT_QUIC_CA_PATH");
336
- if ((ca_file && ca_file[0] != '\0') || (ca_path && ca_path[0] != '\0')) {
337
- if (wolfSSL_CTX_load_verify_locations(ssl_ctx_, ca_file, ca_path) != 1) {
336
+ const char *file_arg = (ca_file && ca_file[0] != '\0') ? ca_file : nullptr;
337
+ const char *path_arg = (ca_path && ca_path[0] != '\0') ? ca_path : nullptr;
338
+ if (file_arg || path_arg) {
339
+ if (wolfSSL_CTX_load_verify_locations(ssl_ctx_, file_arg, path_arg) == 1) {
340
+ ca_loaded = true;
341
+ } else {
338
342
  setError("Failed to load CA bundle from MQTT_QUIC_CA_FILE/CA_PATH");
339
343
  return -1;
340
344
  }
345
+ }
346
+ if (!ca_loaded && wolfSSL_CTX_set_default_verify_paths(ssl_ctx_) == 1) {
341
347
  ca_loaded = true;
342
348
  }
343
- if (!ca_loaded) {
344
- if (wolfSSL_CTX_set_default_verify_paths(ssl_ctx_) == 1) {
345
- ca_loaded = true;
346
- }
349
+ if (!ca_loaded && wolfSSL_CTX_load_system_CA_certs(ssl_ctx_) == 1) {
350
+ ca_loaded = true;
347
351
  }
348
352
  if (!ca_loaded) {
349
353
  setError("No CA bundle available for TLS verification");
@@ -660,15 +664,15 @@ class QuicClient {
660
664
  ssl_ctx_ = nullptr;
661
665
  }
662
666
  if (fd_ != -1) {
663
- close(fd_);
667
+ ::close(fd_);
664
668
  fd_ = -1;
665
669
  }
666
670
  if (wakeup_fds_[0] != -1) {
667
- close(wakeup_fds_[0]);
671
+ ::close(wakeup_fds_[0]);
668
672
  wakeup_fds_[0] = -1;
669
673
  }
670
674
  if (wakeup_fds_[1] != -1) {
671
- close(wakeup_fds_[1]);
675
+ ::close(wakeup_fds_[1]);
672
676
  wakeup_fds_[1] = -1;
673
677
  }
674
678
  }
@@ -740,10 +744,11 @@ class QuicClient {
740
744
  return 0;
741
745
  }
742
746
 
743
- static int stream_close_cb(ngtcp2_conn *conn, int64_t stream_id,
744
- uint64_t app_error_code, void *user_data,
745
- void *stream_user_data) {
747
+ static int stream_close_cb(ngtcp2_conn *conn, uint32_t flags,
748
+ int64_t stream_id, uint64_t app_error_code,
749
+ void *user_data, void *stream_user_data) {
746
750
  (void)conn;
751
+ (void)flags;
747
752
  (void)app_error_code;
748
753
  (void)stream_user_data;
749
754
  auto *client = static_cast<QuicClient *>(user_data);
@@ -926,4 +931,11 @@ Java_ai_annadata_mqttquic_quic_NGTCP2Client_nativeGetLastError(
926
931
  return env->NewStringUTF(it->second->last_error());
927
932
  }
928
933
 
934
+ // Debug-build alias: Kotlin/AGP can mangle the method name to include the module suffix.
935
+ JNIEXPORT jstring JNICALL
936
+ Java_ai_annadata_mqttquic_quic_NGTCP2Client_nativeGetLastError_00024annadata_1capacitor_1mqtt_1quic_1debug__J(
937
+ JNIEnv *env, jobject thiz, jlong connHandle) {
938
+ return Java_ai_annadata_mqttquic_quic_NGTCP2Client_nativeGetLastError(env, thiz, connHandle);
939
+ }
940
+
929
941
  } // extern "C"
@@ -100,7 +100,7 @@ class MqttQuicPlugin : Plugin() {
100
100
  notifyListeners("message", data)
101
101
  }
102
102
  }
103
- client.connect(host, port, clientId, username, password, cleanSession, keepalive, sessionExpiryInterval)
103
+ client.connect(host, port, clientId, username, password, cleanSession ?: true, keepalive ?: 20, sessionExpiryInterval)
104
104
  call.resolve(JSObject().put("connected", true))
105
105
  notifyListeners("connected", JSObject().put("connected", true))
106
106
  } catch (e: Exception) {
@@ -199,7 +199,7 @@ class MqttQuicPlugin : Plugin() {
199
199
  val properties = mutableMapOf<Int, Any>()
200
200
  messageExpiryInterval?.let { properties[MQTT5PropertyType.MESSAGE_EXPIRY_INTERVAL.toInt()] = it }
201
201
  contentType?.let { properties[MQTT5PropertyType.CONTENT_TYPE.toInt()] = it }
202
- client.publish(topic, data, minOf(qos, 2), if (properties.isNotEmpty()) properties else null)
202
+ client.publish(topic, data, minOf(qos ?: 0, 2), if (properties.isNotEmpty()) properties else null)
203
203
  call.resolve(JSObject().put("success", true))
204
204
  } catch (e: Exception) {
205
205
  val msg = e.message ?: "Publish failed"
@@ -226,7 +226,7 @@ class MqttQuicPlugin : Plugin() {
226
226
 
227
227
  scope.launch {
228
228
  try {
229
- client.subscribe(topic, minOf(qos, 2), subscriptionIdentifier)
229
+ client.subscribe(topic, minOf(qos ?: 0, 2), subscriptionIdentifier)
230
230
  call.resolve(JSObject().put("success", true))
231
231
  } catch (e: Exception) {
232
232
  call.reject(e.message ?: "Subscribe failed")
@@ -372,9 +372,9 @@ class MQTTClient {
372
372
  /** Send PINGREQ at effectiveKeepalive interval so server sees activity and does not close (idle/keepalive). [MQTT-3.1.2-20] */
373
373
  private fun startKeepaliveLoop() {
374
374
  keepaliveJob?.cancel()
375
- val ka = lock.withLock { effectiveKeepalive }
376
- if (ka <= 0) return
377
375
  keepaliveJob = scope.launch {
376
+ val ka = lock.withLock { effectiveKeepalive }
377
+ if (ka <= 0) return@launch
378
378
  while (isActive) {
379
379
  delay(ka * 1000L) // seconds to ms
380
380
  val (w, stillConnected) = lock.withLock {
@@ -428,8 +428,7 @@ class MQTTClient {
428
428
  if (activeProtocolVersion == MQTTProtocolLevel.V5) {
429
429
  MQTT5Protocol.parsePublishV5(rest, 0, qos, topicAliasMap)
430
430
  } else {
431
- val t = MQTTProtocol.parsePublish(rest, 0, qos)
432
- Triple(t.first, t.second, t.third)
431
+ MQTTProtocol.parsePublish(rest, 0, qos)
433
432
  }
434
433
  }
435
434
 
@@ -201,9 +201,9 @@ object MQTTProtocol {
201
201
 
202
202
  /**
203
203
  * Parse PUBLISH payload (after fixed header).
204
- * Returns (topic, packetId?, payload, newOffset). packetId only for QoS > 0.
204
+ * Returns (topic, packetId?, payload). packetId only for QoS > 0.
205
205
  */
206
- fun parsePublish(data: ByteArray, offset: Int, qos: Int): Triple<String, Int?, ByteArray, Int> {
206
+ fun parsePublish(data: ByteArray, offset: Int, qos: Int): Triple<String, Int?, ByteArray> {
207
207
  var off = offset
208
208
  val (topic, next) = decodeString(data, off)
209
209
  off = next
@@ -214,7 +214,7 @@ object MQTTProtocol {
214
214
  off += 2
215
215
  }
216
216
  val payload = data.copyOfRange(off, data.size)
217
- return Triple(topic, pid, payload, data.size)
217
+ return Triple(topic, pid, payload)
218
218
  }
219
219
 
220
220
  fun buildSubscribe(packetId: Int, topic: String, qos: Int = 0): ByteArray {
@@ -5,21 +5,21 @@ package ai.annadata.mqttquic.mqtt
5
5
  */
6
6
 
7
7
  object MQTTMessageType {
8
- const val CONNECT: Byte = 0x10
9
- const val CONNACK: Byte = 0x20
10
- const val PUBLISH: Byte = 0x30
11
- const val PUBACK: Byte = 0x40
12
- const val PUBREC: Byte = 0x50
13
- const val PUBREL: Byte = 0x62
14
- const val PUBCOMP: Byte = 0x70
15
- const val SUBSCRIBE: Byte = 0x82
16
- const val SUBACK: Byte = 0x90
17
- const val UNSUBSCRIBE: Byte = 0xA2
18
- const val UNSUBACK: Byte = 0xB0
19
- const val PINGREQ: Byte = 0xC0
20
- const val PINGRESP: Byte = 0xD0
21
- const val DISCONNECT: Byte = 0xE0
22
- const val AUTH: Byte = 0xF0
8
+ const val CONNECT: Byte = 0x10.toByte()
9
+ const val CONNACK: Byte = 0x20.toByte()
10
+ const val PUBLISH: Byte = 0x30.toByte()
11
+ const val PUBACK: Byte = 0x40.toByte()
12
+ const val PUBREC: Byte = 0x50.toByte()
13
+ const val PUBREL: Byte = 0x62.toByte()
14
+ const val PUBCOMP: Byte = 0x70.toByte()
15
+ const val SUBSCRIBE: Byte = 0x82.toByte()
16
+ const val SUBACK: Byte = 0x90.toByte()
17
+ const val UNSUBSCRIBE: Byte = 0xA2.toByte()
18
+ const val UNSUBACK: Byte = 0xB0.toByte()
19
+ const val PINGREQ: Byte = 0xC0.toByte()
20
+ const val PINGRESP: Byte = 0xD0.toByte()
21
+ const val DISCONNECT: Byte = 0xE0.toByte()
22
+ const val AUTH: Byte = 0xF0.toByte()
23
23
  }
24
24
 
25
25
  object MQTTConnectFlags {
@@ -47,6 +47,7 @@ class NGTCP2Client : QuicClient {
47
47
  private external fun nativeClose(connHandle: Long)
48
48
  private external fun nativeIsConnected(connHandle: Long): Boolean
49
49
  internal external fun nativeCloseStream(connHandle: Long, streamId: Long): Int
50
+ @JvmName("nativeGetLastError")
50
51
  internal external fun nativeGetLastError(connHandle: Long): String
51
52
 
52
53
  // Connection state
@@ -182,7 +182,7 @@ Implement `MqttQuicService` web fallback using `mqtt.js` or similar library.
182
182
  ```
183
183
  capacitor-mqtt-quic/
184
184
  ├── ios/
185
- │ ├── MqttQuicPlugin.podspec
185
+ │ ├── AnnadataCapacitorMqttQuic.podspec
186
186
  │ └── Sources/MqttQuicPlugin/
187
187
  │ ├── MQTT/ # Phase 1
188
188
  │ ├── Transport/ # Phase 1, 2
@@ -129,7 +129,7 @@ The ngtcp2 integration replaces the stub QUIC implementations (`QuicClientStub`)
129
129
  - Implement TLS 1.3 handshake
130
130
  - Implement UDP packet handlers
131
131
 
132
- 4. **Update `MqttQuicPlugin.podspec`:**
132
+ 4. **Update `AnnadataCapacitorMqttQuic.podspec`:**
133
133
  - Add ngtcp2 library linking
134
134
  - Add header search paths
135
135
 
@@ -9,7 +9,7 @@ This checklist makes the plugin **production-grade**: pack everything needed (in
9
9
  ## Prerequisites
10
10
 
11
11
  - Node.js 18+
12
- - **iOS:** macOS, Xcode 14+
12
+ - **iOS:** macOS, Xcode 15+
13
13
  - **Android:** Android Studio, NDK r25+ (e.g. via `$ANDROID_HOME/ndk/<version>`)
14
14
  - npm account with access to `@annadata` scope
15
15
 
@@ -32,9 +32,15 @@ This produces:
32
32
 
33
33
  The podspec’s `vendored_libraries` point at `ios/libs/`; these **must** be present when you pack/publish.
34
34
 
35
- ### Android (optional but recommended for zero-config)
35
+ ### Android (required for zero-config / “complete” package)
36
36
 
37
- Android uses **WolfSSL** (same TLS backend as iOS). Build WolfSSL + nghttp3 + ngtcp2 for all ABIs so consumers don’t need to run the one-time setup:
37
+ Android uses **WolfSSL** (same TLS backend as iOS). To ship a **complete** plugin so clients don’t run any native build, build WolfSSL + nghttp3 + ngtcp2 for all ABIs:
38
+
39
+ ```bash
40
+ npm run build:android-prebuilts
41
+ ```
42
+
43
+ Or manually:
38
44
 
39
45
  ```bash
40
46
  ./build-native.sh --android-only --abi arm64-v8a
@@ -3,7 +3,7 @@ require 'json'
3
3
  package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
4
4
 
5
5
  Pod::Spec.new do |s|
6
- s.name = 'MqttQuicPlugin'
6
+ s.name = 'AnnadataCapacitorMqttQuic'
7
7
  s.version = package['version']
8
8
  s.summary = 'MQTT-over-QUIC Capacitor plugin (iOS)'
9
9
  s.license = package['license']
@@ -12,7 +12,7 @@ Pod::Spec.new do |s|
12
12
  s.source = { :git => 'https://github.com/annadata/capacitor-mqtt-quic', :tag => s.version.to_s }
13
13
  s.source_files = 'Sources/**/*.{swift,h,m,c,cc,mm,cpp}'
14
14
  s.resources = ['Sources/MqttQuicPlugin/Resources/*.pem']
15
- s.ios.deployment_target = '14.0'
15
+ s.ios.deployment_target = '15.0'
16
16
  s.dependency 'Capacitor'
17
17
  s.swift_version = '5.1'
18
18
  s.vendored_libraries = [
@@ -25,8 +25,8 @@ Pod::Spec.new do |s|
25
25
  s.private_header_files = 'Sources/**/*.h', 'include/**/*.h'
26
26
  s.header_mappings_dir = 'Sources'
27
27
  s.pod_target_xcconfig = {
28
- 'HEADER_SEARCH_PATHS' => '$(PODS_ROOT)/MqttQuicPlugin/include/ngtcp2 $(PODS_ROOT)/MqttQuicPlugin/include/nghttp3 $(PODS_ROOT)/MqttQuicPlugin/include/wolfssl',
29
- 'LIBRARY_SEARCH_PATHS' => '$(PODS_ROOT)/MqttQuicPlugin/libs',
28
+ 'HEADER_SEARCH_PATHS' => '$(PODS_ROOT)/AnnadataCapacitorMqttQuic/include/ngtcp2 $(PODS_ROOT)/AnnadataCapacitorMqttQuic/include/nghttp3 $(PODS_ROOT)/AnnadataCapacitorMqttQuic/include/wolfssl',
29
+ 'LIBRARY_SEARCH_PATHS' => '$(PODS_ROOT)/AnnadataCapacitorMqttQuic/libs',
30
30
  'OTHER_LDFLAGS' => '-lngtcp2 -lngtcp2_crypto_wolfssl -lnghttp3 -lwolfssl',
31
31
  'SWIFT_ACTIVE_COMPILATION_CONDITIONS' => 'NGTCP2_ENABLED NGHTTP3_ENABLED'
32
32
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "appId": "ai.annadata.mqttquic",
3
- "appName": "MqttQuicPlugin",
3
+ "appName": "AnnadataCapacitorMqttQuic",
4
4
  "webDir": "www",
5
5
  "packageClassList": [
6
6
  "MqttQuicPlugin"
@@ -34,7 +34,7 @@ If you have access to pre-built ngtcp2, nghttp3, and OpenSSL libraries:
34
34
  2. Place `libnghttp3.a` in `ios/libs/`
35
35
  3. Place OpenSSL libraries (`libssl.a`, `libcrypto.a`) in `ios/libs/`
36
36
  4. Place headers in `ios/include/ngtcp2/`, `ios/include/nghttp3/`, and `ios/include/openssl/`
37
- 5. Update `MqttQuicPlugin.podspec` to link against these libraries
37
+ 5. Update `AnnadataCapacitorMqttQuic.podspec` to link against these libraries
38
38
 
39
39
  ### Option 2: Build from Source
40
40
 
@@ -154,7 +154,7 @@ Then clean and rebuild the iOS app so it links the new xcframework. See the app
154
154
 
155
155
  ### Option A: CocoaPods
156
156
 
157
- Update `ios/MqttQuicPlugin.podspec`:
157
+ Update `ios/AnnadataCapacitorMqttQuic.podspec`:
158
158
 
159
159
  ```ruby
160
160
  Pod::Spec.new do |s|
@@ -342,4 +342,4 @@ After building ngtcp2:
342
342
  2. Replace `QuicClientStub` with `NGTCP2Client` in `MQTTClient.swift`
343
343
  3. Test connection to MQTT server over QUIC
344
344
 
345
- See `NGTCP2_INTEGRATION_PLAN.md` for detailed implementation guide.
345
+ See [NGTCP2_INTEGRATION_PLAN.md](../docs/NGTCP2_INTEGRATION_PLAN.md) for detailed implementation guide.
package/ios/Package.swift CHANGED
@@ -1,17 +1,16 @@
1
1
  // swift-tools-version: 5.9
2
2
  import PackageDescription
3
3
 
4
- // MqttQuicPlugin iOS – Swift Package for Capacitor 8 (SPM).
5
- // Requires Libs/MqttQuicLibs.xcframework. After building OpenSSL, ngtcp2, nghttp3
6
- // (see build-openssl.sh, build-ngtcp2.sh, build-nghttp3.sh), run:
4
+ // AnnadataCapacitorMqttQuic – Swift Package for Capacitor 8 (SPM).
5
+ // Requires Libs/MqttQuicLibs.xcframework. After building (see build-native.sh), run:
7
6
  // ./create-xcframework.sh
8
7
  let package = Package(
9
- name: "MqttQuicPlugin",
10
- platforms: [.iOS(.v14)],
8
+ name: "AnnadataCapacitorMqttQuic",
9
+ platforms: [.iOS(.v15)],
11
10
  products: [
12
11
  .library(
13
- name: "MqttQuicPlugin",
14
- targets: ["MqttQuicPlugin"]
12
+ name: "AnnadataCapacitorMqttQuic",
13
+ targets: ["AnnadataCapacitorMqttQuic"]
15
14
  )
16
15
  ],
17
16
  dependencies: [
@@ -38,7 +37,7 @@ let package = Package(
38
37
  ]
39
38
  ),
40
39
  .target(
41
- name: "MqttQuicPlugin",
40
+ name: "AnnadataCapacitorMqttQuic",
42
41
  dependencies: [
43
42
  "NGTCP2Bridge",
44
43
  .product(name: "Capacitor", package: "capacitor-swift-pm")
@@ -4,7 +4,7 @@
4
4
  //
5
5
 
6
6
  import XCTest
7
- @testable import MqttQuicPlugin
7
+ @testable import AnnadataCapacitorMqttQuic
8
8
 
9
9
  final class MQTTProtocolTests: XCTestCase {
10
10
 
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@annadata/capacitor-mqtt-quic",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "type": "module",
5
5
  "description": "MQTT-over-QUIC client for Capacitor (iOS, Android, Web). Native: ngtcp2+WolfSSL; Web: MQTT over WebSocket (WSS), same API.",
6
6
  "main": "dist/plugin.cjs.js",
@@ -11,6 +11,7 @@
11
11
  "dist",
12
12
  "ios",
13
13
  "android",
14
+ "Package.swift",
14
15
  "AnnadataCapacitorMqttQuic.podspec",
15
16
  "README.md",
16
17
  "deps-versions.sh",
@@ -21,6 +22,7 @@
21
22
  "build": "npm run build:tsc && npm run build:rollup",
22
23
  "build:tsc": "npx tsc",
23
24
  "build:rollup": "npx rollup -c rollup.config.js",
25
+ "build:android-prebuilts": "./build-native.sh --android-only --abi arm64-v8a && ./build-native.sh --android-only --abi armeabi-v7a && ./build-native.sh --android-only --abi x86_64",
24
26
  "watch": "rollup -c rollup.config.js -w",
25
27
  "lint": "eslint src --ext ts",
26
28
  "prepack": "npm run clean:build-artifacts",
@@ -41,7 +43,8 @@
41
43
  ],
42
44
  "capacitor": {
43
45
  "ios": {
44
- "src": "ios"
46
+ "src": "ios",
47
+ "name": "AnnadataCapacitorMqttQuic"
45
48
  },
46
49
  "android": {
47
50
  "src": "android"