@react-native-firebase/app 26.3.3 → 27.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (283) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/README.md +10 -27
  3. package/RNFBApp.podspec +13 -5
  4. package/android/build.gradle +3 -0
  5. package/android/src/main/java/io/invertase/firebase/common/RNFBHandleCollisionException.kt +29 -0
  6. package/android/src/main/java/io/invertase/firebase/common/RNFBHandleMap.kt +146 -0
  7. package/android/src/main/java/io/invertase/firebase/common/TaskExecutorService.kt +156 -0
  8. package/android/src/main/java/io/invertase/firebase/common/UniversalFirebaseModule.kt +44 -0
  9. package/android/src/main/java/io/invertase/firebase/common/UniversalFirebasePreferences.kt +100 -0
  10. package/android/src/reactnative/java/io/invertase/firebase/app/NativeRNFBTurboApp.kt +250 -0
  11. package/android/src/reactnative/java/io/invertase/firebase/app/ReactNativeFirebaseApp.kt +64 -0
  12. package/android/src/reactnative/java/io/invertase/firebase/app/{ReactNativeFirebaseAppInitProvider.java → ReactNativeFirebaseAppInitProvider.kt} +3 -4
  13. package/android/src/reactnative/java/io/invertase/firebase/app/ReactNativeFirebaseAppPackage.kt +47 -0
  14. package/android/src/reactnative/java/io/invertase/firebase/app/ReactNativeFirebaseAppRegistrar.kt +33 -0
  15. package/android/src/reactnative/java/io/invertase/firebase/app/ReactNativeFirebaseVersion.java +1 -1
  16. package/android/src/reactnative/java/io/invertase/firebase/common/RCTConvertFirebase.kt +185 -0
  17. package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseEvent.kt +35 -0
  18. package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseEventEmitter.kt +662 -0
  19. package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseJSON.kt +103 -0
  20. package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseMeta.kt +96 -0
  21. package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseModule.kt +140 -0
  22. package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebasePreferences.kt +109 -0
  23. package/android/src/reactnative/java/io/invertase/firebase/common/SharedUtils.kt +451 -0
  24. package/android/src/reactnative/java/io/invertase/firebase/interfaces/{ContextProvider.java → ContextProvider.kt} +8 -8
  25. package/android/src/reactnative/java/io/invertase/firebase/interfaces/{NativeError.java → NativeError.kt} +8 -8
  26. package/android/src/reactnative/java/io/invertase/firebase/interfaces/{NativeEvent.java → NativeEvent.kt} +6 -6
  27. package/android/src/reactnative/java/io/invertase/firebase/utils/NativeRNFBTurboUtils.kt +219 -0
  28. package/android/src/test/java/expo/core/ModuleRegistry.java +9 -0
  29. package/android/src/test/java/io/invertase/firebase/app/NativeRNFBTurboAppTest.java +208 -60
  30. package/android/src/test/java/io/invertase/firebase/app/ReactNativeFirebaseAppComponentsJavaCompatibilityTest.java +70 -0
  31. package/android/src/test/java/io/invertase/firebase/app/ReactNativeFirebaseAppJavaCompatibilityTest.java +60 -0
  32. package/android/src/test/java/io/invertase/firebase/app/ReactNativeFirebaseAppPackageTest.java +165 -0
  33. package/android/src/test/java/io/invertase/firebase/app/ReactNativeFirebaseAppTest.kt +167 -0
  34. package/android/src/test/java/io/invertase/firebase/common/RCTConvertFirebaseJavaCompatibilityTest.java +112 -0
  35. package/android/src/test/java/io/invertase/firebase/common/RCTConvertFirebaseTest.kt +317 -0
  36. package/android/src/test/java/io/invertase/firebase/common/RNFBHandleMapTest.kt +309 -0
  37. package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebaseEventEmitterJavaCompatibilityTest.java +176 -0
  38. package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebaseEventEmitterTest.java +144 -13
  39. package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebaseEventTest.kt +61 -0
  40. package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebaseJSONJavaCompatibilityTest.java +85 -0
  41. package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebaseJSONTest.kt +296 -0
  42. package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebaseMetaJavaCompatibilityTest.java +67 -0
  43. package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebaseMetaTest.kt +246 -0
  44. package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebaseModuleTest.kt +254 -0
  45. package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebasePreferencesJavaCompatibilityTest.java +87 -0
  46. package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebasePreferencesTest.kt +260 -0
  47. package/android/src/test/java/io/invertase/firebase/common/SharedUtilsJavaCompatibilityTest.java +255 -0
  48. package/android/src/test/java/io/invertase/firebase/common/SharedUtilsTest.kt +674 -0
  49. package/android/src/test/java/io/invertase/firebase/common/TaskExecutorServiceTest.kt +290 -0
  50. package/android/src/test/java/io/invertase/firebase/common/UniversalFirebaseModuleJavaCompatibilityTest.java +178 -0
  51. package/android/src/test/java/io/invertase/firebase/common/UniversalFirebasePreferencesJavaCompatibilityTest.java +99 -0
  52. package/android/src/test/java/io/invertase/firebase/common/UniversalFirebasePreferencesTest.kt +217 -0
  53. package/android/src/test/java/io/invertase/firebase/commoncompat/ReactNativeFirebaseModuleJavaCompatibilityTest.java +176 -0
  54. package/android/src/test/java/io/invertase/firebase/commoncompat/ReactNativeFirebaseModuleKotlinCompatibilityTest.kt +52 -0
  55. package/android/src/test/java/io/invertase/firebase/interfaces/InterfacesJavaCompatibilityTest.java +144 -0
  56. package/android/src/test/java/io/invertase/firebase/interfaces/NativeEventTest.kt +56 -0
  57. package/android/src/test/java/io/invertase/firebase/utils/NativeRNFBTurboUtilsTest.java +517 -23
  58. package/dist/module/FirebaseApp.js +10 -0
  59. package/dist/module/FirebaseApp.js.map +1 -1
  60. package/dist/module/common/Base64.js +13 -21
  61. package/dist/module/common/Base64.js.map +1 -1
  62. package/dist/module/common/deeps.js +3 -0
  63. package/dist/module/common/deeps.js.map +1 -1
  64. package/dist/module/common/index.js +17 -9
  65. package/dist/module/common/index.js.map +1 -1
  66. package/dist/module/internal/FirebaseModule.js.map +1 -1
  67. package/dist/module/internal/RNFBNativeEventEmitter.js +12 -1
  68. package/dist/module/internal/RNFBNativeEventEmitter.js.map +1 -1
  69. package/dist/module/internal/SharedEventEmitter.js +3 -1
  70. package/dist/module/internal/SharedEventEmitter.js.map +1 -1
  71. package/dist/module/internal/asyncStorage.js +7 -11
  72. package/dist/module/internal/asyncStorage.js.map +1 -1
  73. package/dist/module/internal/index.js +1 -0
  74. package/dist/module/internal/index.js.map +1 -1
  75. package/dist/module/internal/logger.js +44 -40
  76. package/dist/module/internal/logger.js.map +1 -1
  77. package/dist/module/internal/nativeModuleAndroidIos.js +2 -29
  78. package/dist/module/internal/nativeModuleAndroidIos.js.map +1 -1
  79. package/dist/module/internal/nativeModuleDebug.js +74 -0
  80. package/dist/module/internal/nativeModuleDebug.js.map +1 -0
  81. package/dist/module/internal/nativeModuleWeb.js +9 -29
  82. package/dist/module/internal/nativeModuleWeb.js.map +1 -1
  83. package/dist/module/internal/registry/app.js +8 -2
  84. package/dist/module/internal/registry/app.js.map +1 -1
  85. package/dist/module/internal/registry/modular.js +2 -2
  86. package/dist/module/internal/registry/modular.js.map +1 -1
  87. package/dist/module/internal/registry/nativeModule.js +3 -3
  88. package/dist/module/internal/registry/nativeModule.js.map +1 -1
  89. package/dist/module/internal/web/RNFBAppModule.js +16 -15
  90. package/dist/module/internal/web/RNFBAppModule.js.map +1 -1
  91. package/dist/module/internal/web/memidb/lib/FakeEventTarget.js +3 -1
  92. package/dist/module/internal/web/memidb/lib/FakeEventTarget.js.map +1 -1
  93. package/dist/module/types/app.js.map +1 -1
  94. package/dist/module/types/internal.js +2 -0
  95. package/dist/module/version.js +1 -1
  96. package/dist/typescript/lib/FirebaseApp.d.ts +7 -0
  97. package/dist/typescript/lib/FirebaseApp.d.ts.map +1 -1
  98. package/dist/typescript/lib/common/Base64.d.ts.map +1 -1
  99. package/dist/typescript/lib/common/deeps.d.ts.map +1 -1
  100. package/dist/typescript/lib/common/index.d.ts.map +1 -1
  101. package/dist/typescript/lib/internal/FirebaseModule.d.ts +2 -3
  102. package/dist/typescript/lib/internal/FirebaseModule.d.ts.map +1 -1
  103. package/dist/typescript/lib/internal/NativeModules.d.ts +4 -1
  104. package/dist/typescript/lib/internal/NativeModules.d.ts.map +1 -1
  105. package/dist/typescript/lib/internal/RNFBNativeEventEmitter.d.ts +2 -2
  106. package/dist/typescript/lib/internal/RNFBNativeEventEmitter.d.ts.map +1 -1
  107. package/dist/typescript/lib/internal/SharedEventEmitter.d.ts +3 -3
  108. package/dist/typescript/lib/internal/SharedEventEmitter.d.ts.map +1 -1
  109. package/dist/typescript/lib/internal/asyncStorage.d.ts.map +1 -1
  110. package/dist/typescript/lib/internal/index.d.ts +1 -0
  111. package/dist/typescript/lib/internal/index.d.ts.map +1 -1
  112. package/dist/typescript/lib/internal/logger.d.ts.map +1 -1
  113. package/dist/typescript/lib/internal/nativeModuleAndroidIos.d.ts.map +1 -1
  114. package/dist/typescript/lib/internal/nativeModuleDebug.d.ts +2 -0
  115. package/dist/typescript/lib/internal/nativeModuleDebug.d.ts.map +1 -0
  116. package/dist/typescript/lib/internal/nativeModuleWeb.d.ts.map +1 -1
  117. package/dist/typescript/lib/internal/registry/app.d.ts.map +1 -1
  118. package/dist/typescript/lib/internal/web/RNFBAppModule.d.ts +7 -7
  119. package/dist/typescript/lib/internal/web/RNFBAppModule.d.ts.map +1 -1
  120. package/dist/typescript/lib/types/app.d.ts +10 -3
  121. package/dist/typescript/lib/types/app.d.ts.map +1 -1
  122. package/dist/typescript/lib/types/internal.d.ts +12 -0
  123. package/dist/typescript/lib/types/internal.d.ts.map +1 -1
  124. package/dist/typescript/lib/version.d.ts +1 -1
  125. package/firebase-schema.json +5 -1
  126. package/firebase_spm.rb +552 -96
  127. package/ios/RNFBApp/RCTConvert+FIRApp.h +2 -5
  128. package/ios/RNFBApp/RCTConvert+FIRApp.m +11 -6
  129. package/ios/RNFBApp/RCTConvert+FIROptions.h +2 -5
  130. package/ios/RNFBApp/RCTConvert+FIROptions.m +11 -10
  131. package/ios/RNFBApp/RCTConvertFIRApp.swift +86 -0
  132. package/ios/RNFBApp/RCTConvertFIROptions.swift +259 -0
  133. package/ios/RNFBApp/RNFBAppCustomAuthDomains.swift +63 -0
  134. package/ios/RNFBApp/RNFBAppInitializeOptionsMapper.swift +113 -0
  135. package/ios/RNFBApp/RNFBAppLogLevelMapper.swift +53 -0
  136. package/ios/RNFBApp/RNFBAppModule+CustomAuthDomain.m +35 -0
  137. package/ios/RNFBApp/RNFBAppModule.h +6 -2
  138. package/ios/RNFBApp/RNFBAppModule.mm +67 -204
  139. package/ios/RNFBApp/RNFBAppModuleFirebase.swift +439 -0
  140. package/ios/RNFBApp/RNFBAppModuleImplementation.h +68 -0
  141. package/ios/RNFBApp/RNFBAppModuleImplementation.m +195 -0
  142. package/ios/RNFBApp/RNFBHandleMap.h +73 -0
  143. package/ios/RNFBApp/RNFBHandleMap.m +87 -0
  144. package/ios/RNFBApp/RNFBHandleMapStorage.swift +90 -0
  145. package/ios/RNFBApp/RNFBInitializeAppArguments.swift +82 -0
  146. package/ios/RNFBApp/RNFBJSON.m +22 -38
  147. package/ios/RNFBApp/RNFBJSONImplementation.swift +71 -0
  148. package/ios/RNFBApp/RNFBMeta.m +24 -26
  149. package/ios/RNFBApp/RNFBMetaStorage.swift +76 -0
  150. package/ios/RNFBApp/RNFBNullSentinelDecoding.swift +135 -0
  151. package/ios/RNFBApp/RNFBNullSentinelInterceptor.h +30 -3
  152. package/ios/RNFBApp/RNFBNullSentinelInterceptor.m +28 -5
  153. package/ios/RNFBApp/RNFBPreferences.m +22 -20
  154. package/ios/RNFBApp/RNFBPreferencesStorage.swift +101 -0
  155. package/ios/RNFBApp/RNFBRCTEventEmitter.m +36 -93
  156. package/ios/RNFBApp/RNFBRCTEventEmitterCore.swift +161 -0
  157. package/ios/RNFBApp/RNFBSharedUtils.h +2 -5
  158. package/ios/RNFBApp/RNFBSharedUtils.m +109 -197
  159. package/ios/RNFBApp/RNFBSharedUtilsConfig.swift +72 -0
  160. package/ios/RNFBApp/RNFBSharedUtilsFIRApp.swift +271 -0
  161. package/ios/RNFBApp/RNFBSharedUtilsFormatting.swift +59 -0
  162. package/ios/RNFBApp/RNFBSharedUtilsPromiseRejection.swift +88 -0
  163. package/ios/RNFBApp/RNFBUtilsHelpers.swift +116 -0
  164. package/ios/RNFBApp/RNFBUtilsModule+Foundation.m +44 -0
  165. package/ios/RNFBApp/RNFBUtilsModule+PhotoAssets.m +47 -0
  166. package/ios/RNFBApp/RNFBUtilsModule.h +11 -1
  167. package/ios/RNFBApp/RNFBUtilsModule.mm +10 -85
  168. package/ios/RNFBApp/RNFBUtilsModuleImplementation.h +32 -0
  169. package/ios/RNFBApp/RNFBUtilsModuleImplementation.m +56 -0
  170. package/ios/RNFBApp/RNFBVersion.m +1 -1
  171. package/ios/RNFBApp.xcodeproj/project.pbxproj +106 -0
  172. package/ios/RNFBAppUnitTests/HostStubs/FirebaseCore/FIRApp.m +171 -0
  173. package/ios/RNFBAppUnitTests/HostStubs/FirebaseCore/FirebaseCore.h +78 -0
  174. package/ios/RNFBAppUnitTests/HostStubs/FirebaseInstallations/FIRInstallations.m +7 -0
  175. package/ios/RNFBAppUnitTests/HostStubs/FirebaseInstallations/FirebaseInstallations.h +8 -0
  176. package/ios/RNFBAppUnitTests/HostStubs/RNFBAppModuleHostStub.m +55 -0
  177. package/ios/RNFBAppUnitTests/HostStubs/RNFBAppTurboModules.h +89 -0
  178. package/ios/RNFBAppUnitTests/HostStubs/RNFBAppTurboModules.mm +30 -0
  179. package/ios/RNFBAppUnitTests/HostStubs/React/RCTBridge.h +19 -0
  180. package/ios/RNFBAppUnitTests/HostStubs/React/RCTBridge.m +32 -0
  181. package/ios/RNFBAppUnitTests/HostStubs/React/RCTBridgeModule.h +19 -0
  182. package/ios/RNFBAppUnitTests/HostStubs/React/RCTConvert.h +28 -0
  183. package/ios/RNFBAppUnitTests/HostStubs/React/RCTConvert.m +23 -0
  184. package/ios/RNFBAppUnitTests/HostStubs/React/RCTEventEmitter.h +9 -0
  185. package/ios/RNFBAppUnitTests/HostStubs/React/RCTInvalidating.h +24 -0
  186. package/ios/RNFBAppUnitTests/HostStubs/React/RCTUtils.h +9 -0
  187. package/ios/RNFBAppUnitTests/HostStubs/module.modulemap +13 -0
  188. package/ios/RNFBAppUnitTests/RCTConvertFIRAppFacadeTests.m +61 -0
  189. package/ios/RNFBAppUnitTests/RCTConvertFIRAppTests.swift +73 -0
  190. package/ios/RNFBAppUnitTests/RCTConvertFIROptionsFacadeTests.m +57 -0
  191. package/ios/RNFBAppUnitTests/RCTConvertFIROptionsTests.swift +243 -0
  192. package/ios/RNFBAppUnitTests/RNFBAppCustomAuthDomainsFacadeTests.m +73 -0
  193. package/ios/RNFBAppUnitTests/RNFBAppCustomAuthDomainsTests.swift +70 -0
  194. package/ios/RNFBAppUnitTests/RNFBAppInitializeOptionsMapperTests.swift +190 -0
  195. package/ios/RNFBAppUnitTests/RNFBAppLogLevelFacadeTests.m +76 -0
  196. package/ios/RNFBAppUnitTests/RNFBAppLogLevelMapperTests.swift +59 -0
  197. package/ios/RNFBAppUnitTests/RNFBAppModuleFirebaseFacadeTests.m +215 -0
  198. package/ios/RNFBAppUnitTests/RNFBAppModuleFirebaseTests.swift +430 -0
  199. package/ios/RNFBAppUnitTests/RNFBAppModuleLifecycleTests.m +206 -0
  200. package/ios/RNFBAppUnitTests/RNFBAppModuleShellTests.mm +367 -0
  201. package/ios/RNFBAppUnitTests/RNFBAppUnitTests.xcodeproj/project.pbxproj +590 -0
  202. package/ios/RNFBAppUnitTests/RNFBAppUnitTests.xcodeproj/xcshareddata/xcschemes/RNFBAppUnitTests.xcscheme +69 -0
  203. package/ios/RNFBAppUnitTests/RNFBFirebaseAppClientTests.swift +165 -0
  204. package/ios/RNFBAppUnitTests/RNFBHandleMapStorageTests.swift +121 -0
  205. package/ios/RNFBAppUnitTests/RNFBHandleMapTests.m +238 -0
  206. package/ios/RNFBAppUnitTests/RNFBInitializeAppArgumentsTests.swift +256 -0
  207. package/ios/RNFBAppUnitTests/RNFBJSONImplementationTests.swift +126 -0
  208. package/ios/RNFBAppUnitTests/RNFBJSONTests.m +172 -0
  209. package/ios/RNFBAppUnitTests/RNFBMetaStorageTests.swift +106 -0
  210. package/ios/RNFBAppUnitTests/RNFBMetaTests.m +65 -0
  211. package/ios/RNFBAppUnitTests/RNFBNullSentinelDecodingTests.swift +286 -0
  212. package/ios/RNFBAppUnitTests/RNFBNullSentinelInterceptorTests.m +312 -0
  213. package/ios/RNFBAppUnitTests/RNFBPreferencesStorageTests.swift +105 -0
  214. package/ios/RNFBAppUnitTests/RNFBPreferencesTests.m +110 -0
  215. package/ios/RNFBAppUnitTests/RNFBRCTEventEmitterCoreTests.swift +163 -0
  216. package/ios/RNFBAppUnitTests/RNFBRCTEventEmitterFacadeTests.m +141 -0
  217. package/ios/RNFBAppUnitTests/RNFBSharedUtilsConfigFacadeTests.m +100 -0
  218. package/ios/RNFBAppUnitTests/RNFBSharedUtilsConfigTests.swift +234 -0
  219. package/ios/RNFBAppUnitTests/RNFBSharedUtilsFIRAppFacadeTests.m +145 -0
  220. package/ios/RNFBAppUnitTests/RNFBSharedUtilsFIRAppTests.swift +173 -0
  221. package/ios/RNFBAppUnitTests/RNFBSharedUtilsFormattingFacadeTests.m +38 -0
  222. package/ios/RNFBAppUnitTests/RNFBSharedUtilsFormattingTests.swift +82 -0
  223. package/ios/RNFBAppUnitTests/RNFBSharedUtilsNullSentinelTests.m +35 -0
  224. package/ios/RNFBAppUnitTests/RNFBSharedUtilsPromiseRejectionFacadeTests.m +107 -0
  225. package/ios/RNFBAppUnitTests/RNFBSharedUtilsPromiseRejectionTests.swift +133 -0
  226. package/ios/RNFBAppUnitTests/RNFBUtilsHelpersTests.swift +144 -0
  227. package/ios/RNFBAppUnitTests/RNFBUtilsModuleFacadeTests.m +72 -0
  228. package/ios/RNFBAppUnitTests/RNFBUtilsModuleShellTests.mm +282 -0
  229. package/ios/RNFBFirebase/Package.swift +53 -0
  230. package/ios/RNFBFirebase/Sources/RNFBFirebase/RNFBFirebase.swift +285 -0
  231. package/ios/RNFBFirebase/Sources/RNFBFirebaseBridge/Public/.gitkeep +0 -0
  232. package/ios/RNFBFirebase/Sources/RNFBFirebaseBridge/RNFBFirebaseBridge.m +169 -0
  233. package/ios/RNFBFirebase/Sources/RNFBFirebaseBridge/include/RNFBFirebaseBridge.h +52 -0
  234. package/ios/RNFBFirebaseUnitTests/RNFBAppDynamicFacadeTests.swift +240 -0
  235. package/{android/src/reactnative/java/io/invertase/firebase/app/ReactNativeFirebaseAppRegistrar.java → ios/RNFBFirebaseUnitTests/RNFBFirebaseClientImport.swift} +22 -17
  236. package/ios/RNFBFirebaseUnitTests/RNFBFirebaseUnitTests.xcodeproj/project.pbxproj +588 -0
  237. package/ios/RNFBFirebaseUnitTests/RNFBFirebaseUnitTests.xcodeproj/xcshareddata/xcschemes/RNFBFirebaseUnitTests.xcscheme +83 -0
  238. package/ios_config.sh +50 -22
  239. package/lib/FirebaseApp.ts +10 -0
  240. package/lib/common/Base64.ts +10 -16
  241. package/lib/common/deeps.ts +3 -0
  242. package/lib/common/index.ts +20 -9
  243. package/lib/internal/FirebaseModule.ts +6 -3
  244. package/lib/internal/NativeModules.ts +4 -1
  245. package/lib/internal/RNFBNativeEventEmitter.ts +14 -5
  246. package/lib/internal/SharedEventEmitter.ts +5 -1
  247. package/lib/internal/asyncStorage.ts +7 -11
  248. package/lib/internal/index.ts +1 -0
  249. package/lib/internal/logger.ts +53 -43
  250. package/lib/internal/nativeModuleAndroidIos.ts +2 -40
  251. package/lib/internal/nativeModuleDebug.ts +97 -0
  252. package/lib/internal/nativeModuleWeb.ts +10 -40
  253. package/lib/internal/react-native-vendor-emitter.d.ts +11 -0
  254. package/lib/internal/registry/app.ts +8 -2
  255. package/lib/internal/registry/modular.ts +2 -2
  256. package/lib/internal/registry/nativeModule.ts +3 -3
  257. package/lib/internal/web/RNFBAppModule.ts +16 -15
  258. package/lib/internal/web/memidb/lib/FakeEventTarget.js +3 -1
  259. package/lib/types/app.ts +10 -3
  260. package/lib/types/internal.ts +19 -0
  261. package/lib/version.ts +1 -1
  262. package/package.json +9 -8
  263. package/plugin/build/ios/appDelegate.js +19 -9
  264. package/plugin/build/ios/podfile.js +16 -16
  265. package/plugin/build/pluginConfig.d.ts +4 -2
  266. package/plugin/src/ios/appDelegate.ts +45 -15
  267. package/plugin/src/ios/podfile.ts +20 -23
  268. package/plugin/src/pluginConfig.ts +4 -2
  269. package/android/src/main/java/io/invertase/firebase/common/TaskExecutorService.java +0 -124
  270. package/android/src/main/java/io/invertase/firebase/common/UniversalFirebaseModule.java +0 -62
  271. package/android/src/main/java/io/invertase/firebase/common/UniversalFirebasePreferences.java +0 -91
  272. package/android/src/reactnative/java/io/invertase/firebase/app/NativeRNFBTurboApp.java +0 -222
  273. package/android/src/reactnative/java/io/invertase/firebase/app/ReactNativeFirebaseApp.java +0 -46
  274. package/android/src/reactnative/java/io/invertase/firebase/app/ReactNativeFirebaseAppPackage.java +0 -49
  275. package/android/src/reactnative/java/io/invertase/firebase/common/RCTConvertFirebase.java +0 -191
  276. package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseEvent.java +0 -53
  277. package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseEventEmitter.java +0 -606
  278. package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseJSON.java +0 -107
  279. package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseMeta.java +0 -101
  280. package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseModule.java +0 -146
  281. package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebasePreferences.java +0 -96
  282. package/android/src/reactnative/java/io/invertase/firebase/common/SharedUtils.java +0 -441
  283. package/android/src/reactnative/java/io/invertase/firebase/utils/NativeRNFBTurboUtils.java +0 -223
@@ -0,0 +1,662 @@
1
+ package io.invertase.firebase.common
2
+
3
+ /*
4
+ * Copyright (c) 2016-present Invertase Limited & Contributors
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this library except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ *
18
+ */
19
+
20
+ import android.content.Context
21
+ import android.os.Handler
22
+ import android.os.Looper
23
+ import android.util.Log
24
+ import androidx.annotation.MainThread
25
+ import androidx.annotation.Nullable
26
+ import com.facebook.react.ReactApplication
27
+ import com.facebook.react.ReactHost
28
+ import com.facebook.react.ReactNativeHost
29
+ import com.facebook.react.bridge.Arguments
30
+ import com.facebook.react.bridge.ReactContext
31
+ import com.facebook.react.bridge.WritableMap
32
+ import com.facebook.react.modules.core.DeviceEventManagerModule
33
+ import io.invertase.firebase.app.ReactNativeFirebaseApp
34
+ import io.invertase.firebase.interfaces.NativeEvent
35
+ import java.lang.ref.WeakReference
36
+ import java.util.ArrayList
37
+ import java.util.HashMap
38
+
39
+ open class ReactNativeFirebaseEventEmitter {
40
+ private val handler = Handler(Looper.getMainLooper())
41
+
42
+ // All mutable state below is guarded by the jsListeners monitor. Attach/detach/ready
43
+ // transitions happen synchronously at cause time under the same monitor as listener
44
+ // registration: with more than one ReactContext generation alive during an instance
45
+ // reload, deferring any of these transitions (e.g. to a congested main looper) reorders
46
+ // them against a replacement runtime's synchronous registrations and can wipe or wedge
47
+ // that runtime's state. Only queue flushing is posted to the main thread.
48
+ private val queuedEvents: MutableList<NativeEvent> = ArrayList()
49
+ private val jsListeners: HashMap<String?, Int> = HashMap()
50
+
51
+ // Weak so the emitter never pins a dead ReactContext (and its module graph) for the
52
+ // lifetime of the process if a runtime is torn down without a paired detach.
53
+ private var attachedReactContext: WeakReference<ReactContext?> = WeakReference(null)
54
+
55
+ // When the host still names a dying generation, a live module's attach is remembered here
56
+ // and promoted once that dying context detaches or the host catches up — without waiting
57
+ // solely on a later Activity resume that may never arrive.
58
+ private var pendingReactContext: WeakReference<ReactContext?> = WeakReference(null)
59
+
60
+ // Host identity we moved past while converging onto pending/attached; late attaches of this
61
+ // identity must not displace the live generation while the host ref lags.
62
+ private var hostLagReactContext: WeakReference<ReactContext?> = WeakReference(null)
63
+
64
+ // When a new pending candidate enters while the attached generation still lives, listener
65
+ // accounting is reset so events queue until the pending JS re-registers. Keep a snapshot of
66
+ // the attached generation's accounting so a pending-only cancel can restore it — otherwise
67
+ // the surviving attached runtime stays deaf for the rest of the session (#8374-class).
68
+ @Nullable private var attachedListenersSnapshot: HashMap<String?, Int>? = null
69
+ private var attachedJsReadySnapshot = false
70
+ private var attachedJsListenerCountSnapshot = 0
71
+
72
+ // After pending-only cancel restores that snapshot, emit must keep targeting the restored
73
+ // attached generation even if the host has already advanced to an unrelated unattached
74
+ // context — otherwise resolveEmitContext prefers raw hostCurrent and silently loses events
75
+ // into a runtime with no RNFB JS subscribers (R1 class after cancel). Cleared when a new
76
+ // generation attaches through acceptAttachedContext / convergence / full detach.
77
+ private var emitPrefersRestoredAttached = false
78
+ private var jsReady = false
79
+ private var jsListenerCount = 0
80
+
81
+ open fun attachReactContext(reactContext: ReactContext?) {
82
+ // Resolve host current outside jsListeners so ReactApplication getters are not held
83
+ // under the listener monitor (they are RN-owned and not expected to re-enter, but the
84
+ // coupling is unnecessary for attach arbitration).
85
+ val hostCurrent = getCurrentReactContextFromHost(reactContext)
86
+
87
+ synchronized(jsListeners) {
88
+ // Under the new architecture more than one ReactContext generation can exist while an
89
+ // instance is destroyed and recreated: a module belonging to the dying generation may
90
+ // run initialize() after the replacement generation has already attached (for example
91
+ // TurboModuleManager creates requested-but-never-built modules during invalidate).
92
+ // Attach is fail-closed when something is already attached: accept the candidate only
93
+ // when the host currently names it. A null host current keeps the previous attachment
94
+ // rather than accepting a late stale context. When the host still names the previous
95
+ // (dying) context, remember the candidate as pending so detach/host catch-up can
96
+ // converge without depending only on a later Activity resume.
97
+ val previousContext = attachedReactContext.get()
98
+ if (previousContext != null && previousContext !== reactContext) {
99
+ if (hostCurrent == null) {
100
+ Log.w(
101
+ TAG,
102
+ "Ignoring attach of ReactContext@" +
103
+ System.identityHashCode(reactContext) +
104
+ " while host current is null; keeping @" +
105
+ System.identityHashCode(previousContext),
106
+ )
107
+ return
108
+ }
109
+
110
+ val hostLag = hostLagReactContext.get()
111
+ if (hostCurrent === reactContext) {
112
+ if (hostLag != null && hostLag === reactContext) {
113
+ Log.w(
114
+ TAG,
115
+ "Ignoring attach of host-lag ReactContext@" +
116
+ System.identityHashCode(reactContext) +
117
+ "; keeping live @" +
118
+ System.identityHashCode(previousContext),
119
+ )
120
+ return
121
+ }
122
+ acceptAttachedContext(
123
+ reactContext,
124
+ resetListenerState = pendingReactContext.get() !== reactContext,
125
+ )
126
+ clearConvergenceState()
127
+ discardAttachedListenerSnapshot()
128
+ } else if (hostCurrent === previousContext) {
129
+ // Host still names the dying attached generation. Remember the candidate as pending
130
+ // so detach/host catch-up can converge. Idempotent re-attach of the same pending
131
+ // identity (e.g. async onHostResume while the host still lags) must not wipe
132
+ // listeners the pending JS already registered. Reset only when entering pending for
133
+ // a new candidate: first pending, or last-writer-wins replace of a different one.
134
+ val existingPending = pendingReactContext.get()
135
+ val newPendingCandidate = existingPending !== reactContext
136
+ Log.i(
137
+ TAG,
138
+ "Host still on @" +
139
+ System.identityHashCode(previousContext) +
140
+ "; pending attach of @" +
141
+ System.identityHashCode(reactContext) +
142
+ (if (newPendingCandidate) "" else " (idempotent)"),
143
+ )
144
+ pendingReactContext = WeakReference(reactContext)
145
+ hostLagReactContext = WeakReference(previousContext)
146
+ if (newPendingCandidate) {
147
+ // Drop previous-generation listener accounting; replacement JS will re-register.
148
+ // Snapshot once per pending window so pending-only cancel can restore the
149
+ // surviving attached generation (last-writer-wins replace keeps the first
150
+ // snapshot — that is still the attached runtime's pre-pending state).
151
+ snapshotAttachedListenerStateIfNeeded()
152
+ resetListenerState()
153
+ }
154
+ handler.post { tryConvergePendingReactContext() }
155
+ return
156
+ } else {
157
+ Log.w(
158
+ TAG,
159
+ "Ignoring attach of non-current ReactContext@" +
160
+ System.identityHashCode(reactContext) +
161
+ ", current is @" +
162
+ System.identityHashCode(hostCurrent),
163
+ )
164
+ return
165
+ }
166
+ } else if (previousContext !== reactContext) {
167
+ acceptAttachedContext(reactContext, resetListenerState = false)
168
+ clearConvergenceState()
169
+ } else {
170
+ // Same context re-attach (e.g. resume). Clear lag only when this identity is the
171
+ // live pending/attached generation the host has caught up to — not when we are
172
+ // still the dying hostLag while a different pending replacement is waiting.
173
+ if (hostCurrent === reactContext) {
174
+ val pending = pendingReactContext.get()
175
+ if (pending == null || pending === reactContext) {
176
+ clearConvergenceState()
177
+ }
178
+ }
179
+ }
180
+ }
181
+
182
+ handler.post { sendQueuedEvents() }
183
+ }
184
+
185
+ open fun detachReactContext(reactContext: ReactContext?) {
186
+ synchronized(jsListeners) {
187
+ // Pending-only invalidate (failed replacement startup / overlapping teardown): the
188
+ // candidate never became attached, so cancel it before the attached-identity check.
189
+ // Restore the surviving attached generation's listener / jsReady snapshot taken when
190
+ // pending first entered — pending-window registrations belong to the cancelled
191
+ // generation and must not leave the attached runtime deaf. Superseded identities that
192
+ // are not the current pending fall through and no-op below — they must not wipe a
193
+ // newer live pending's registrations.
194
+ val pending = pendingReactContext.get()
195
+ if (pending === reactContext && attachedReactContext.get() !== reactContext) {
196
+ Log.i(
197
+ TAG,
198
+ "Cancelled pending ReactContext@" +
199
+ System.identityHashCode(reactContext) +
200
+ "; attached remains @" +
201
+ System.identityHashCode(attachedReactContext.get()),
202
+ )
203
+ clearConvergenceState()
204
+ restoreAttachedListenerSnapshot()
205
+ // Events queued while the pending window wiped listeners must drain now — attached
206
+ // JS already registered before pending entered and will not re-addListener.
207
+ handler.post { sendQueuedEvents() }
208
+ return
209
+ }
210
+
211
+ // Only clear state installed by the runtime that is going away; a dying generation
212
+ // must not wipe listener registrations a replacement runtime has already made. A
213
+ // replacement runtime always attaches (synchronously, at module creation) before its
214
+ // JS can register listeners, so an identity match here means the dying state is ours.
215
+ if (attachedReactContext.get() !== reactContext) {
216
+ return
217
+ }
218
+
219
+ if (pending != null && pending !== reactContext) {
220
+ Log.i(
221
+ TAG,
222
+ "Detached ReactContext@" +
223
+ System.identityHashCode(reactContext) +
224
+ "; promoting pending @" +
225
+ System.identityHashCode(pending),
226
+ )
227
+ attachedReactContext = WeakReference(pending)
228
+ pendingReactContext = WeakReference(null)
229
+ // Keep jsReady / jsListeners: they belong to the promoted generation's JS.
230
+ // hostLag stays set so emit and late attaches do not regress to the dying identity
231
+ // while the host ref still names it.
232
+ discardAttachedListenerSnapshot()
233
+ emitPrefersRestoredAttached = false
234
+ handler.post { sendQueuedEvents() }
235
+ return
236
+ }
237
+
238
+ Log.i(TAG, "Detached ReactContext@" + System.identityHashCode(reactContext))
239
+ attachedReactContext = WeakReference(null)
240
+ resetListenerState()
241
+ clearConvergenceState()
242
+ discardAttachedListenerSnapshot()
243
+ emitPrefersRestoredAttached = false
244
+ // queuedEvents are intentionally kept: they drain once the next runtime attaches and
245
+ // registers listeners, which is how events raised while no runtime is alive (for
246
+ // example messaging events for a killed app) reach JS.
247
+ }
248
+ }
249
+
250
+ open fun notifyJsReady(ready: Boolean?) {
251
+ synchronized(jsListeners) {
252
+ // Java assigns Boolean into primitive boolean — unbox at this assignment site.
253
+ jsReady = nullableAsPlatformType(ready)
254
+ }
255
+
256
+ handler.post { sendQueuedEvents() }
257
+ }
258
+
259
+ open fun sendEvent(event: NativeEvent?) {
260
+ handler.post {
261
+ val nativeEvent: NativeEvent
262
+ synchronized(jsListeners) {
263
+ nativeEvent = nullableAsPlatformType(event)
264
+ // First event dereference stays under the listener monitor, exactly as in Java.
265
+ if (!jsListeners.containsKey(nativeEvent.getEventName())) {
266
+ queuedEvents.add(nativeEvent)
267
+ return@post
268
+ }
269
+ }
270
+ // emit() resolves ReactApplication host current outside jsListeners; do not hold
271
+ // the monitor across that call.
272
+ if (!emit(nativeEvent)) {
273
+ synchronized(jsListeners) {
274
+ queuedEvents.add(nativeEvent)
275
+ }
276
+ }
277
+ }
278
+ }
279
+
280
+ open fun addListener(eventName: String?) {
281
+ synchronized(jsListeners) {
282
+ jsListenerCount++
283
+ if (!jsListeners.containsKey(eventName)) {
284
+ jsListeners.put(eventName, 1)
285
+ } else {
286
+ val listenersForEvent: Int = nullableAsPlatformType(jsListeners[eventName])
287
+ jsListeners.put(eventName, listenersForEvent + 1)
288
+ }
289
+ }
290
+
291
+ handler.post { sendQueuedEvents() }
292
+ }
293
+
294
+ open fun removeListener(
295
+ eventName: String?,
296
+ all: Boolean?,
297
+ ) {
298
+ synchronized(jsListeners) {
299
+ if (jsListeners.containsKey(eventName)) {
300
+ val listenersForEvent: Int = nullableAsPlatformType(jsListeners[eventName])
301
+
302
+ // Preserve Java short-circuit: unbox `all` only when count > 1.
303
+ if (listenersForEvent <= 1 || nullableAsPlatformType(all)) {
304
+ jsListeners.remove(eventName)
305
+ } else {
306
+ jsListeners.put(eventName, listenersForEvent - 1)
307
+ }
308
+
309
+ jsListenerCount -= if (nullableAsPlatformType(all)) listenersForEvent else 1
310
+ }
311
+ }
312
+ }
313
+
314
+ open val listenersMap: WritableMap
315
+ get() {
316
+ val writableMap = Arguments.createMap()
317
+ val events = Arguments.createMap()
318
+
319
+ // Resolve host current outside jsListeners; converge/snapshot under the monitor.
320
+ val pendingHint: ReactContext?
321
+ val attachedHint: ReactContext?
322
+ synchronized(jsListeners) {
323
+ pendingHint = pendingReactContext.get()
324
+ attachedHint = attachedReactContext.get()
325
+ }
326
+ val hostForConverge =
327
+ getCurrentReactContextFromHost(if (pendingHint != null) pendingHint else attachedHint)
328
+
329
+ val attachedContext: ReactContext?
330
+ synchronized(jsListeners) {
331
+ tryConvergePendingReactContextLocked(hostForConverge)
332
+ attachedContext = attachedReactContext.get()
333
+
334
+ writableMap.putInt("listeners", jsListenerCount)
335
+ writableMap.putInt("queued", queuedEvents.size)
336
+ writableMap.putBoolean("jsReady", jsReady)
337
+ writableMap.putInt(
338
+ "attachedContextHash",
339
+ if (attachedContext == null) 0 else System.identityHashCode(attachedContext),
340
+ )
341
+
342
+ for (entry in jsListeners.entries) {
343
+ events.putInt(
344
+ nullableAsPlatformType(entry.key),
345
+ nullableAsPlatformType(entry.value),
346
+ )
347
+ }
348
+ }
349
+
350
+ val currentContext = getCurrentReactContextFromHost(attachedContext)
351
+ writableMap.putInt(
352
+ "currentContextHash",
353
+ if (currentContext == null) 0 else System.identityHashCode(currentContext),
354
+ )
355
+ writableMap.putMap("events", events)
356
+
357
+ return writableMap
358
+ }
359
+
360
+ @MainThread
361
+ private fun sendQueuedEvents() {
362
+ val toSend: MutableList<NativeEvent>
363
+ synchronized(jsListeners) {
364
+ toSend = ArrayList()
365
+ for (event in ArrayList(queuedEvents)) {
366
+ if (jsListeners.containsKey(event.getEventName())) {
367
+ queuedEvents.remove(event)
368
+ toSend.add(event)
369
+ }
370
+ }
371
+ }
372
+ for (event in toSend) {
373
+ sendEvent(event)
374
+ }
375
+ }
376
+
377
+ @MainThread
378
+ private fun emit(event: NativeEvent): Boolean {
379
+ // Snapshot readiness / pointers under the monitor, resolve host current outside (avoid
380
+ // holding jsListeners across ReactApplication getters), then re-enter to converge/resolve.
381
+ var attachedHint: ReactContext?
382
+ val ready: Boolean
383
+ synchronized(jsListeners) {
384
+ ready = jsReady
385
+ if (!ready) {
386
+ return false
387
+ }
388
+ attachedHint = attachedReactContext.get()
389
+ if (attachedHint == null) {
390
+ attachedHint = pendingReactContext.get()
391
+ }
392
+ }
393
+ val hostCurrent = getCurrentReactContextFromHost(attachedHint)
394
+
395
+ val emitContext: ReactContext?
396
+ synchronized(jsListeners) {
397
+ if (!jsReady) {
398
+ return false
399
+ }
400
+ tryConvergePendingReactContextLocked(hostCurrent)
401
+
402
+ // Resolve the context hosting the live JS runtime at emit time rather than trusting the
403
+ // attached one alone: a context captured at module-creation time can belong to a previous
404
+ // generation whose runtime no longer contains the JS listeners, and on bridgeless a stale
405
+ // context still reports an active instance so it cannot be validated - only replaced
406
+ // (#8374, #8900). When attached has already converged ahead of a lagging host ref, prefer
407
+ // attached over the dying host current so events are not delivered into a runtime with no
408
+ // listeners.
409
+ val attached = attachedReactContext.get()
410
+ emitContext = resolveEmitContext(attached, hostCurrent)
411
+ }
412
+
413
+ if (emitContext == null) {
414
+ return false
415
+ }
416
+ if (!emitContext.hasActiveReactInstance()) {
417
+ return false
418
+ }
419
+
420
+ try {
421
+ emitContext
422
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
423
+ .emit("rnfb_" + event.getEventName(), event.getEventBody())
424
+ } catch (e: Exception) {
425
+ Log.wtf(TAG, "Error sending Event " + event.getEventName(), e)
426
+ return false
427
+ }
428
+
429
+ return true
430
+ }
431
+
432
+ @Nullable
433
+ private fun resolveEmitContext(
434
+ @Nullable attached: ReactContext?,
435
+ @Nullable hostCurrent: ReactContext?,
436
+ ): ReactContext? {
437
+ val pending = pendingReactContext.get()
438
+ val hostLag = hostLagReactContext.get()
439
+
440
+ // A pending replacement owns (or will own) the listener map. Prefer it over host/attached
441
+ // for the whole pending window — including when the host has already advanced to an
442
+ // unrelated third generation that has not yet attached via this emitter. Emitting into
443
+ // that third context would return true with no RNFB JS subscribers and no re-queue.
444
+ if (pending != null) {
445
+ return pending
446
+ }
447
+
448
+ if (hostCurrent == null) {
449
+ return attached
450
+ }
451
+ if (attached == null || attached === hostCurrent) {
452
+ if (attached === hostCurrent) {
453
+ clearConvergenceState()
454
+ emitPrefersRestoredAttached = false
455
+ }
456
+ return hostCurrent
457
+ }
458
+
459
+ if (hostLag != null && hostLag === hostCurrent) {
460
+ return attached
461
+ }
462
+
463
+ // Pending-only cancel restored listeners onto attached; an unrelated hostCurrent that has
464
+ // not yet attached through this emitter must not steal delivery (silent loss, no re-queue).
465
+ // Flag is only set while an attached generation still owns the restored map.
466
+ if (emitPrefersRestoredAttached) {
467
+ return attached
468
+ }
469
+
470
+ // Attached is stale relative to a host that has moved on — prefer the host.
471
+ return hostCurrent
472
+ }
473
+
474
+ private fun tryConvergePendingReactContext() {
475
+ val pendingHint: ReactContext?
476
+ synchronized(jsListeners) {
477
+ pendingHint = pendingReactContext.get()
478
+ }
479
+ if (pendingHint == null) {
480
+ return
481
+ }
482
+ val hostCurrent = getCurrentReactContextFromHost(pendingHint)
483
+ synchronized(jsListeners) {
484
+ if (tryConvergePendingReactContextLocked(hostCurrent)) {
485
+ handler.post { sendQueuedEvents() }
486
+ }
487
+ }
488
+ }
489
+
490
+ /**
491
+ * Prefer resolving `hostCurrent` outside `jsListeners` and passing it in so ReactApplication
492
+ * getters are not held under the listener monitor.
493
+ *
494
+ * @return true when attachment changed
495
+ */
496
+ private fun tryConvergePendingReactContextLocked(
497
+ @Nullable hostCurrent: ReactContext?,
498
+ ): Boolean {
499
+ val pending = pendingReactContext.get()
500
+ if (pending == null) {
501
+ return false
502
+ }
503
+ if (hostCurrent !== pending) {
504
+ return false
505
+ }
506
+ val previous = attachedReactContext.get()
507
+ if (previous === pending) {
508
+ clearConvergenceState()
509
+ return false
510
+ }
511
+ Log.i(
512
+ TAG,
513
+ "Host caught up; attaching pending ReactContext@" + System.identityHashCode(pending),
514
+ )
515
+ acceptAttachedContext(pending, resetListenerState = false)
516
+ clearConvergenceState()
517
+ discardAttachedListenerSnapshot()
518
+ return true
519
+ }
520
+
521
+ private fun acceptAttachedContext(
522
+ reactContext: ReactContext?,
523
+ resetListenerState: Boolean,
524
+ ) {
525
+ val previousContext = attachedReactContext.get()
526
+ if (previousContext !== reactContext) {
527
+ Log.i(
528
+ TAG,
529
+ "ReactContext changed: @" +
530
+ System.identityHashCode(previousContext) +
531
+ " -> @" +
532
+ System.identityHashCode(reactContext),
533
+ )
534
+ attachedReactContext = WeakReference(reactContext)
535
+ // A real attach of a new generation supersedes restored-attached emit affinity.
536
+ emitPrefersRestoredAttached = false
537
+ if (resetListenerState) {
538
+ discardAttachedListenerSnapshot()
539
+ resetListenerState()
540
+ }
541
+ }
542
+ }
543
+
544
+ private fun resetListenerState() {
545
+ jsReady = false
546
+ jsListeners.clear()
547
+ jsListenerCount = 0
548
+ }
549
+
550
+ private fun snapshotAttachedListenerStateIfNeeded() {
551
+ if (attachedListenersSnapshot != null) {
552
+ return
553
+ }
554
+ attachedListenersSnapshot = HashMap(jsListeners)
555
+ attachedJsReadySnapshot = jsReady
556
+ attachedJsListenerCountSnapshot = jsListenerCount
557
+ }
558
+
559
+ private fun restoreAttachedListenerSnapshot() {
560
+ val snapshot = attachedListenersSnapshot
561
+ if (snapshot == null) {
562
+ resetListenerState()
563
+ emitPrefersRestoredAttached = false
564
+ return
565
+ }
566
+ jsListeners.clear()
567
+ jsListeners.putAll(snapshot)
568
+ jsReady = attachedJsReadySnapshot
569
+ jsListenerCount = attachedJsListenerCountSnapshot
570
+ discardAttachedListenerSnapshot()
571
+ emitPrefersRestoredAttached = true
572
+ }
573
+
574
+ private fun discardAttachedListenerSnapshot() {
575
+ attachedListenersSnapshot = null
576
+ attachedJsReadySnapshot = false
577
+ attachedJsListenerCountSnapshot = 0
578
+ }
579
+
580
+ private fun clearConvergenceState() {
581
+ pendingReactContext = WeakReference(null)
582
+ hostLagReactContext = WeakReference(null)
583
+ }
584
+
585
+ @Suppress("UNCHECKED_CAST")
586
+ private fun <T> nullableAsPlatformType(value: T?): T = value as T
587
+
588
+ companion object {
589
+ private const val TAG = "RNFB_EMITTER"
590
+
591
+ // Kotlin emits this private companion property as a private static outer field.
592
+ // @JvmStatic keeps getSharedInstance on the outer class with the original descriptor.
593
+ private var sharedInstance: ReactNativeFirebaseEventEmitter = ReactNativeFirebaseEventEmitter()
594
+
595
+ @JvmStatic
596
+ fun getSharedInstance(): ReactNativeFirebaseEventEmitter = sharedInstance
597
+
598
+ @JvmStatic
599
+ @Nullable
600
+ private fun getCurrentReactContextFromHost(
601
+ @Nullable contextHint: ReactContext?,
602
+ ): ReactContext? {
603
+ var applicationContext: Context? =
604
+ if (contextHint != null) {
605
+ contextHint.applicationContext
606
+ } else {
607
+ ReactNativeFirebaseApp.getApplicationContext()
608
+ }
609
+ if (applicationContext != null) {
610
+ // Normalize in case a non-application context (for example a ContentProvider context)
611
+ // was stored as the fallback.
612
+ applicationContext = applicationContext.applicationContext
613
+ }
614
+ if (applicationContext !is ReactApplication) {
615
+ // Brownfield hosts that do not implement ReactApplication fall back to the attached
616
+ // context, preserving pre-existing behaviour.
617
+ return null
618
+ }
619
+
620
+ val reactApplication = applicationContext as ReactApplication
621
+
622
+ // Note: these getters may lazily construct their host on first call; that is acceptable
623
+ // here because a live react-native-firebase module implies the app's host already exists.
624
+ var fromReactHost: ReactContext? = null
625
+ try {
626
+ val reactHost: ReactHost? = reactApplication.reactHost
627
+ if (reactHost != null) {
628
+ fromReactHost = reactHost.currentReactContext
629
+ if (fromReactHost != null) {
630
+ return fromReactHost
631
+ }
632
+ // Hybrid / transitional hosts may expose a non-null ReactHost whose current context
633
+ // is briefly null while the bridge ReactNativeHost still carries the live context.
634
+ // Fall through rather than treating null as a permanent answer.
635
+ }
636
+ } catch (e: RuntimeException) {
637
+ // App-authored hosts may throw, and react-native 0.83+ throws from the deprecated
638
+ // reactNativeHost getter on bridgeless-only apps.
639
+ Log.d(TAG, "Failed to resolve current ReactContext via ReactHost", e)
640
+ } catch (e: LinkageError) {
641
+ Log.d(TAG, "Failed to resolve current ReactContext via ReactHost", e)
642
+ }
643
+
644
+ try {
645
+ val reactNativeHost: ReactNativeHost? = reactApplication.reactNativeHost
646
+ // hasInstance() guards against getReactInstanceManager() eagerly creating an instance
647
+ // manager purely as a side effect of emitting an event.
648
+ if (reactNativeHost != null && reactNativeHost.hasInstance()) {
649
+ return reactNativeHost.reactInstanceManager.currentReactContext
650
+ }
651
+ } catch (e: RuntimeException) {
652
+ // Bridgeless-only apps throw from getReactNativeHost(); if ReactHost was present but
653
+ // returned null current, that null stands — still log so the failure is visible.
654
+ Log.d(TAG, "Failed to resolve current ReactContext via ReactNativeHost", e)
655
+ } catch (e: LinkageError) {
656
+ Log.d(TAG, "Failed to resolve current ReactContext via ReactNativeHost", e)
657
+ }
658
+
659
+ return fromReactHost
660
+ }
661
+ }
662
+ }