@react-native-firebase/app 26.4.0 → 27.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -0
- package/README.md +10 -27
- package/RNFBApp.podspec +13 -5
- package/android/build.gradle +3 -0
- package/android/src/main/java/io/invertase/firebase/common/RNFBHandleCollisionException.kt +29 -0
- package/android/src/main/java/io/invertase/firebase/common/RNFBHandleMap.kt +146 -0
- package/android/src/main/java/io/invertase/firebase/common/TaskExecutorService.kt +156 -0
- package/android/src/main/java/io/invertase/firebase/common/UniversalFirebaseModule.kt +44 -0
- package/android/src/main/java/io/invertase/firebase/common/UniversalFirebasePreferences.kt +100 -0
- package/android/src/reactnative/java/io/invertase/firebase/app/NativeRNFBTurboApp.kt +250 -0
- package/android/src/reactnative/java/io/invertase/firebase/app/ReactNativeFirebaseApp.kt +64 -0
- package/android/src/reactnative/java/io/invertase/firebase/app/{ReactNativeFirebaseAppInitProvider.java → ReactNativeFirebaseAppInitProvider.kt} +3 -4
- package/android/src/reactnative/java/io/invertase/firebase/app/ReactNativeFirebaseAppPackage.kt +47 -0
- package/android/src/reactnative/java/io/invertase/firebase/app/ReactNativeFirebaseAppRegistrar.kt +33 -0
- package/android/src/reactnative/java/io/invertase/firebase/app/ReactNativeFirebaseVersion.java +1 -1
- package/android/src/reactnative/java/io/invertase/firebase/common/RCTConvertFirebase.kt +185 -0
- package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseEvent.kt +35 -0
- package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseEventEmitter.kt +662 -0
- package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseJSON.kt +103 -0
- package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseMeta.kt +96 -0
- package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseModule.kt +140 -0
- package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebasePreferences.kt +109 -0
- package/android/src/reactnative/java/io/invertase/firebase/common/SharedUtils.kt +451 -0
- package/android/src/reactnative/java/io/invertase/firebase/interfaces/{ContextProvider.java → ContextProvider.kt} +8 -8
- package/android/src/reactnative/java/io/invertase/firebase/interfaces/{NativeError.java → NativeError.kt} +8 -8
- package/android/src/reactnative/java/io/invertase/firebase/interfaces/{NativeEvent.java → NativeEvent.kt} +6 -6
- package/android/src/reactnative/java/io/invertase/firebase/utils/NativeRNFBTurboUtils.kt +219 -0
- package/android/src/test/java/expo/core/ModuleRegistry.java +9 -0
- package/android/src/test/java/io/invertase/firebase/app/NativeRNFBTurboAppTest.java +182 -59
- package/android/src/test/java/io/invertase/firebase/app/ReactNativeFirebaseAppComponentsJavaCompatibilityTest.java +70 -0
- package/android/src/test/java/io/invertase/firebase/app/ReactNativeFirebaseAppJavaCompatibilityTest.java +60 -0
- package/android/src/test/java/io/invertase/firebase/app/ReactNativeFirebaseAppPackageTest.java +165 -0
- package/android/src/test/java/io/invertase/firebase/app/ReactNativeFirebaseAppTest.kt +167 -0
- package/android/src/test/java/io/invertase/firebase/common/RCTConvertFirebaseJavaCompatibilityTest.java +112 -0
- package/android/src/test/java/io/invertase/firebase/common/RCTConvertFirebaseTest.kt +317 -0
- package/android/src/test/java/io/invertase/firebase/common/RNFBHandleMapTest.kt +309 -0
- package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebaseEventEmitterJavaCompatibilityTest.java +176 -0
- package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebaseEventEmitterTest.java +143 -12
- package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebaseEventTest.kt +61 -0
- package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebaseJSONJavaCompatibilityTest.java +85 -0
- package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebaseJSONTest.kt +296 -0
- package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebaseMetaJavaCompatibilityTest.java +67 -0
- package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebaseMetaTest.kt +246 -0
- package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebaseModuleTest.kt +254 -0
- package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebasePreferencesJavaCompatibilityTest.java +87 -0
- package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebasePreferencesTest.kt +260 -0
- package/android/src/test/java/io/invertase/firebase/common/SharedUtilsJavaCompatibilityTest.java +255 -0
- package/android/src/test/java/io/invertase/firebase/common/SharedUtilsTest.kt +674 -0
- package/android/src/test/java/io/invertase/firebase/common/TaskExecutorServiceTest.kt +290 -0
- package/android/src/test/java/io/invertase/firebase/common/UniversalFirebaseModuleJavaCompatibilityTest.java +178 -0
- package/android/src/test/java/io/invertase/firebase/common/UniversalFirebasePreferencesJavaCompatibilityTest.java +99 -0
- package/android/src/test/java/io/invertase/firebase/common/UniversalFirebasePreferencesTest.kt +217 -0
- package/android/src/test/java/io/invertase/firebase/commoncompat/ReactNativeFirebaseModuleJavaCompatibilityTest.java +176 -0
- package/android/src/test/java/io/invertase/firebase/commoncompat/ReactNativeFirebaseModuleKotlinCompatibilityTest.kt +52 -0
- package/android/src/test/java/io/invertase/firebase/interfaces/InterfacesJavaCompatibilityTest.java +144 -0
- package/android/src/test/java/io/invertase/firebase/interfaces/NativeEventTest.kt +56 -0
- package/android/src/test/java/io/invertase/firebase/utils/NativeRNFBTurboUtilsTest.java +517 -21
- package/dist/module/FirebaseApp.js +10 -0
- package/dist/module/FirebaseApp.js.map +1 -1
- package/dist/module/common/deeps.js +3 -0
- package/dist/module/common/deeps.js.map +1 -1
- package/dist/module/internal/FirebaseModule.js.map +1 -1
- package/dist/module/internal/RNFBNativeEventEmitter.js +12 -1
- package/dist/module/internal/RNFBNativeEventEmitter.js.map +1 -1
- package/dist/module/internal/SharedEventEmitter.js +3 -1
- package/dist/module/internal/SharedEventEmitter.js.map +1 -1
- package/dist/module/internal/index.js +1 -0
- package/dist/module/internal/index.js.map +1 -1
- package/dist/module/internal/registry/app.js +7 -1
- package/dist/module/internal/registry/app.js.map +1 -1
- package/dist/module/types/app.js.map +1 -1
- package/dist/module/types/internal.js +2 -0
- package/dist/module/version.js +1 -1
- package/dist/typescript/lib/FirebaseApp.d.ts +7 -0
- package/dist/typescript/lib/FirebaseApp.d.ts.map +1 -1
- package/dist/typescript/lib/common/deeps.d.ts.map +1 -1
- package/dist/typescript/lib/internal/FirebaseModule.d.ts +2 -3
- package/dist/typescript/lib/internal/FirebaseModule.d.ts.map +1 -1
- package/dist/typescript/lib/internal/NativeModules.d.ts +4 -1
- package/dist/typescript/lib/internal/NativeModules.d.ts.map +1 -1
- package/dist/typescript/lib/internal/RNFBNativeEventEmitter.d.ts +2 -2
- package/dist/typescript/lib/internal/RNFBNativeEventEmitter.d.ts.map +1 -1
- package/dist/typescript/lib/internal/SharedEventEmitter.d.ts +3 -3
- package/dist/typescript/lib/internal/SharedEventEmitter.d.ts.map +1 -1
- package/dist/typescript/lib/internal/index.d.ts +1 -0
- package/dist/typescript/lib/internal/index.d.ts.map +1 -1
- package/dist/typescript/lib/internal/registry/app.d.ts.map +1 -1
- package/dist/typescript/lib/types/app.d.ts +10 -3
- package/dist/typescript/lib/types/app.d.ts.map +1 -1
- package/dist/typescript/lib/types/internal.d.ts +12 -0
- package/dist/typescript/lib/types/internal.d.ts.map +1 -1
- package/dist/typescript/lib/version.d.ts +1 -1
- package/firebase-schema.json +4 -0
- package/firebase_spm.rb +552 -96
- package/ios/RNFBApp/RCTConvert+FIRApp.h +2 -5
- package/ios/RNFBApp/RCTConvert+FIRApp.m +11 -6
- package/ios/RNFBApp/RCTConvert+FIROptions.h +2 -5
- package/ios/RNFBApp/RCTConvert+FIROptions.m +11 -10
- package/ios/RNFBApp/RCTConvertFIRApp.swift +86 -0
- package/ios/RNFBApp/RCTConvertFIROptions.swift +259 -0
- package/ios/RNFBApp/RNFBAppCustomAuthDomains.swift +63 -0
- package/ios/RNFBApp/RNFBAppInitializeOptionsMapper.swift +113 -0
- package/ios/RNFBApp/RNFBAppLogLevelMapper.swift +53 -0
- package/ios/RNFBApp/RNFBAppModule+CustomAuthDomain.m +35 -0
- package/ios/RNFBApp/RNFBAppModule.h +6 -2
- package/ios/RNFBApp/RNFBAppModule.mm +65 -228
- package/ios/RNFBApp/RNFBAppModuleFirebase.swift +439 -0
- package/ios/RNFBApp/RNFBAppModuleImplementation.h +68 -0
- package/ios/RNFBApp/RNFBAppModuleImplementation.m +195 -0
- package/ios/RNFBApp/RNFBHandleMap.m +27 -56
- package/ios/RNFBApp/RNFBHandleMapStorage.swift +90 -0
- package/ios/RNFBApp/RNFBInitializeAppArguments.swift +82 -0
- package/ios/RNFBApp/RNFBJSON.m +22 -38
- package/ios/RNFBApp/RNFBJSONImplementation.swift +71 -0
- package/ios/RNFBApp/RNFBMeta.m +24 -26
- package/ios/RNFBApp/RNFBMetaStorage.swift +76 -0
- package/ios/RNFBApp/RNFBNullSentinelDecoding.swift +135 -0
- package/ios/RNFBApp/RNFBNullSentinelInterceptor.h +30 -3
- package/ios/RNFBApp/RNFBNullSentinelInterceptor.m +28 -5
- package/ios/RNFBApp/RNFBPreferences.m +22 -20
- package/ios/RNFBApp/RNFBPreferencesStorage.swift +101 -0
- package/ios/RNFBApp/RNFBRCTEventEmitter.m +36 -93
- package/ios/RNFBApp/RNFBRCTEventEmitterCore.swift +161 -0
- package/ios/RNFBApp/RNFBSharedUtils.h +2 -5
- package/ios/RNFBApp/RNFBSharedUtils.m +109 -198
- package/ios/RNFBApp/RNFBSharedUtilsConfig.swift +72 -0
- package/ios/RNFBApp/RNFBSharedUtilsFIRApp.swift +271 -0
- package/ios/RNFBApp/RNFBSharedUtilsFormatting.swift +59 -0
- package/ios/RNFBApp/RNFBSharedUtilsPromiseRejection.swift +88 -0
- package/ios/RNFBApp/RNFBUtilsHelpers.swift +116 -0
- package/ios/RNFBApp/RNFBUtilsModule+Foundation.m +44 -0
- package/ios/RNFBApp/RNFBUtilsModule+PhotoAssets.m +47 -0
- package/ios/RNFBApp/RNFBUtilsModule.h +11 -1
- package/ios/RNFBApp/RNFBUtilsModule.mm +10 -85
- package/ios/RNFBApp/RNFBUtilsModuleImplementation.h +32 -0
- package/ios/RNFBApp/RNFBUtilsModuleImplementation.m +56 -0
- package/ios/RNFBApp/RNFBVersion.m +1 -1
- package/ios/RNFBApp.xcodeproj/project.pbxproj +100 -0
- package/ios/RNFBAppUnitTests/HostStubs/FirebaseCore/FIRApp.m +171 -0
- package/ios/RNFBAppUnitTests/HostStubs/FirebaseCore/FirebaseCore.h +78 -0
- package/ios/RNFBAppUnitTests/HostStubs/FirebaseInstallations/FIRInstallations.m +7 -0
- package/ios/RNFBAppUnitTests/HostStubs/FirebaseInstallations/FirebaseInstallations.h +8 -0
- package/ios/RNFBAppUnitTests/HostStubs/RNFBAppModuleHostStub.m +55 -0
- package/ios/RNFBAppUnitTests/HostStubs/RNFBAppTurboModules.h +89 -0
- package/ios/RNFBAppUnitTests/HostStubs/RNFBAppTurboModules.mm +30 -0
- package/ios/RNFBAppUnitTests/HostStubs/React/RCTBridge.h +19 -0
- package/ios/RNFBAppUnitTests/HostStubs/React/RCTBridge.m +32 -0
- package/ios/RNFBAppUnitTests/HostStubs/React/RCTBridgeModule.h +19 -0
- package/ios/RNFBAppUnitTests/HostStubs/React/RCTConvert.h +28 -0
- package/ios/RNFBAppUnitTests/HostStubs/React/RCTConvert.m +23 -0
- package/ios/RNFBAppUnitTests/HostStubs/React/RCTEventEmitter.h +9 -0
- package/ios/RNFBAppUnitTests/HostStubs/React/RCTInvalidating.h +24 -0
- package/ios/RNFBAppUnitTests/HostStubs/React/RCTUtils.h +9 -0
- package/ios/RNFBAppUnitTests/HostStubs/module.modulemap +13 -0
- package/ios/RNFBAppUnitTests/RCTConvertFIRAppFacadeTests.m +61 -0
- package/ios/RNFBAppUnitTests/RCTConvertFIRAppTests.swift +73 -0
- package/ios/RNFBAppUnitTests/RCTConvertFIROptionsFacadeTests.m +57 -0
- package/ios/RNFBAppUnitTests/RCTConvertFIROptionsTests.swift +243 -0
- package/ios/RNFBAppUnitTests/RNFBAppCustomAuthDomainsFacadeTests.m +73 -0
- package/ios/RNFBAppUnitTests/RNFBAppCustomAuthDomainsTests.swift +70 -0
- package/ios/RNFBAppUnitTests/RNFBAppInitializeOptionsMapperTests.swift +190 -0
- package/ios/RNFBAppUnitTests/RNFBAppLogLevelFacadeTests.m +76 -0
- package/ios/RNFBAppUnitTests/RNFBAppLogLevelMapperTests.swift +59 -0
- package/ios/RNFBAppUnitTests/RNFBAppModuleFirebaseFacadeTests.m +215 -0
- package/ios/RNFBAppUnitTests/RNFBAppModuleFirebaseTests.swift +430 -0
- package/ios/RNFBAppUnitTests/RNFBAppModuleLifecycleTests.m +206 -0
- package/ios/RNFBAppUnitTests/RNFBAppModuleShellTests.mm +367 -0
- package/ios/RNFBAppUnitTests/RNFBAppUnitTests.xcodeproj/project.pbxproj +355 -2
- package/ios/RNFBAppUnitTests/RNFBFirebaseAppClientTests.swift +165 -0
- package/ios/RNFBAppUnitTests/RNFBHandleMapStorageTests.swift +121 -0
- package/ios/RNFBAppUnitTests/RNFBInitializeAppArgumentsTests.swift +256 -0
- package/ios/RNFBAppUnitTests/RNFBJSONImplementationTests.swift +126 -0
- package/ios/RNFBAppUnitTests/RNFBJSONTests.m +172 -0
- package/ios/RNFBAppUnitTests/RNFBMetaStorageTests.swift +106 -0
- package/ios/RNFBAppUnitTests/RNFBMetaTests.m +65 -0
- package/ios/RNFBAppUnitTests/RNFBNullSentinelDecodingTests.swift +286 -0
- package/ios/RNFBAppUnitTests/RNFBNullSentinelInterceptorTests.m +312 -0
- package/ios/RNFBAppUnitTests/RNFBPreferencesStorageTests.swift +105 -0
- package/ios/RNFBAppUnitTests/RNFBPreferencesTests.m +110 -0
- package/ios/RNFBAppUnitTests/RNFBRCTEventEmitterCoreTests.swift +163 -0
- package/ios/RNFBAppUnitTests/RNFBRCTEventEmitterFacadeTests.m +141 -0
- package/ios/RNFBAppUnitTests/RNFBSharedUtilsConfigFacadeTests.m +100 -0
- package/ios/RNFBAppUnitTests/RNFBSharedUtilsConfigTests.swift +234 -0
- package/ios/RNFBAppUnitTests/RNFBSharedUtilsFIRAppFacadeTests.m +145 -0
- package/ios/RNFBAppUnitTests/RNFBSharedUtilsFIRAppTests.swift +173 -0
- package/ios/RNFBAppUnitTests/RNFBSharedUtilsFormattingFacadeTests.m +38 -0
- package/ios/RNFBAppUnitTests/RNFBSharedUtilsFormattingTests.swift +82 -0
- package/ios/RNFBAppUnitTests/RNFBSharedUtilsNullSentinelTests.m +35 -0
- package/ios/RNFBAppUnitTests/RNFBSharedUtilsPromiseRejectionFacadeTests.m +107 -0
- package/ios/RNFBAppUnitTests/RNFBSharedUtilsPromiseRejectionTests.swift +133 -0
- package/ios/RNFBAppUnitTests/RNFBUtilsHelpersTests.swift +144 -0
- package/ios/RNFBAppUnitTests/RNFBUtilsModuleFacadeTests.m +72 -0
- package/ios/RNFBAppUnitTests/RNFBUtilsModuleShellTests.mm +282 -0
- package/ios/RNFBFirebase/Package.swift +53 -0
- package/ios/RNFBFirebase/Sources/RNFBFirebase/RNFBFirebase.swift +285 -0
- package/ios/RNFBFirebase/Sources/RNFBFirebaseBridge/Public/.gitkeep +0 -0
- package/ios/RNFBFirebase/Sources/RNFBFirebaseBridge/RNFBFirebaseBridge.m +169 -0
- package/ios/RNFBFirebase/Sources/RNFBFirebaseBridge/include/RNFBFirebaseBridge.h +52 -0
- package/ios/RNFBFirebaseUnitTests/RNFBAppDynamicFacadeTests.swift +240 -0
- package/{android/src/main/java/io/invertase/firebase/common/RNFBHandleCollisionException.java → ios/RNFBFirebaseUnitTests/RNFBFirebaseClientImport.swift} +23 -14
- package/ios/RNFBFirebaseUnitTests/RNFBFirebaseUnitTests.xcodeproj/project.pbxproj +588 -0
- package/ios/RNFBFirebaseUnitTests/RNFBFirebaseUnitTests.xcodeproj/xcshareddata/xcschemes/RNFBFirebaseUnitTests.xcscheme +83 -0
- package/ios_config.sh +50 -22
- package/lib/FirebaseApp.ts +10 -0
- package/lib/common/deeps.ts +3 -0
- package/lib/internal/FirebaseModule.ts +6 -3
- package/lib/internal/NativeModules.ts +4 -1
- package/lib/internal/RNFBNativeEventEmitter.ts +14 -5
- package/lib/internal/SharedEventEmitter.ts +5 -1
- package/lib/internal/index.ts +1 -0
- package/lib/internal/react-native-vendor-emitter.d.ts +11 -0
- package/lib/internal/registry/app.ts +7 -1
- package/lib/types/app.ts +10 -3
- package/lib/types/internal.ts +19 -0
- package/lib/version.ts +1 -1
- package/package.json +8 -8
- package/plugin/build/ios/appDelegate.js +2 -2
- package/plugin/build/ios/podfile.js +16 -16
- package/plugin/build/pluginConfig.d.ts +4 -2
- package/plugin/src/ios/appDelegate.ts +2 -2
- package/plugin/src/ios/podfile.ts +20 -23
- package/plugin/src/pluginConfig.ts +4 -2
- package/android/src/main/java/io/invertase/firebase/common/RNFBHandleMap.java +0 -150
- package/android/src/main/java/io/invertase/firebase/common/TaskExecutorService.java +0 -148
- package/android/src/main/java/io/invertase/firebase/common/UniversalFirebaseModule.java +0 -62
- package/android/src/main/java/io/invertase/firebase/common/UniversalFirebasePreferences.java +0 -91
- package/android/src/reactnative/java/io/invertase/firebase/app/NativeRNFBTurboApp.java +0 -224
- package/android/src/reactnative/java/io/invertase/firebase/app/ReactNativeFirebaseApp.java +0 -46
- package/android/src/reactnative/java/io/invertase/firebase/app/ReactNativeFirebaseAppPackage.java +0 -49
- package/android/src/reactnative/java/io/invertase/firebase/app/ReactNativeFirebaseAppRegistrar.java +0 -35
- package/android/src/reactnative/java/io/invertase/firebase/common/RCTConvertFirebase.java +0 -192
- package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseEvent.java +0 -53
- package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseEventEmitter.java +0 -606
- package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseJSON.java +0 -109
- package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseMeta.java +0 -101
- package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseModule.java +0 -146
- package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebasePreferences.java +0 -96
- package/android/src/reactnative/java/io/invertase/firebase/common/SharedUtils.java +0 -441
- package/android/src/reactnative/java/io/invertase/firebase/utils/NativeRNFBTurboUtils.java +0 -223
- package/android/src/test/java/io/invertase/firebase/common/RNFBHandleMapTest.java +0 -275
- package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebaseJSONTest.java +0 -108
- package/android/src/test/java/io/invertase/firebase/common/TaskExecutorServiceTest.java +0 -97
package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseEventEmitter.kt
ADDED
|
@@ -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
|
+
}
|