@offline-protocol/mesh-sdk 0.2.1 → 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 +571 -34
  320. package/ios/BleManager.swift +427 -14
  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,15 +152,35 @@ 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
162
168
  private let CENTRAL_RESET_BACKOFF: TimeInterval = 45.0
163
169
  private let MINIMUM_RSSI_TO_CONNECT: Int16 = -90
170
+ /// Rate limiting for unknown connectable devices that need GATT verification
171
+ private var unknownDeviceAttempts: [UUID: Date] = [:]
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?
164
184
 
165
185
  // MARK: - Thread helpers
166
186
  @inline(__always)
@@ -387,10 +407,15 @@ public class BleManager: NSObject, TransportManager {
387
407
  pendingFragments.removeAll()
388
408
  pendingOutboundFragments.removeAll()
389
409
  lastSeenMeshAdvertisements.removeAll()
410
+ unknownDeviceAttempts.removeAll()
411
+ recentAdvertisementHashes.removeAll()
390
412
  pendingAdvertiseRestart?.cancel()
391
413
  pendingAdvertiseRestart = nil
392
414
  lastAdvertiseRestartAt = nil
393
415
  transportStartAt = nil
416
+ lastProactiveScanRefresh = nil
417
+ lastForcedBleRefresh = nil
418
+ aggressiveDiscoveryStarted = nil
394
419
  subscribedCentrals.removeAll()
395
420
 
396
421
  // Clean up managers
@@ -477,13 +502,27 @@ public class BleManager: NSObject, TransportManager {
477
502
  scanRestartCount = 0
478
503
  }
479
504
 
505
+ // Scan without service UUID filter for iOS ↔ Android interoperability
506
+ // iOS's scanForPeripherals(withServices:) has known issues recognizing 128-bit
507
+ // service UUIDs from Android advertisements. Scanning with nil allows us to see
508
+ // all peripherals and filter in the discovery callback instead.
480
509
  central.scanForPeripherals(
481
- withServices: [SERVICE_UUID],
510
+ withServices: nil,
482
511
  options: [CBCentralManagerScanOptionAllowDuplicatesKey: true]
483
512
  )
484
513
  isScanning = true
485
- scanStartDate = Date()
514
+ let now = Date()
515
+ scanStartDate = now
486
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
+ }
487
526
  startScanMonitor()
488
527
  if logThrottler.shouldLog(key: "scan_started") {
489
528
  let context: [String: Any] = [
@@ -512,6 +551,7 @@ public class BleManager: NSObject, TransportManager {
512
551
  scanStartDate = nil
513
552
  lastDiscoveryDate = nil
514
553
  connectionAttemptTimestamps.removeAll()
554
+ connectionRetryCount.removeAll()
515
555
  if logThrottler.shouldLog(key: "scan_stopped") {
516
556
  print("[BleManager] Stopped scanning (reason: \(reason))")
517
557
  }
@@ -528,12 +568,61 @@ public class BleManager: NSObject, TransportManager {
528
568
  let now = Date()
529
569
  let lastActivity = self.lastDiscoveryDate ?? self.scanStartDate ?? now
530
570
  let idleDuration = now.timeIntervalSince(lastActivity)
571
+
572
+ // Check for inactivity-based restart
531
573
  if idleDuration >= self.SCAN_RESTART_INTERVAL {
532
574
  if self.logThrottler.shouldLog(key: "scan_watchdog", interval: self.SCAN_RESTART_INTERVAL) {
533
575
  print("[BleManager] Restarting scan after \(Int(idleDuration))s of inactivity")
534
576
  self.emitDiagnostic("warning", "Restarting BLE scan due to inactivity", context: ["idle_seconds": Int(idleDuration)])
535
577
  }
536
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()
537
626
  }
538
627
  }
539
628
  timer.resume()
@@ -558,6 +647,33 @@ public class BleManager: NSObject, TransportManager {
558
647
  }
559
648
  }
560
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
+
561
677
  private func evaluateCentralHealthAfterRestart() {
562
678
  guard scanRestartCount >= MAX_CONSECUTIVE_SCAN_RESTARTS else { return }
563
679
  let now = Date()
@@ -617,13 +733,22 @@ public class BleManager: NSObject, TransportManager {
617
733
  private func attemptConnection(to peripheral: CBPeripheral, reason: String, rssi: Int16? = nil, desiredRole: MeshController.MeshRole? = nil) {
618
734
  DispatchQueue.main.async { [weak self] in
619
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
620
741
  if self.connections.connectedPeripheral(for: peripheral.identifier) != nil {
621
742
  return
622
743
  }
744
+
745
+ // 2. Recent attempt cooldown check
623
746
  let now = Date()
624
747
  if let lastAttempt = self.connectionAttemptTimestamps[peripheral.identifier], now.timeIntervalSince(lastAttempt) < self.MIN_RECONNECT_INTERVAL {
625
748
  return
626
749
  }
750
+
751
+ // 3. RSSI threshold check
627
752
  if let effectiveRSSI = rssi ?? self.peripheralRSSI[peripheral.identifier], effectiveRSSI < self.MINIMUM_RSSI_TO_CONNECT {
628
753
  if self.logThrottler.shouldLog(key: "rssi_skip_\(peripheral.identifier.uuidString)", interval: 10) {
629
754
  self.emitDiagnostic("debug", "Skipping BLE connect due to weak RSSI", context: [
@@ -634,12 +759,24 @@ public class BleManager: NSObject, TransportManager {
634
759
  }
635
760
  return
636
761
  }
762
+
763
+ // 4. Connection capacity check (atomic with the connect call)
637
764
  if self.currentConnectionCount() >= self.MAX_CONNECTIONS_PER_DEVICE {
638
765
  if self.logThrottler.shouldLog(key: "mesh_conn_cap_ios", interval: 10) {
639
766
  print("[BleManager] Connection cap reached, not connecting to \(peripheral.identifier)")
640
767
  }
641
768
  return
642
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
643
780
  self.connectionAttemptTimestamps[peripheral.identifier] = now
644
781
  if let desiredRole = desiredRole {
645
782
  self.connections.setPendingRole(desiredRole, for: peripheral.identifier)
@@ -647,6 +784,7 @@ public class BleManager: NSObject, TransportManager {
647
784
  self.connections.setPendingRole(.member, for: peripheral.identifier)
648
785
  }
649
786
  peripheral.delegate = self
787
+
650
788
  if peripheral.state == .connected {
651
789
  self.connections.registerPeripheral(peripheral)
652
790
  if let service = peripheral.services?.first(where: { $0.uuid == self.SERVICE_UUID }) {
@@ -656,6 +794,7 @@ public class BleManager: NSObject, TransportManager {
656
794
  }
657
795
  return
658
796
  }
797
+
659
798
  self.centralManager?.connect(peripheral, options: nil)
660
799
  if self.logThrottler.shouldLog(key: "connect_attempt_\(peripheral.identifier.uuidString)", interval: 10) {
661
800
  print("[BleManager] Attempting connection to \(peripheral.identifier) (reason: \(reason))")
@@ -1026,6 +1165,7 @@ public class BleManager: NSObject, TransportManager {
1026
1165
  pendingFragments.removeValue(forKey: identifier)
1027
1166
  pendingOutboundFragments.removeValue(forKey: deviceId)
1028
1167
  connectionAttemptTimestamps.removeValue(forKey: identifier)
1168
+ connectionRetryCount.removeValue(forKey: identifier)
1029
1169
  meshController.registerDisconnection(peerId: deviceId)
1030
1170
  refreshSelfMetrics()
1031
1171
 
@@ -1216,6 +1356,9 @@ public class BleManager: NSObject, TransportManager {
1216
1356
 
1217
1357
  private func pruneMeshObservations(now: Date = Date()) {
1218
1358
  lastSeenMeshAdvertisements = lastSeenMeshAdvertisements.filter { now.timeIntervalSince($0.value.timestamp) <= MESH_OBSERVATION_TTL }
1359
+
1360
+ // Also clean up stale unknown device rate limiting entries
1361
+ unknownDeviceAttempts = unknownDeviceAttempts.filter { now.timeIntervalSince($0.value) <= UNKNOWN_DEVICE_RATE_LIMIT * 3 }
1219
1362
  }
1220
1363
 
1221
1364
  // MARK: - Adaptive Scan Methods
@@ -1248,6 +1391,13 @@ public class BleManager: NSObject, TransportManager {
1248
1391
  /// Checks if we should skip this peripheral based on RSSI filtering.
1249
1392
  /// Returns true if the signal is too weak and we're in a dense environment.
1250
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
+
1251
1401
  // In dense networks, apply stricter RSSI filtering
1252
1402
  let threshold: Int16
1253
1403
  if estimatedVisiblePeerCount > ADAPTIVE_HIGH_DENSITY_THRESHOLD {
@@ -1266,19 +1416,34 @@ public class BleManager: NSObject, TransportManager {
1266
1416
  /// Checks if we should throttle connection attempts based on rate limits.
1267
1417
  /// Returns true if we should skip this connection attempt.
1268
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
+
1269
1422
  // Prune old entries
1270
1423
  let oneMinuteAgo = now.addingTimeInterval(-60.0)
1271
1424
  globalConnectionAttempts = globalConnectionAttempts.filter { $0 > oneMinuteAgo }
1425
+
1426
+ let effectiveCooldown: TimeInterval = isAggressivePhase ? 5.0 : ADAPTIVE_COOLDOWN_PER_PERIPHERAL
1272
1427
  peripheralConnectionAttempts = peripheralConnectionAttempts.filter {
1273
- now.timeIntervalSince($0.value) < ADAPTIVE_COOLDOWN_PER_PERIPHERAL
1428
+ now.timeIntervalSince($0.value) < effectiveCooldown
1274
1429
  }
1275
1430
 
1276
1431
  // Check per-peripheral cooldown
1277
1432
  if let lastAttempt = peripheralConnectionAttempts[peripheral],
1278
- now.timeIntervalSince(lastAttempt) < ADAPTIVE_COOLDOWN_PER_PERIPHERAL {
1433
+ now.timeIntervalSince(lastAttempt) < effectiveCooldown {
1279
1434
  return true
1280
1435
  }
1281
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
+
1282
1447
  // In dense networks, apply global rate limiting
1283
1448
  if estimatedVisiblePeerCount > ADAPTIVE_LOW_DENSITY_THRESHOLD {
1284
1449
  let maxAttempts = ADAPTIVE_MAX_CONNECTIONS_PER_MINUTE
@@ -1319,6 +1484,124 @@ public class BleManager: NSObject, TransportManager {
1319
1484
 
1320
1485
  return normalizedHash < skipProbability
1321
1486
  }
1487
+
1488
+ // MARK: - Smart Filtering for iOS ↔ Android Interoperability
1489
+
1490
+ /// Determines if a discovered peripheral should be processed.
1491
+ /// This implements smart filtering since we scan without a service UUID filter
1492
+ /// (required for iOS ↔ Android interoperability).
1493
+ ///
1494
+ /// Accepts:
1495
+ /// - Devices advertising our service UUID (iOS devices)
1496
+ /// - Devices with our service data
1497
+ /// - Previously discovered mesh devices
1498
+ /// - Unknown connectable devices (rate-limited, verified via GATT)
1499
+ private func shouldProcessDiscoveredPeripheral(
1500
+ peripheral: CBPeripheral,
1501
+ advertisementData: [String: Any],
1502
+ rssi: Int16,
1503
+ now: Date
1504
+ ) -> Bool {
1505
+ // 1. Check if device is advertising our service UUID
1506
+ if let serviceUUIDs = advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID] {
1507
+ if serviceUUIDs.contains(SERVICE_UUID) {
1508
+ return true
1509
+ }
1510
+ }
1511
+
1512
+ // 2. Check for our service data (may come from scan response)
1513
+ if let serviceData = advertisementData[CBAdvertisementDataServiceDataKey] as? [CBUUID: Data] {
1514
+ if serviceData[SERVICE_UUID] != nil {
1515
+ return true
1516
+ }
1517
+ }
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
+
1534
+ // 3. Check if this is a previously discovered mesh device
1535
+ if lastSeenMeshAdvertisements[peripheral.identifier] != nil {
1536
+ return true
1537
+ }
1538
+
1539
+ // 4. Check if we already have this peripheral in our discovered list
1540
+ // (previously connected or successfully verified via GATT)
1541
+ if discoveredPeripherals[peripheral.identifier] != nil {
1542
+ return true
1543
+ }
1544
+
1545
+ // 5. Check if we already have a device ID mapping for this peripheral
1546
+ if connections.peripheralDeviceId(for: peripheral.identifier) != nil ||
1547
+ connections.centralDeviceId(for: peripheral.identifier) != nil {
1548
+ return true
1549
+ }
1550
+
1551
+ // 6. For unknown connectable devices, allow with rate limiting
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
1555
+ let isConnectable: Bool
1556
+ if #available(iOS 13.0, *) {
1557
+ isConnectable = (advertisementData[CBAdvertisementDataIsConnectable] as? NSNumber)?.boolValue ?? false
1558
+ } else {
1559
+ isConnectable = true
1560
+ }
1561
+
1562
+ if isConnectable {
1563
+ // Rate limit unknown device connection attempts
1564
+ if let lastAttempt = unknownDeviceAttempts[peripheral.identifier],
1565
+ now.timeIntervalSince(lastAttempt) < UNKNOWN_DEVICE_RATE_LIMIT {
1566
+ return false
1567
+ }
1568
+
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 {
1589
+ unknownDeviceAttempts[peripheral.identifier] = now
1590
+ if logThrottler.shouldLog(key: "unknown_connectable_\(peripheral.identifier.uuidString)", interval: 30) {
1591
+ print("[BleManager] Allowing unknown connectable device for GATT verification: \(peripheral.identifier) RSSI=\(rssi)")
1592
+ emitDiagnostic("debug", "Allowing unknown device for GATT verification", context: [
1593
+ "identifier": peripheral.identifier.uuidString,
1594
+ "rssi": rssi,
1595
+ "recentAttempts": recentUnknownAttempts
1596
+ ])
1597
+ }
1598
+ return true
1599
+ }
1600
+ }
1601
+
1602
+ // Filter out all other devices (not our mesh network)
1603
+ return false
1604
+ }
1322
1605
 
1323
1606
  private func identifierForNodeHash(_ nodeHash: UInt64) -> UUID? {
1324
1607
  for (identifier, observation) in lastSeenMeshAdvertisements where observation.advertisement.nodeIdHash == nodeHash {
@@ -1326,6 +1609,32 @@ public class BleManager: NSObject, TransportManager {
1326
1609
  }
1327
1610
  return nil
1328
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
+ }
1329
1638
 
1330
1639
  private func maybeHandleRebalance(reason: String) {
1331
1640
  pruneMeshObservations()
@@ -1499,6 +1808,37 @@ extension BleManager: CBCentralManagerDelegate {
1499
1808
  // Adaptive scanning: track discoveries for density estimation
1500
1809
  recordDiscoveryForDensity(now: now)
1501
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
+
1828
+ // Smart filtering for iOS ↔ Android interoperability
1829
+ // Since we scan without a service UUID filter (for Android compatibility),
1830
+ // we need to filter discovered peripherals here instead.
1831
+ let shouldProcess = shouldProcessDiscoveredPeripheral(
1832
+ peripheral: peripheral,
1833
+ advertisementData: advertisementData,
1834
+ rssi: rssiValue,
1835
+ now: now
1836
+ )
1837
+
1838
+ if !shouldProcess {
1839
+ return
1840
+ }
1841
+
1502
1842
  // Adaptive scanning: early RSSI filtering in dense networks
1503
1843
  if shouldFilterByRssi(rssiValue) {
1504
1844
  if logThrottler.shouldLog(key: "adaptive_rssi_filter", interval: 10) {
@@ -1562,11 +1902,25 @@ extension BleManager: CBCentralManagerDelegate {
1562
1902
  }
1563
1903
 
1564
1904
  // Adaptive scanning: rate limit connection attempts
1565
- if shouldThrottleConnection(to: peripheral.identifier, now: now) {
1566
- if logThrottler.shouldLog(key: "adaptive_throttle_\(peripheral.identifier.uuidString)", interval: 30) {
1567
- 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
1568
1917
  }
1569
- 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
+ ])
1570
1924
  }
1571
1925
 
1572
1926
  let desiredRole: MeshController.MeshRole = (decision.intent == .interCluster) ? .bridge : .member
@@ -1603,6 +1957,7 @@ extension BleManager: CBCentralManagerDelegate {
1603
1957
 
1604
1958
  connections.registerPeripheral(peripheral)
1605
1959
  connectionAttemptTimestamps.removeValue(forKey: peripheral.identifier)
1960
+ connectionRetryCount.removeValue(forKey: peripheral.identifier) // Reset retry count on successful connection
1606
1961
 
1607
1962
  // Discover services
1608
1963
  peripheral.discoverServices([SERVICE_UUID])
@@ -1610,14 +1965,36 @@ extension BleManager: CBCentralManagerDelegate {
1610
1965
 
1611
1966
  public func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
1612
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
+
1613
1973
  emitDiagnostic("error", "Failed to connect to BLE peripheral", context: [
1614
1974
  "identifier": peripheral.identifier.uuidString,
1615
- "error": error?.localizedDescription ?? "unknown"
1975
+ "error": error?.localizedDescription ?? "unknown",
1976
+ "retryCount": retryCount
1616
1977
  ])
1617
1978
  connectionAttemptTimestamps.removeValue(forKey: peripheral.identifier)
1618
1979
  _ = connections.consumePendingRole(for: peripheral.identifier)
1619
- DispatchQueue.main.asyncAfter(deadline: .now() + MIN_RECONNECT_INTERVAL) { [weak self] in
1620
- 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")
1621
1998
  }
1622
1999
  }
1623
2000
 
@@ -1668,8 +2045,34 @@ extension BleManager: CBCentralManagerDelegate {
1668
2045
  }
1669
2046
  }
1670
2047
 
1671
- // Attempt reconnection after a short delay
1672
- 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
1673
2076
  guard let self = self else { return }
1674
2077
  if self.state == .running && self.discoveredPeripherals[peripheral.identifier] != nil {
1675
2078
  self.attemptConnection(to: peripheral, reason: "retry_disconnect")
@@ -2011,6 +2414,14 @@ extension BleManager: CBPeripheralManagerDelegate {
2011
2414
  ])
2012
2415
  return
2013
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
+ ])
2014
2425
 
2015
2426
  if connections.centralDeviceId(for: central.identifier) == nil && connections.peripheralDeviceId(for: central.identifier) == nil {
2016
2427
  ensureDeviceId(for: central.identifier)
@@ -2024,9 +2435,11 @@ extension BleManager: CBPeripheralManagerDelegate {
2024
2435
 
2025
2436
  public func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didUnsubscribeFrom characteristic: CBCharacteristic) {
2026
2437
  print("[BleManager] Central unsubscribed from characteristic: \(characteristic.uuid)")
2438
+ subscribedCentrals.remove(central.identifier)
2027
2439
  emitDiagnostic("info", "Central unsubscribed", context: [
2028
2440
  "central": central.identifier.uuidString,
2029
- "characteristic": characteristic.uuid.uuidString
2441
+ "characteristic": characteristic.uuid.uuidString,
2442
+ "remainingSubscribed": subscribedCentrals.count
2030
2443
  ])
2031
2444
  connections.removeCentralDeviceId(for: central.identifier)
2032
2445
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@offline-protocol/mesh-sdk",
3
- "version": "0.2.1",
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",