@offline-protocol/mesh-sdk 0.2.2 → 0.2.3

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 (322) hide show
  1. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +1 -1
  2. package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
  3. package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +1 -1
  4. package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +1 -1
  5. package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +1 -1
  6. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab +0 -0
  7. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values +0 -0
  8. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at +0 -0
  9. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.s +1 -1
  10. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab +0 -0
  11. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream +0 -0
  12. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream.len +0 -0
  13. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.len +0 -0
  14. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.values.at +0 -0
  15. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i +0 -0
  16. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab +0 -0
  17. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream +0 -0
  18. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len +0 -0
  19. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len +0 -0
  20. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values +0 -0
  21. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at +0 -0
  22. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i +0 -0
  23. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/inline-functions.tab.values.at +0 -0
  24. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab +0 -0
  25. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream +0 -0
  26. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len +0 -0
  27. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len +0 -0
  28. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values +0 -0
  29. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at +0 -0
  30. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.s +1 -1
  31. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i +0 -0
  32. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab +0 -0
  33. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream +0 -0
  34. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream.len +0 -0
  35. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.len +0 -0
  36. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values +0 -0
  37. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at +0 -0
  38. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.s +1 -1
  39. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i +0 -0
  40. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab +0 -0
  41. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at +0 -0
  42. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab +0 -0
  43. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream +0 -0
  44. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len +0 -0
  45. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.len +0 -0
  46. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values +0 -0
  47. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at +0 -0
  48. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.s +1 -1
  49. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i +0 -0
  50. package/android/build/kotlin/compileDebugKotlin/cacheable/last-build.bin +0 -0
  51. package/android/build/kotlin/compileDebugKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin +0 -0
  52. package/android/build/kotlin/compileDebugKotlin/local-state/build-history.bin +0 -0
  53. package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
  54. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/BleManager$AdvertisementCacheEntry.class +0 -0
  55. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/BleManager$Companion.class +0 -0
  56. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/BleManager$MeshObservation.class +0 -0
  57. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/BleManager$PendingFragment.class +0 -0
  58. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/BleManager$fragmentPollingRunnable$1.class +0 -0
  59. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/BleManager$gattClientCallback$1.class +0 -0
  60. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/BleManager$gattServerCallback$1.class +0 -0
  61. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/BleManager$resolveTargetAddress$$inlined$sortedBy$1.class +0 -0
  62. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/BleManager$routingCleanupRunnable$1.class +0 -0
  63. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/BleManager$scanWatchdogRunnable$1.class +0 -0
  64. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/BleManager$startAdvertising$1.class +0 -0
  65. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/BleManager$startConnectionMonitor$1.class +0 -0
  66. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/BleManager$startScanning$1.class +0 -0
  67. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/BleManager.class +0 -0
  68. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/InternetManager$Companion.class +0 -0
  69. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/InternetManager$messagePollingRunnable$1.class +0 -0
  70. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/InternetManager$pingRunnable$1.class +0 -0
  71. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/InternetManager$webSocketListener$1.class +0 -0
  72. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/InternetManager.class +0 -0
  73. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/LogThrottler.class +0 -0
  74. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/OfflineProtocolModule$Companion.class +0 -0
  75. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/OfflineProtocolModule$Constants.class +0 -0
  76. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/OfflineProtocolModule$ParsedConfig.class +0 -0
  77. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/OfflineProtocolModule$create$1.class +0 -0
  78. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/OfflineProtocolModule$create$3$1.class +0 -0
  79. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/OfflineProtocolModule$create$5$1.class +0 -0
  80. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/OfflineProtocolModule.class +0 -0
  81. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/OfflineProtocolPackage.class +0 -0
  82. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/TransportException$AlreadyRunning.class +0 -0
  83. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/TransportException$InvalidState.class +0 -0
  84. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/TransportException$NotAvailable.class +0 -0
  85. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/TransportException$NotRunning.class +0 -0
  86. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/TransportException$PermissionDenied.class +0 -0
  87. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/TransportException$PlatformError.class +0 -0
  88. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/TransportException$StartFailed.class +0 -0
  89. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/TransportException.class +0 -0
  90. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/TransportManager.class +0 -0
  91. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/TransportManagerListener.class +0 -0
  92. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/TransportState.class +0 -0
  93. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/WifiDirectManager$Companion.class +0 -0
  94. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/WifiDirectManager$messagePollingRunnable$1.class +0 -0
  95. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/WifiDirectManager$p2pReceiver$1.class +0 -0
  96. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/WifiDirectManager$startPeerDiscovery$1.class +0 -0
  97. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/WifiDirectManager$stopPeerDiscovery$1.class +0 -0
  98. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/WifiDirectManager.class +0 -0
  99. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/ble/MeshConnectionRegistry.class +0 -0
  100. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/mesh/MeshAdvertisementData$Companion.class +0 -0
  101. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/mesh/MeshAdvertisementData.class +0 -0
  102. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/mesh/MeshController$Companion.class +0 -0
  103. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/mesh/MeshController$ConnectionIntent.class +0 -0
  104. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/mesh/MeshController$MeshConfig.class +0 -0
  105. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/mesh/MeshController$MeshDecision.class +0 -0
  106. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/mesh/MeshController$MeshRole.class +0 -0
  107. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/mesh/MeshController$PeerMetrics.class +0 -0
  108. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/mesh/MeshController$PeerState.class +0 -0
  109. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/mesh/MeshController$RebalanceDirective.class +0 -0
  110. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/mesh/MeshController$RemoteCandidate.class +0 -0
  111. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/mesh/MeshController$ScoreWeights.class +0 -0
  112. package/android/build/tmp/kotlin-classes/debug/com/offlineprotocol/mesh/MeshController.class +0 -0
  113. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/AckConfig$Companion.class +0 -0
  114. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/AckConfig.class +0 -0
  115. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/BleFragment$Companion.class +0 -0
  116. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/BleFragment.class +0 -0
  117. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/DedupConfig$Companion.class +0 -0
  118. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/DedupConfig.class +0 -0
  119. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/DedupStats$Companion.class +0 -0
  120. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/DedupStats.class +0 -0
  121. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/Disposable$Companion.class +0 -0
  122. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/Disposable.class +0 -0
  123. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/DorsConfig$Companion.class +0 -0
  124. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/DorsConfig.class +0 -0
  125. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/EventCallback$Companion.class +0 -0
  126. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/EventCallback.class +0 -0
  127. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverter$DefaultImpls.class +0 -0
  128. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverter.class +0 -0
  129. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterBoolean.class +0 -0
  130. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterCallbackInterface.class +0 -0
  131. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterFloat.class +0 -0
  132. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterOptionalShort.class +0 -0
  133. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterOptionalString.class +0 -0
  134. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterOptionalTypeBleFragment.class +0 -0
  135. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterOptionalTypeFileProgress.class +0 -0
  136. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterOptionalTypeInternetMessage.class +0 -0
  137. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterOptionalTypeRouteEntry.class +0 -0
  138. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterOptionalTypeTransportMetrics.class +0 -0
  139. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterOptionalTypeWifiDirectMessage.class +0 -0
  140. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterOptionalUByte.class +0 -0
  141. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterOptionalULong.class +0 -0
  142. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterRustBuffer.class +0 -0
  143. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterSequenceString.class +0 -0
  144. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterSequenceTypeMessageStats.class +0 -0
  145. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterSequenceTypeNetworkLink.class +0 -0
  146. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterSequenceTypeNetworkNode.class +0 -0
  147. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterSequenceTypeRouteEntry.class +0 -0
  148. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterSequenceUByte.class +0 -0
  149. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterShort.class +0 -0
  150. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterString.class +0 -0
  151. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeAckConfig.class +0 -0
  152. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeBleFragment.class +0 -0
  153. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeDedupConfig.class +0 -0
  154. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeDedupStats.class +0 -0
  155. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeDorsConfig.class +0 -0
  156. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeEventCallback.class +0 -0
  157. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeFileProgress.class +0 -0
  158. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeGradientRoutingConfig.class +0 -0
  159. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeInternetMessage.class +0 -0
  160. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeMessagePriority.class +0 -0
  161. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeMessageStats.class +0 -0
  162. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeNetworkLink.class +0 -0
  163. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeNetworkNode.class +0 -0
  164. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeNetworkTopology.class +0 -0
  165. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeOfflineProtocol.class +0 -0
  166. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypePathConfig.class +0 -0
  167. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypePeerDevice.class +0 -0
  168. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeProtocolConfig.class +0 -0
  169. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeProtocolConfigExtended.class +0 -0
  170. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeProtocolError.class +0 -0
  171. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeProtocolState.class +0 -0
  172. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeRelayConfig.class +0 -0
  173. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeRelayPriority.class +0 -0
  174. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeReliabilityConfig.class +0 -0
  175. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeRetryConfig.class +0 -0
  176. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeRouteEntry.class +0 -0
  177. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeRoutingStats.class +0 -0
  178. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeTransportConfig.class +0 -0
  179. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeTransportMetrics.class +0 -0
  180. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeTransportType.class +0 -0
  181. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterTypeWifiDirectMessage.class +0 -0
  182. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterUByte.class +0 -0
  183. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterUInt.class +0 -0
  184. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterULong.class +0 -0
  185. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FfiConverterUShort.class +0 -0
  186. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FileProgress$Companion.class +0 -0
  187. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/FileProgress.class +0 -0
  188. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ForeignBytes$ByValue.class +0 -0
  189. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ForeignBytes.class +0 -0
  190. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/GradientRoutingConfig$Companion.class +0 -0
  191. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/GradientRoutingConfig.class +0 -0
  192. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/IntegrityCheckingUniffiLib.class +0 -0
  193. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/InternalException.class +0 -0
  194. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/InternetMessage$Companion.class +0 -0
  195. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/InternetMessage.class +0 -0
  196. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/JavaLangRefCleanable.class +0 -0
  197. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/JavaLangRefCleaner.class +0 -0
  198. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/MessagePriority$Companion.class +0 -0
  199. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/MessagePriority.class +0 -0
  200. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/MessageStats$Companion.class +0 -0
  201. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/MessageStats.class +0 -0
  202. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/NetworkLink$Companion.class +0 -0
  203. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/NetworkLink.class +0 -0
  204. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/NetworkNode$Companion.class +0 -0
  205. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/NetworkNode.class +0 -0
  206. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/NetworkTopology$Companion.class +0 -0
  207. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/NetworkTopology.class +0 -0
  208. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/NoHandle.class +0 -0
  209. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/OfflineProtocol$Companion.class +0 -0
  210. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/OfflineProtocol$UniffiCleanAction.class +0 -0
  211. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/OfflineProtocol.class +0 -0
  212. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/OfflineProtocolInterface$Companion.class +0 -0
  213. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/OfflineProtocolInterface.class +0 -0
  214. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/Offline_protocolKt.class +0 -0
  215. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/PathConfig$Companion.class +0 -0
  216. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/PathConfig.class +0 -0
  217. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/PeerDevice$Companion.class +0 -0
  218. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/PeerDevice.class +0 -0
  219. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ProtocolConfig$Companion.class +0 -0
  220. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ProtocolConfig.class +0 -0
  221. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ProtocolConfigExtended$Companion.class +0 -0
  222. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ProtocolConfigExtended.class +0 -0
  223. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ProtocolException$AlreadyStarted.class +0 -0
  224. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ProtocolException$ErrorHandler.class +0 -0
  225. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ProtocolException$InvalidConfiguration.class +0 -0
  226. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ProtocolException$InvalidState.class +0 -0
  227. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ProtocolException$NotStarted.class +0 -0
  228. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ProtocolException$Other.class +0 -0
  229. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ProtocolException$SendFailed.class +0 -0
  230. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ProtocolException.class +0 -0
  231. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ProtocolState$Companion.class +0 -0
  232. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ProtocolState.class +0 -0
  233. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/RelayConfig$Companion.class +0 -0
  234. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/RelayConfig.class +0 -0
  235. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/RelayPriority$Companion.class +0 -0
  236. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/RelayPriority.class +0 -0
  237. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ReliabilityConfig$Companion.class +0 -0
  238. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/ReliabilityConfig.class +0 -0
  239. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/RetryConfig$Companion.class +0 -0
  240. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/RetryConfig.class +0 -0
  241. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/RouteEntry$Companion.class +0 -0
  242. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/RouteEntry.class +0 -0
  243. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/RoutingStats$Companion.class +0 -0
  244. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/RoutingStats.class +0 -0
  245. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/RustBuffer$ByReference.class +0 -0
  246. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/RustBuffer$ByValue.class +0 -0
  247. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/RustBuffer$Companion.class +0 -0
  248. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/RustBuffer.class +0 -0
  249. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/TransportConfig$Companion.class +0 -0
  250. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/TransportConfig.class +0 -0
  251. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/TransportMetrics$Companion.class +0 -0
  252. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/TransportMetrics.class +0 -0
  253. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/TransportType$Companion.class +0 -0
  254. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/TransportType.class +0 -0
  255. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiCallbackInterfaceClone.class +0 -0
  256. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiCallbackInterfaceEventCallbackMethod0.class +0 -0
  257. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiCallbackInterfaceFree.class +0 -0
  258. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiCleaner$Cleanable.class +0 -0
  259. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiCleaner$Companion.class +0 -0
  260. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiCleaner.class +0 -0
  261. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureCompleteF32.class +0 -0
  262. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureCompleteF64.class +0 -0
  263. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureCompleteI16.class +0 -0
  264. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureCompleteI32.class +0 -0
  265. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureCompleteI64.class +0 -0
  266. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureCompleteI8.class +0 -0
  267. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureCompleteRustBuffer.class +0 -0
  268. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureCompleteU16.class +0 -0
  269. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureCompleteU32.class +0 -0
  270. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureCompleteU64.class +0 -0
  271. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureCompleteU8.class +0 -0
  272. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureCompleteVoid.class +0 -0
  273. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureDroppedCallback.class +0 -0
  274. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureDroppedCallbackStruct$UniffiByValue.class +0 -0
  275. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureDroppedCallbackStruct.class +0 -0
  276. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultF32$UniffiByValue.class +0 -0
  277. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultF32.class +0 -0
  278. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultF64$UniffiByValue.class +0 -0
  279. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultF64.class +0 -0
  280. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultI16$UniffiByValue.class +0 -0
  281. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultI16.class +0 -0
  282. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultI32$UniffiByValue.class +0 -0
  283. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultI32.class +0 -0
  284. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultI64$UniffiByValue.class +0 -0
  285. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultI64.class +0 -0
  286. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultI8$UniffiByValue.class +0 -0
  287. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultI8.class +0 -0
  288. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultRustBuffer$UniffiByValue.class +0 -0
  289. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultRustBuffer.class +0 -0
  290. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultU16$UniffiByValue.class +0 -0
  291. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultU16.class +0 -0
  292. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultU32$UniffiByValue.class +0 -0
  293. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultU32.class +0 -0
  294. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultU64$UniffiByValue.class +0 -0
  295. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultU64.class +0 -0
  296. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultU8$UniffiByValue.class +0 -0
  297. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultU8.class +0 -0
  298. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultVoid$UniffiByValue.class +0 -0
  299. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiForeignFutureResultVoid.class +0 -0
  300. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiHandleMap.class +0 -0
  301. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiJnaCleanable.class +0 -0
  302. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiJnaCleaner.class +0 -0
  303. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiLib.class +0 -0
  304. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiNullRustCallStatusErrorHandler.class +0 -0
  305. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiRustCallStatus$ByValue.class +0 -0
  306. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiRustCallStatus$Companion.class +0 -0
  307. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiRustCallStatus.class +0 -0
  308. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiRustCallStatusErrorHandler.class +0 -0
  309. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiRustFutureContinuationCallback.class +0 -0
  310. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiVTableCallbackInterfaceEventCallback$UniffiByValue.class +0 -0
  311. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiVTableCallbackInterfaceEventCallback.class +0 -0
  312. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/UniffiWithHandle.class +0 -0
  313. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/WifiDirectMessage$Companion.class +0 -0
  314. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/WifiDirectMessage.class +0 -0
  315. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/uniffiCallbackInterfaceEventCallback$onEvent.class +0 -0
  316. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/uniffiCallbackInterfaceEventCallback$uniffiClone.class +0 -0
  317. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/uniffiCallbackInterfaceEventCallback$uniffiFree.class +0 -0
  318. package/android/build/tmp/kotlin-classes/debug/uniffi/offline_protocol/uniffiCallbackInterfaceEventCallback.class +0 -0
  319. package/android/src/main/java/com/offlineprotocol/BleManager.kt +551 -33
  320. package/ios/BleManager.swift +323 -17
  321. package/package.json +1 -1
  322. /package/android/build/tmp/kotlin-classes/debug/META-INF/{offlineprotocol_react-native_debug.kotlin_module → offline-protocol_mesh-sdk_debug.kotlin_module} +0 -0
@@ -152,10 +152,16 @@ public class BleManager: NSObject, TransportManager {
152
152
  private var lastPeerCountUpdate: Date?
153
153
  private let SCAN_HEARTBEAT_INTERVAL: TimeInterval = 10.0
154
154
  private let SCAN_RESTART_INTERVAL: TimeInterval = 30.0
155
+ /// Force a complete BLE stack refresh periodically even when things seem healthy
156
+ private let FORCED_BLE_REFRESH_INTERVAL: TimeInterval = 120.0
157
+ private var lastForcedBleRefresh: Date?
155
158
  private var connectionMonitor: DispatchSourceTimer?
156
159
  private var connectionAttemptTimestamps: [UUID: Date] = [:]
160
+ private var connectionRetryCount: [UUID: Int] = [:]
157
161
  private let CONNECTION_MONITOR_INTERVAL: TimeInterval = 5.0
158
162
  private let MIN_RECONNECT_INTERVAL: TimeInterval = 5.0
163
+ private let MAX_RECONNECT_INTERVAL: TimeInterval = 60.0
164
+ private let MAX_CONNECTION_RETRIES = 5
159
165
  private var scanRestartCount: Int = 0
160
166
  private var lastCentralReset: Date?
161
167
  private let MAX_CONSECUTIVE_SCAN_RESTARTS = 3
@@ -163,7 +169,18 @@ public class BleManager: NSObject, TransportManager {
163
169
  private let MINIMUM_RSSI_TO_CONNECT: Int16 = -90
164
170
  /// Rate limiting for unknown connectable devices that need GATT verification
165
171
  private var unknownDeviceAttempts: [UUID: Date] = [:]
166
- private let UNKNOWN_DEVICE_RATE_LIMIT: TimeInterval = 10.0
172
+ private let UNKNOWN_DEVICE_RATE_LIMIT: TimeInterval = 5.0 // More aggressive for cross-platform discovery
173
+ private let UNKNOWN_DEVICE_MIN_RSSI: Int16 = -80 // More lenient for cross-platform discovery
174
+ private let MAX_UNKNOWN_DEVICE_ATTEMPTS_PER_MINUTE = 10
175
+ /// Proactive scan refresh interval even when discoveries are occurring
176
+ private let PROACTIVE_SCAN_REFRESH_INTERVAL: TimeInterval = 60.0
177
+ private var lastProactiveScanRefresh: Date?
178
+ /// Tracks recently seen advertisement hashes to avoid duplicate processing
179
+ private var recentAdvertisementHashes: [UUID: (hash: Int, timestamp: Date)] = [:]
180
+ /// Initial aggressive discovery phase duration - more frequent scanning initially
181
+ private let AGGRESSIVE_DISCOVERY_PHASE: TimeInterval = 30.0
182
+ /// Tracks when aggressive discovery phase started
183
+ private var aggressiveDiscoveryStarted: Date?
167
184
 
168
185
  // MARK: - Thread helpers
169
186
  @inline(__always)
@@ -391,10 +408,14 @@ public class BleManager: NSObject, TransportManager {
391
408
  pendingOutboundFragments.removeAll()
392
409
  lastSeenMeshAdvertisements.removeAll()
393
410
  unknownDeviceAttempts.removeAll()
411
+ recentAdvertisementHashes.removeAll()
394
412
  pendingAdvertiseRestart?.cancel()
395
413
  pendingAdvertiseRestart = nil
396
414
  lastAdvertiseRestartAt = nil
397
415
  transportStartAt = nil
416
+ lastProactiveScanRefresh = nil
417
+ lastForcedBleRefresh = nil
418
+ aggressiveDiscoveryStarted = nil
398
419
  subscribedCentrals.removeAll()
399
420
 
400
421
  // Clean up managers
@@ -490,8 +511,18 @@ public class BleManager: NSObject, TransportManager {
490
511
  options: [CBCentralManagerScanOptionAllowDuplicatesKey: true]
491
512
  )
492
513
  isScanning = true
493
- scanStartDate = Date()
514
+ let now = Date()
515
+ scanStartDate = now
494
516
  lastDiscoveryDate = scanStartDate
517
+ lastProactiveScanRefresh = now
518
+ // Start aggressive discovery phase for initial faster connection
519
+ if aggressiveDiscoveryStarted == nil {
520
+ aggressiveDiscoveryStarted = now
521
+ print("[BleManager] Starting aggressive discovery phase (\(AGGRESSIVE_DISCOVERY_PHASE)s)")
522
+ emitDiagnostic("info", "Starting aggressive discovery phase", context: [
523
+ "duration": AGGRESSIVE_DISCOVERY_PHASE
524
+ ])
525
+ }
495
526
  startScanMonitor()
496
527
  if logThrottler.shouldLog(key: "scan_started") {
497
528
  let context: [String: Any] = [
@@ -520,6 +551,7 @@ public class BleManager: NSObject, TransportManager {
520
551
  scanStartDate = nil
521
552
  lastDiscoveryDate = nil
522
553
  connectionAttemptTimestamps.removeAll()
554
+ connectionRetryCount.removeAll()
523
555
  if logThrottler.shouldLog(key: "scan_stopped") {
524
556
  print("[BleManager] Stopped scanning (reason: \(reason))")
525
557
  }
@@ -536,12 +568,61 @@ public class BleManager: NSObject, TransportManager {
536
568
  let now = Date()
537
569
  let lastActivity = self.lastDiscoveryDate ?? self.scanStartDate ?? now
538
570
  let idleDuration = now.timeIntervalSince(lastActivity)
571
+
572
+ // Check for inactivity-based restart
539
573
  if idleDuration >= self.SCAN_RESTART_INTERVAL {
540
574
  if self.logThrottler.shouldLog(key: "scan_watchdog", interval: self.SCAN_RESTART_INTERVAL) {
541
575
  print("[BleManager] Restarting scan after \(Int(idleDuration))s of inactivity")
542
576
  self.emitDiagnostic("warning", "Restarting BLE scan due to inactivity", context: ["idle_seconds": Int(idleDuration)])
543
577
  }
544
578
  self.restartScanningDueToInactivity()
579
+ return
580
+ }
581
+
582
+ // Proactive scan refresh even when discoveries are occurring
583
+ // This ensures we don't miss devices due to BLE stack issues
584
+ let lastRefresh = self.lastProactiveScanRefresh ?? self.scanStartDate ?? now
585
+ if now.timeIntervalSince(lastRefresh) >= self.PROACTIVE_SCAN_REFRESH_INTERVAL {
586
+ if self.logThrottler.shouldLog(key: "proactive_scan_refresh", interval: self.PROACTIVE_SCAN_REFRESH_INTERVAL) {
587
+ print("[BleManager] Proactively refreshing BLE scan")
588
+ self.emitDiagnostic("info", "Proactive scan refresh")
589
+ }
590
+ self.lastProactiveScanRefresh = now
591
+ self.restartScanningDueToInactivity()
592
+ }
593
+
594
+ // Forced complete BLE refresh - more aggressive than proactive refresh
595
+ // This helps recover from edge cases where the BLE stack becomes stuck
596
+ let lastForced = self.lastForcedBleRefresh ?? self.transportStartAt ?? now
597
+ if now.timeIntervalSince(lastForced) >= self.FORCED_BLE_REFRESH_INTERVAL {
598
+ self.lastForcedBleRefresh = now
599
+ if self.logThrottler.shouldLog(key: "forced_ble_refresh", interval: self.FORCED_BLE_REFRESH_INTERVAL) {
600
+ print("[BleManager] Performing forced BLE refresh for reliability")
601
+ self.emitDiagnostic("info", "Forced BLE refresh for reliability", context: [
602
+ "connectedPeers": self.connections.connectedPeripheralCount(),
603
+ "discoveredPeers": self.discoveredPeripherals.count
604
+ ])
605
+ }
606
+ // Stop and restart both scanning and advertising
607
+ self.stopScanning(reason: "forced_refresh")
608
+ self.refreshAdvertising(reason: "forced_refresh")
609
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
610
+ self?.startScanning(reason: "forced_refresh")
611
+ }
612
+ }
613
+
614
+ // After aggressive phase ends, do a targeted scan with service UUID filter
615
+ // This can help discover Android devices that might have been missed
616
+ if let started = self.aggressiveDiscoveryStarted,
617
+ now.timeIntervalSince(started) >= self.AGGRESSIVE_DISCOVERY_PHASE,
618
+ now.timeIntervalSince(started) < self.AGGRESSIVE_DISCOVERY_PHASE + self.SCAN_HEARTBEAT_INTERVAL {
619
+ print("[BleManager] Aggressive phase ended, performing targeted service scan")
620
+ self.emitDiagnostic("info", "Aggressive phase ended, performing targeted service scan", context: [
621
+ "discoveredPeers": self.discoveredPeripherals.count,
622
+ "connectedPeers": self.connections.connectedPeripheralCount()
623
+ ])
624
+ // Brief targeted scan with service UUID
625
+ self.performTargetedServiceScan()
545
626
  }
546
627
  }
547
628
  timer.resume()
@@ -566,6 +647,33 @@ public class BleManager: NSObject, TransportManager {
566
647
  }
567
648
  }
568
649
 
650
+ /// Performs a brief targeted scan with service UUID filter.
651
+ /// This helps discover Android devices that might not advertise our service UUID
652
+ /// in the main advertisement packet but include it in scan response.
653
+ private func performTargetedServiceScan() {
654
+ guard let central = centralManager, central.state == .poweredOn else { return }
655
+ guard isScanning else { return }
656
+
657
+ // Brief stop and restart with service filter
658
+ central.stopScan()
659
+
660
+ // Scan with service UUID filter for 5 seconds
661
+ central.scanForPeripherals(
662
+ withServices: [SERVICE_UUID],
663
+ options: [CBCentralManagerScanOptionAllowDuplicatesKey: true]
664
+ )
665
+
666
+ // After 5 seconds, go back to filterless scanning
667
+ DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) { [weak self] in
668
+ guard let self = self, self.isScanning else { return }
669
+ self.centralManager?.stopScan()
670
+ self.centralManager?.scanForPeripherals(
671
+ withServices: nil,
672
+ options: [CBCentralManagerScanOptionAllowDuplicatesKey: true]
673
+ )
674
+ }
675
+ }
676
+
569
677
  private func evaluateCentralHealthAfterRestart() {
570
678
  guard scanRestartCount >= MAX_CONSECUTIVE_SCAN_RESTARTS else { return }
571
679
  let now = Date()
@@ -625,13 +733,22 @@ public class BleManager: NSObject, TransportManager {
625
733
  private func attemptConnection(to peripheral: CBPeripheral, reason: String, rssi: Int16? = nil, desiredRole: MeshController.MeshRole? = nil) {
626
734
  DispatchQueue.main.async { [weak self] in
627
735
  guard let self = self else { return }
736
+
737
+ // Atomic check-and-connect: all checks must pass before proceeding
738
+ // This prevents race conditions when multiple discovery callbacks try to connect
739
+
740
+ // 1. Already connected check
628
741
  if self.connections.connectedPeripheral(for: peripheral.identifier) != nil {
629
742
  return
630
743
  }
744
+
745
+ // 2. Recent attempt cooldown check
631
746
  let now = Date()
632
747
  if let lastAttempt = self.connectionAttemptTimestamps[peripheral.identifier], now.timeIntervalSince(lastAttempt) < self.MIN_RECONNECT_INTERVAL {
633
748
  return
634
749
  }
750
+
751
+ // 3. RSSI threshold check
635
752
  if let effectiveRSSI = rssi ?? self.peripheralRSSI[peripheral.identifier], effectiveRSSI < self.MINIMUM_RSSI_TO_CONNECT {
636
753
  if self.logThrottler.shouldLog(key: "rssi_skip_\(peripheral.identifier.uuidString)", interval: 10) {
637
754
  self.emitDiagnostic("debug", "Skipping BLE connect due to weak RSSI", context: [
@@ -642,12 +759,24 @@ public class BleManager: NSObject, TransportManager {
642
759
  }
643
760
  return
644
761
  }
762
+
763
+ // 4. Connection capacity check (atomic with the connect call)
645
764
  if self.currentConnectionCount() >= self.MAX_CONNECTIONS_PER_DEVICE {
646
765
  if self.logThrottler.shouldLog(key: "mesh_conn_cap_ios", interval: 10) {
647
766
  print("[BleManager] Connection cap reached, not connecting to \(peripheral.identifier)")
648
767
  }
649
768
  return
650
769
  }
770
+
771
+ // 5. Double-check peripheral state before connecting
772
+ guard peripheral.state != .connecting else {
773
+ if self.logThrottler.shouldLog(key: "already_connecting_\(peripheral.identifier.uuidString)", interval: 5) {
774
+ print("[BleManager] Already connecting to \(peripheral.identifier)")
775
+ }
776
+ return
777
+ }
778
+
779
+ // All checks passed - proceed with connection
651
780
  self.connectionAttemptTimestamps[peripheral.identifier] = now
652
781
  if let desiredRole = desiredRole {
653
782
  self.connections.setPendingRole(desiredRole, for: peripheral.identifier)
@@ -655,6 +784,7 @@ public class BleManager: NSObject, TransportManager {
655
784
  self.connections.setPendingRole(.member, for: peripheral.identifier)
656
785
  }
657
786
  peripheral.delegate = self
787
+
658
788
  if peripheral.state == .connected {
659
789
  self.connections.registerPeripheral(peripheral)
660
790
  if let service = peripheral.services?.first(where: { $0.uuid == self.SERVICE_UUID }) {
@@ -664,6 +794,7 @@ public class BleManager: NSObject, TransportManager {
664
794
  }
665
795
  return
666
796
  }
797
+
667
798
  self.centralManager?.connect(peripheral, options: nil)
668
799
  if self.logThrottler.shouldLog(key: "connect_attempt_\(peripheral.identifier.uuidString)", interval: 10) {
669
800
  print("[BleManager] Attempting connection to \(peripheral.identifier) (reason: \(reason))")
@@ -1034,6 +1165,7 @@ public class BleManager: NSObject, TransportManager {
1034
1165
  pendingFragments.removeValue(forKey: identifier)
1035
1166
  pendingOutboundFragments.removeValue(forKey: deviceId)
1036
1167
  connectionAttemptTimestamps.removeValue(forKey: identifier)
1168
+ connectionRetryCount.removeValue(forKey: identifier)
1037
1169
  meshController.registerDisconnection(peerId: deviceId)
1038
1170
  refreshSelfMetrics()
1039
1171
 
@@ -1259,6 +1391,13 @@ public class BleManager: NSObject, TransportManager {
1259
1391
  /// Checks if we should skip this peripheral based on RSSI filtering.
1260
1392
  /// Returns true if the signal is too weak and we're in a dense environment.
1261
1393
  private func shouldFilterByRssi(_ rssi: Int16) -> Bool {
1394
+ // During aggressive discovery phase, don't apply density-based filtering
1395
+ if let started = aggressiveDiscoveryStarted,
1396
+ Date().timeIntervalSince(started) < AGGRESSIVE_DISCOVERY_PHASE {
1397
+ // Only filter out extremely weak signals during aggressive phase
1398
+ return rssi < MINIMUM_RSSI_TO_CONNECT
1399
+ }
1400
+
1262
1401
  // In dense networks, apply stricter RSSI filtering
1263
1402
  let threshold: Int16
1264
1403
  if estimatedVisiblePeerCount > ADAPTIVE_HIGH_DENSITY_THRESHOLD {
@@ -1277,19 +1416,34 @@ public class BleManager: NSObject, TransportManager {
1277
1416
  /// Checks if we should throttle connection attempts based on rate limits.
1278
1417
  /// Returns true if we should skip this connection attempt.
1279
1418
  private func shouldThrottleConnection(to peripheral: UUID, now: Date) -> Bool {
1419
+ // During aggressive discovery phase, use much shorter cooldowns
1420
+ let isAggressivePhase = aggressiveDiscoveryStarted.map { now.timeIntervalSince($0) < AGGRESSIVE_DISCOVERY_PHASE } ?? false
1421
+
1280
1422
  // Prune old entries
1281
1423
  let oneMinuteAgo = now.addingTimeInterval(-60.0)
1282
1424
  globalConnectionAttempts = globalConnectionAttempts.filter { $0 > oneMinuteAgo }
1425
+
1426
+ let effectiveCooldown: TimeInterval = isAggressivePhase ? 5.0 : ADAPTIVE_COOLDOWN_PER_PERIPHERAL
1283
1427
  peripheralConnectionAttempts = peripheralConnectionAttempts.filter {
1284
- now.timeIntervalSince($0.value) < ADAPTIVE_COOLDOWN_PER_PERIPHERAL
1428
+ now.timeIntervalSince($0.value) < effectiveCooldown
1285
1429
  }
1286
1430
 
1287
1431
  // Check per-peripheral cooldown
1288
1432
  if let lastAttempt = peripheralConnectionAttempts[peripheral],
1289
- now.timeIntervalSince(lastAttempt) < ADAPTIVE_COOLDOWN_PER_PERIPHERAL {
1433
+ now.timeIntervalSince(lastAttempt) < effectiveCooldown {
1290
1434
  return true
1291
1435
  }
1292
1436
 
1437
+ // During aggressive phase, allow more connection attempts
1438
+ if isAggressivePhase {
1439
+ // Allow up to 3x the normal rate during aggressive phase
1440
+ let maxAttempts = ADAPTIVE_MAX_CONNECTIONS_PER_MINUTE * 3
1441
+ if globalConnectionAttempts.count >= maxAttempts {
1442
+ return true
1443
+ }
1444
+ return false
1445
+ }
1446
+
1293
1447
  // In dense networks, apply global rate limiting
1294
1448
  if estimatedVisiblePeerCount > ADAPTIVE_LOW_DENSITY_THRESHOLD {
1295
1449
  let maxAttempts = ADAPTIVE_MAX_CONNECTIONS_PER_MINUTE
@@ -1362,6 +1516,21 @@ public class BleManager: NSObject, TransportManager {
1362
1516
  }
1363
1517
  }
1364
1518
 
1519
+ // 2b. Check overflow service UUIDs - Android devices sometimes advertise in overflow area
1520
+ // when the main advertisement packet is full
1521
+ if let overflowUUIDs = advertisementData[CBAdvertisementDataOverflowServiceUUIDsKey] as? [CBUUID] {
1522
+ if overflowUUIDs.contains(SERVICE_UUID) {
1523
+ return true
1524
+ }
1525
+ }
1526
+
1527
+ // 2c. Check solicited service UUIDs - some Android devices use this
1528
+ if let solicitedUUIDs = advertisementData[CBAdvertisementDataSolicitedServiceUUIDsKey] as? [CBUUID] {
1529
+ if solicitedUUIDs.contains(SERVICE_UUID) {
1530
+ return true
1531
+ }
1532
+ }
1533
+
1365
1534
  // 3. Check if this is a previously discovered mesh device
1366
1535
  if lastSeenMeshAdvertisements[peripheral.identifier] != nil {
1367
1536
  return true
@@ -1381,6 +1550,8 @@ public class BleManager: NSObject, TransportManager {
1381
1550
 
1382
1551
  // 6. For unknown connectable devices, allow with rate limiting
1383
1552
  // These will be verified via GATT service discovery after connection
1553
+ // This is CRITICAL for iOS ↔ Android cross-platform discovery since
1554
+ // each platform may not recognize the other's service UUID format in advertisements
1384
1555
  let isConnectable: Bool
1385
1556
  if #available(iOS 13.0, *) {
1386
1557
  isConnectable = (advertisementData[CBAdvertisementDataIsConnectable] as? NSNumber)?.boolValue ?? false
@@ -1395,14 +1566,33 @@ public class BleManager: NSObject, TransportManager {
1395
1566
  return false
1396
1567
  }
1397
1568
 
1398
- // Only process strong signals for unknown devices
1399
- if rssi >= -75 {
1569
+ // Check global rate limit for unknown device attempts
1570
+ let oneMinuteAgo = now.addingTimeInterval(-60.0)
1571
+ let recentUnknownAttempts = unknownDeviceAttempts.values.filter { $0 > oneMinuteAgo }.count
1572
+ if recentUnknownAttempts >= MAX_UNKNOWN_DEVICE_ATTEMPTS_PER_MINUTE {
1573
+ return false
1574
+ }
1575
+
1576
+ // Use a tiered approach: stronger signals get priority
1577
+ let shouldAttempt: Bool
1578
+ if rssi >= -70 {
1579
+ // Strong signal - always try
1580
+ shouldAttempt = true
1581
+ } else if rssi >= UNKNOWN_DEVICE_MIN_RSSI && recentUnknownAttempts < MAX_UNKNOWN_DEVICE_ATTEMPTS_PER_MINUTE / 2 {
1582
+ // Moderate signal and we have budget
1583
+ shouldAttempt = true
1584
+ } else {
1585
+ shouldAttempt = false
1586
+ }
1587
+
1588
+ if shouldAttempt {
1400
1589
  unknownDeviceAttempts[peripheral.identifier] = now
1401
1590
  if logThrottler.shouldLog(key: "unknown_connectable_\(peripheral.identifier.uuidString)", interval: 30) {
1402
1591
  print("[BleManager] Allowing unknown connectable device for GATT verification: \(peripheral.identifier) RSSI=\(rssi)")
1403
1592
  emitDiagnostic("debug", "Allowing unknown device for GATT verification", context: [
1404
1593
  "identifier": peripheral.identifier.uuidString,
1405
- "rssi": rssi
1594
+ "rssi": rssi,
1595
+ "recentAttempts": recentUnknownAttempts
1406
1596
  ])
1407
1597
  }
1408
1598
  return true
@@ -1419,6 +1609,32 @@ public class BleManager: NSObject, TransportManager {
1419
1609
  }
1420
1610
  return nil
1421
1611
  }
1612
+
1613
+ /// Computes a hash of the advertisement data for duplicate detection.
1614
+ /// Uses peripheral ID, RSSI bucket, and key advertisement data.
1615
+ private func computeAdvertisementHash(peripheral: CBPeripheral, advertisementData: [String: Any], rssi: Int16) -> Int {
1616
+ var hasher = Hasher()
1617
+ hasher.combine(peripheral.identifier)
1618
+ // Use RSSI buckets of 5 dBm to avoid hash changes from minor signal fluctuations
1619
+ hasher.combine(rssi / 5)
1620
+
1621
+ // Include service UUIDs if present
1622
+ if let serviceUUIDs = advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID] {
1623
+ for uuid in serviceUUIDs {
1624
+ hasher.combine(uuid.uuidString)
1625
+ }
1626
+ }
1627
+
1628
+ // Include service data if present
1629
+ if let serviceData = advertisementData[CBAdvertisementDataServiceDataKey] as? [CBUUID: Data] {
1630
+ for (uuid, data) in serviceData {
1631
+ hasher.combine(uuid.uuidString)
1632
+ hasher.combine(data)
1633
+ }
1634
+ }
1635
+
1636
+ return hasher.finalize()
1637
+ }
1422
1638
 
1423
1639
  private func maybeHandleRebalance(reason: String) {
1424
1640
  pruneMeshObservations()
@@ -1592,6 +1808,23 @@ extension BleManager: CBCentralManagerDelegate {
1592
1808
  // Adaptive scanning: track discoveries for density estimation
1593
1809
  recordDiscoveryForDensity(now: now)
1594
1810
 
1811
+ // Duplicate advertisement detection - avoid processing identical advertisements
1812
+ // This improves performance in dense networks where the same device may be seen many times
1813
+ let advertHash = computeAdvertisementHash(peripheral: peripheral, advertisementData: advertisementData, rssi: rssiValue)
1814
+ if let cached = recentAdvertisementHashes[peripheral.identifier] {
1815
+ // If we've seen this exact advertisement recently, skip processing
1816
+ if cached.hash == advertHash && now.timeIntervalSince(cached.timestamp) < 1.0 {
1817
+ return
1818
+ }
1819
+ }
1820
+ recentAdvertisementHashes[peripheral.identifier] = (hash: advertHash, timestamp: now)
1821
+
1822
+ // Prune old advertisement cache entries periodically
1823
+ if recentAdvertisementHashes.count > 100 {
1824
+ let cutoff = now.addingTimeInterval(-30.0)
1825
+ recentAdvertisementHashes = recentAdvertisementHashes.filter { $0.value.timestamp > cutoff }
1826
+ }
1827
+
1595
1828
  // Smart filtering for iOS ↔ Android interoperability
1596
1829
  // Since we scan without a service UUID filter (for Android compatibility),
1597
1830
  // we need to filter discovered peripherals here instead.
@@ -1669,11 +1902,25 @@ extension BleManager: CBCentralManagerDelegate {
1669
1902
  }
1670
1903
 
1671
1904
  // Adaptive scanning: rate limit connection attempts
1672
- if shouldThrottleConnection(to: peripheral.identifier, now: now) {
1673
- if logThrottler.shouldLog(key: "adaptive_throttle_\(peripheral.identifier.uuidString)", interval: 30) {
1674
- print("[BleManager] Adaptive: throttling connection to \(peripheral.identifier)")
1905
+ // Skip throttling for first-time discoveries with strong signals for faster connection
1906
+ let notSeenBefore = lastSeenMeshAdvertisements[peripheral.identifier] == nil
1907
+ let notConnected = connections.connectedPeripheral(for: peripheral.identifier) == nil
1908
+ let isFirstDiscovery = notSeenBefore && notConnected
1909
+ let hasStrongSignal = rssiValue >= -70
1910
+
1911
+ if !isFirstDiscovery || !hasStrongSignal {
1912
+ if shouldThrottleConnection(to: peripheral.identifier, now: now) {
1913
+ if logThrottler.shouldLog(key: "adaptive_throttle_\(peripheral.identifier.uuidString)", interval: 30) {
1914
+ print("[BleManager] Adaptive: throttling connection to \(peripheral.identifier)")
1915
+ }
1916
+ return
1675
1917
  }
1676
- return
1918
+ } else if isFirstDiscovery && hasStrongSignal {
1919
+ print("[BleManager] Fast-tracking first discovery with strong signal: \(peripheral.identifier) RSSI=\(rssiValue)")
1920
+ emitDiagnostic("info", "Fast-tracking first discovery", context: [
1921
+ "identifier": peripheral.identifier.uuidString,
1922
+ "rssi": rssiValue
1923
+ ])
1677
1924
  }
1678
1925
 
1679
1926
  let desiredRole: MeshController.MeshRole = (decision.intent == .interCluster) ? .bridge : .member
@@ -1710,6 +1957,7 @@ extension BleManager: CBCentralManagerDelegate {
1710
1957
 
1711
1958
  connections.registerPeripheral(peripheral)
1712
1959
  connectionAttemptTimestamps.removeValue(forKey: peripheral.identifier)
1960
+ connectionRetryCount.removeValue(forKey: peripheral.identifier) // Reset retry count on successful connection
1713
1961
 
1714
1962
  // Discover services
1715
1963
  peripheral.discoverServices([SERVICE_UUID])
@@ -1717,14 +1965,36 @@ extension BleManager: CBCentralManagerDelegate {
1717
1965
 
1718
1966
  public func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
1719
1967
  print("[BleManager] Failed to connect to peripheral: \(error?.localizedDescription ?? "unknown")")
1968
+
1969
+ // Increment retry count and calculate backoff
1970
+ let retryCount = (connectionRetryCount[peripheral.identifier] ?? 0) + 1
1971
+ connectionRetryCount[peripheral.identifier] = retryCount
1972
+
1720
1973
  emitDiagnostic("error", "Failed to connect to BLE peripheral", context: [
1721
1974
  "identifier": peripheral.identifier.uuidString,
1722
- "error": error?.localizedDescription ?? "unknown"
1975
+ "error": error?.localizedDescription ?? "unknown",
1976
+ "retryCount": retryCount
1723
1977
  ])
1724
1978
  connectionAttemptTimestamps.removeValue(forKey: peripheral.identifier)
1725
1979
  _ = connections.consumePendingRole(for: peripheral.identifier)
1726
- DispatchQueue.main.asyncAfter(deadline: .now() + MIN_RECONNECT_INTERVAL) { [weak self] in
1727
- self?.attemptConnection(to: peripheral, reason: "retry_fail")
1980
+
1981
+ // Give up after max retries
1982
+ guard retryCount <= MAX_CONNECTION_RETRIES else {
1983
+ print("[BleManager] Max retries (\(MAX_CONNECTION_RETRIES)) exceeded for \(peripheral.identifier), giving up")
1984
+ emitDiagnostic("warning", "Max connection retries exceeded", context: [
1985
+ "identifier": peripheral.identifier.uuidString,
1986
+ "retryCount": retryCount
1987
+ ])
1988
+ connectionRetryCount.removeValue(forKey: peripheral.identifier)
1989
+ return
1990
+ }
1991
+
1992
+ // Exponential backoff: 5s, 10s, 20s, 40s, 60s (capped)
1993
+ let backoffInterval = min(MAX_RECONNECT_INTERVAL, MIN_RECONNECT_INTERVAL * pow(2.0, Double(retryCount - 1)))
1994
+
1995
+ DispatchQueue.main.asyncAfter(deadline: .now() + backoffInterval) { [weak self] in
1996
+ guard let self = self, self.state == .running else { return }
1997
+ self.attemptConnection(to: peripheral, reason: "retry_fail")
1728
1998
  }
1729
1999
  }
1730
2000
 
@@ -1775,8 +2045,34 @@ extension BleManager: CBCentralManagerDelegate {
1775
2045
  }
1776
2046
  }
1777
2047
 
1778
- // Attempt reconnection after a short delay
1779
- DispatchQueue.main.asyncAfter(deadline: .now() + MIN_RECONNECT_INTERVAL) { [weak self] in
2048
+ // Attempt reconnection with exponential backoff
2049
+ let retryCount = (connectionRetryCount[peripheral.identifier] ?? 0) + 1
2050
+ connectionRetryCount[peripheral.identifier] = retryCount
2051
+
2052
+ // Give up after max retries
2053
+ guard retryCount <= MAX_CONNECTION_RETRIES else {
2054
+ print("[BleManager] Max retries (\(MAX_CONNECTION_RETRIES)) exceeded for \(peripheral.identifier) on disconnect, giving up")
2055
+ connectionRetryCount.removeValue(forKey: peripheral.identifier)
2056
+ // Notify peer lost since we're giving up
2057
+ if let deviceId = connections.peripheralDeviceId(for: peripheral.identifier) {
2058
+ protocolInstance.removeNeighborRoutes(neighborId: deviceId)
2059
+ try? self.protocolInstance.blePeerLost(peerId: deviceId)
2060
+ meshController.registerDisconnection(peerId: deviceId)
2061
+ refreshSelfMetrics()
2062
+ connections.removeConnectionRole(for: deviceId)
2063
+ connections.removePeripheralDeviceId(for: peripheral.identifier)
2064
+ connections.removeCentralDeviceId(for: peripheral.identifier)
2065
+ DispatchQueue.main.async {
2066
+ self.refreshAdvertising(reason: "disconnect_max_retries")
2067
+ }
2068
+ maybeHandleRebalance(reason: "disconnect_max_retries")
2069
+ }
2070
+ return
2071
+ }
2072
+
2073
+ let backoffInterval = min(MAX_RECONNECT_INTERVAL, MIN_RECONNECT_INTERVAL * pow(2.0, Double(retryCount - 1)))
2074
+
2075
+ DispatchQueue.main.asyncAfter(deadline: .now() + backoffInterval) { [weak self] in
1780
2076
  guard let self = self else { return }
1781
2077
  if self.state == .running && self.discoveredPeripherals[peripheral.identifier] != nil {
1782
2078
  self.attemptConnection(to: peripheral, reason: "retry_disconnect")
@@ -2118,6 +2414,14 @@ extension BleManager: CBPeripheralManagerDelegate {
2118
2414
  ])
2119
2415
  return
2120
2416
  }
2417
+
2418
+ // Track this central as subscribed for connection count
2419
+ subscribedCentrals.insert(central.identifier)
2420
+ print("[BleManager] Central subscribed: \(central.identifier)")
2421
+ emitDiagnostic("info", "Central subscribed to characteristic", context: [
2422
+ "central": central.identifier.uuidString,
2423
+ "totalSubscribed": subscribedCentrals.count
2424
+ ])
2121
2425
 
2122
2426
  if connections.centralDeviceId(for: central.identifier) == nil && connections.peripheralDeviceId(for: central.identifier) == nil {
2123
2427
  ensureDeviceId(for: central.identifier)
@@ -2131,9 +2435,11 @@ extension BleManager: CBPeripheralManagerDelegate {
2131
2435
 
2132
2436
  public func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didUnsubscribeFrom characteristic: CBCharacteristic) {
2133
2437
  print("[BleManager] Central unsubscribed from characteristic: \(characteristic.uuid)")
2438
+ subscribedCentrals.remove(central.identifier)
2134
2439
  emitDiagnostic("info", "Central unsubscribed", context: [
2135
2440
  "central": central.identifier.uuidString,
2136
- "characteristic": characteristic.uuid.uuidString
2441
+ "characteristic": characteristic.uuid.uuidString,
2442
+ "remainingSubscribed": subscribedCentrals.count
2137
2443
  ])
2138
2444
  connections.removeCentralDeviceId(for: central.identifier)
2139
2445
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@offline-protocol/mesh-sdk",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Offline-first mesh networking SDK with intelligent transport switching for React Native",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",