@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,65 @@
1
+ /**
2
+ * Copyright (c) 2016-present Invertase Limited & Contributors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this library except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ #import <XCTest/XCTest.h>
18
+
19
+ #import "RNFBHandleMapStorage-Swift.inc"
20
+ #import "RNFBMeta.h"
21
+
22
+ @interface RNFBMetaTests : XCTestCase
23
+ @end
24
+
25
+ @implementation RNFBMetaTests
26
+
27
+ - (void)testPublicClassSelectorsAndReturnEncodingsAreUnchanged {
28
+ NSArray<NSString *> *selectorNames = @[
29
+ @"contains:",
30
+ @"getBooleanValue:defaultValue:",
31
+ @"getStringValue:defaultValue:",
32
+ @"getAll",
33
+ ];
34
+ for (NSString *selectorName in selectorNames) {
35
+ XCTAssertTrue([RNFBMeta respondsToSelector:NSSelectorFromString(selectorName)]);
36
+ }
37
+
38
+ NSMethodSignature *contains = [RNFBMeta methodSignatureForSelector:@selector(contains:)];
39
+ XCTAssertEqual(strcmp(contains.methodReturnType, @encode(BOOL)), 0);
40
+
41
+ NSMethodSignature *boolean = [RNFBMeta methodSignatureForSelector:@selector(getBooleanValue:
42
+ defaultValue:)];
43
+ XCTAssertEqual(strcmp(boolean.methodReturnType, @encode(BOOL)), 0);
44
+ XCTAssertEqual(strcmp([boolean getArgumentTypeAtIndex:3], @encode(BOOL)), 0);
45
+
46
+ NSMethodSignature *string = [RNFBMeta methodSignatureForSelector:@selector(getStringValue:
47
+ defaultValue:)];
48
+ XCTAssertEqual(strcmp(string.methodReturnType, @encode(id)), 0);
49
+
50
+ NSMethodSignature *getAll = [RNFBMeta methodSignatureForSelector:@selector(getAll)];
51
+ XCTAssertEqual(strcmp(getAll.methodReturnType, @encode(id)), 0);
52
+ }
53
+
54
+ - (void)testFacadeForwardsToSharedMainBundleStorage {
55
+ // Smoke: class methods are callable and getAll returns a dictionary (may be empty).
56
+ XCTAssertNotNil([RNFBMeta getAll]);
57
+ XCTAssertTrue([[RNFBMeta getAll] isKindOfClass:[NSDictionary class]]);
58
+ XCTAssertFalse([RNFBMeta contains:@"__rnfb_meta_unit_test_missing_key__"]);
59
+ XCTAssertTrue([RNFBMeta getBooleanValue:@"__rnfb_meta_unit_test_missing_key__" defaultValue:YES]);
60
+ XCTAssertEqualObjects([RNFBMeta getStringValue:@"__rnfb_meta_unit_test_missing_key__"
61
+ defaultValue:@"fallback"],
62
+ @"fallback");
63
+ }
64
+
65
+ @end
@@ -0,0 +1,286 @@
1
+ /**
2
+ * Copyright (c) 2016-present Invertase Limited & Contributors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this library except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import Foundation
18
+ import XCTest
19
+
20
+ final class RNFBNullSentinelDecodingTests: XCTestCase {
21
+ private func decode(_ value: Any?) -> Any? {
22
+ RNFBNullSentinelDecoder.decodeNullSentinels(value)
23
+ }
24
+
25
+ private func sentinel(_ flag: Any = true) -> NSDictionary {
26
+ ["__rnfbNull": flag] as NSDictionary
27
+ }
28
+
29
+ private func serverTimestamp() -> NSDictionary {
30
+ [".sv": "timestamp"] as NSDictionary
31
+ }
32
+
33
+ func testNilAndNonContainersPreserveIdentity() {
34
+ XCTAssertNil(decode(nil))
35
+
36
+ let number = NSNumber(value: 42)
37
+ let string = "hello" as NSString
38
+ let date = NSDate()
39
+ let data = NSData()
40
+ let null = NSNull()
41
+
42
+ XCTAssertTrue(decode(number) as AnyObject === number)
43
+ XCTAssertTrue(decode(string) as AnyObject === string)
44
+ XCTAssertTrue(decode(date) as AnyObject === date)
45
+ XCTAssertTrue(decode(data) as AnyObject === data)
46
+ XCTAssertTrue(decode(null) as AnyObject === null)
47
+ }
48
+
49
+ func testDecodeAndDecodeNullSentinelsSelectorsAreEquivalent() {
50
+ let input: NSDictionary = ["a": sentinel(), "b": "keep"]
51
+ let viaCanonical = RNFBNullSentinelDecoder.decodeNullSentinels(input) as! NSDictionary
52
+ let viaAlias = RNFBNullSentinelDecoder.decode(input) as! NSDictionary
53
+ XCTAssertEqual(viaCanonical, viaAlias)
54
+ XCTAssertTrue(viaCanonical["a"] is NSNull)
55
+ XCTAssertEqual(viaCanonical["b"] as? String, "keep")
56
+ }
57
+
58
+ func testRootSentinelBecomesNSNull() {
59
+ XCTAssertTrue(decode(sentinel()) is NSNull)
60
+ XCTAssertTrue(decode(sentinel(NSNumber(value: 1))) is NSNull)
61
+ }
62
+
63
+ func testRootSentinelRequiresExactlyOneKeyAndTruthyFlag() {
64
+ XCTAssertFalse(decode(sentinel(false)) is NSNull)
65
+ XCTAssertEqual(decode(sentinel(false)) as? NSDictionary, ["__rnfbNull": false] as NSDictionary)
66
+
67
+ let extraKey = ["__rnfbNull": true, "other": 1] as NSDictionary
68
+ XCTAssertEqual(decode(extraKey) as? NSDictionary, extraKey)
69
+
70
+ let empty = decode(NSDictionary()) as! NSDictionary
71
+ XCTAssertEqual(empty.count, 0)
72
+ XCTAssertTrue(empty is NSMutableDictionary)
73
+ }
74
+
75
+ func testStringFlagIsNotASentinel() {
76
+ for flag in ["true", "YES", "yes", "1"] as [NSString] {
77
+ let input = sentinel(flag)
78
+ XCTAssertEqual(decode(input) as? NSDictionary, input)
79
+ }
80
+ }
81
+
82
+ func testNonBoolRespondingFlagIsNotASentinel() {
83
+ let exotic = sentinel(NSDate())
84
+ let decoded = decode(exotic) as! NSDictionary
85
+ XCTAssertEqual(decoded.count, 1)
86
+ XCTAssertTrue(decoded["__rnfbNull"] is NSDate)
87
+ }
88
+
89
+ func testRootSentinelFlagNonNumberUnchangedWithoutCrash() {
90
+ let nullFlag = ["__rnfbNull": NSNull()] as NSDictionary
91
+ let dictFlag = ["__rnfbNull": ["a": 1] as NSDictionary] as NSDictionary
92
+ let arrayFlag = ["__rnfbNull": [1] as NSArray] as NSDictionary
93
+
94
+ XCTAssertEqual(decode(nullFlag) as? NSDictionary, nullFlag)
95
+ XCTAssertEqual(decode(dictFlag) as? NSDictionary, dictFlag)
96
+ XCTAssertEqual(decode(arrayFlag) as? NSDictionary, arrayFlag)
97
+ }
98
+
99
+ func testRootServerValueUnchanged() {
100
+ let input = serverTimestamp()
101
+ XCTAssertEqual(decode(input) as? NSDictionary, input)
102
+ }
103
+
104
+ func testNestedDictionarySentinelsBecomeNSNull() {
105
+ let input: NSDictionary = [
106
+ "a": sentinel(),
107
+ "b": "keep",
108
+ "nested": [
109
+ "inner": sentinel(),
110
+ "n": 1,
111
+ "falseFlag": sentinel(false),
112
+ "empty": NSDictionary(),
113
+ ] as NSDictionary,
114
+ ]
115
+
116
+ let result = decode(input) as! NSDictionary
117
+ XCTAssertTrue(result["a"] is NSNull)
118
+ XCTAssertEqual(result["b"] as? String, "keep")
119
+ let nested = result["nested"] as! NSDictionary
120
+ XCTAssertTrue(nested["inner"] is NSNull)
121
+ XCTAssertEqual(nested["n"] as? Int, 1)
122
+ XCTAssertEqual(nested["falseFlag"] as? NSDictionary, ["__rnfbNull": false] as NSDictionary)
123
+ XCTAssertEqual((nested["empty"] as? NSDictionary)?.count, 0)
124
+ }
125
+
126
+ func testNestedSentinelWithExtraKeysUnchanged() {
127
+ let input: NSDictionary = ["child": ["__rnfbNull": true, "other": "x"] as NSDictionary]
128
+ XCTAssertEqual(decode(input) as? NSDictionary, input)
129
+ }
130
+
131
+ func testNestedSentinelFlagNonNumberUnchanged() {
132
+ let input: NSDictionary = [
133
+ "nullFlag": ["__rnfbNull": NSNull()] as NSDictionary,
134
+ "dictFlag": ["__rnfbNull": ["a": 1] as NSDictionary] as NSDictionary,
135
+ "arrayFlag": ["__rnfbNull": [1] as NSArray] as NSDictionary,
136
+ "stringFlag": ["__rnfbNull": "true"] as NSDictionary,
137
+ ]
138
+ XCTAssertEqual(decode(input) as? NSDictionary, input)
139
+
140
+ let arrayInput: NSArray = [
141
+ ["__rnfbNull": NSNull()] as NSDictionary,
142
+ ["__rnfbNull": ["a": 1] as NSDictionary] as NSDictionary,
143
+ ["__rnfbNull": [1] as NSArray] as NSDictionary,
144
+ ["__rnfbNull": "true"] as NSDictionary,
145
+ ]
146
+ XCTAssertEqual(decode(arrayInput) as? NSArray, arrayInput)
147
+ }
148
+
149
+ func testValidSentinelSiblingDecodedWhileNonNumberFlagPassesThrough() {
150
+ let input: NSDictionary = [
151
+ "valid": sentinel(),
152
+ "guarded": ["__rnfbNull": NSNull()] as NSDictionary,
153
+ "list": [sentinel(), ["__rnfbNull": [1] as NSArray] as NSDictionary] as NSArray,
154
+ ]
155
+ let result = decode(input) as! NSDictionary
156
+ XCTAssertTrue(result["valid"] is NSNull)
157
+ XCTAssertEqual(result["guarded"] as? NSDictionary, ["__rnfbNull": NSNull()] as NSDictionary)
158
+ let list = result["list"] as! NSArray
159
+ XCTAssertTrue(list[0] is NSNull)
160
+ XCTAssertEqual(list[1] as? NSDictionary, ["__rnfbNull": [1] as NSArray] as NSDictionary)
161
+ }
162
+
163
+ func testArrayWalkPreservesNSNullAndReplacesSentinels() {
164
+ let input: NSArray = [
165
+ sentinel(),
166
+ NSNull(),
167
+ "x",
168
+ 3,
169
+ serverTimestamp(),
170
+ [sentinel(), 2] as NSArray,
171
+ ]
172
+
173
+ let result = decode(input) as! NSArray
174
+ XCTAssertEqual(result.count, 6)
175
+ XCTAssertTrue(result[0] is NSNull)
176
+ XCTAssertTrue(result[1] is NSNull)
177
+ XCTAssertEqual(result[2] as? String, "x")
178
+ XCTAssertEqual(result[3] as? Int, 3)
179
+ XCTAssertEqual(result[4] as? NSDictionary, serverTimestamp())
180
+ let nested = result[5] as! NSArray
181
+ XCTAssertTrue(nested[0] is NSNull)
182
+ XCTAssertEqual(nested[1] as? Int, 2)
183
+ XCTAssertTrue(result is NSMutableArray)
184
+ }
185
+
186
+ func testRootEmptyArrayReturnsEmptyMutableArray() {
187
+ let decoded = decode([] as NSArray) as! NSArray
188
+ XCTAssertEqual(decoded.count, 0)
189
+ XCTAssertTrue(decoded is NSMutableArray)
190
+ }
191
+
192
+ func testNestedArraysSentinelsDecodedAtEveryLevel() {
193
+ let input: NSArray = [[sentinel(), [sentinel(), "leaf"] as NSArray] as NSArray, [] as NSArray]
194
+ let result = decode(input) as! NSArray
195
+ let first = result[0] as! NSArray
196
+ XCTAssertTrue(first[0] is NSNull)
197
+ XCTAssertTrue((first[1] as! NSArray)[0] is NSNull)
198
+ XCTAssertEqual((first[1] as! NSArray)[1] as? String, "leaf")
199
+ XCTAssertEqual((result[1] as! NSArray).count, 0)
200
+ }
201
+
202
+ func testArrayInsideDictionaryAndMixedMatrixDecoded() {
203
+ let input: NSDictionary = [
204
+ "list": [sentinel(), "x", [sentinel()] as NSArray] as NSArray,
205
+ "items": [
206
+ ["id": 1, "note": sentinel()] as NSDictionary,
207
+ ["id": 2, "tags": [sentinel()] as NSArray] as NSDictionary,
208
+ ] as NSArray,
209
+ ]
210
+ let result = decode(input) as! NSDictionary
211
+ let list = result["list"] as! NSArray
212
+ XCTAssertTrue(list[0] is NSNull)
213
+ XCTAssertEqual(list[1] as? String, "x")
214
+ XCTAssertTrue((list[2] as! NSArray)[0] is NSNull)
215
+
216
+ let items = result["items"] as! NSArray
217
+ XCTAssertTrue((items[0] as! NSDictionary)["note"] is NSNull)
218
+ XCTAssertTrue(((items[1] as! NSDictionary)["tags"] as! NSArray)[0] is NSNull)
219
+ }
220
+
221
+ func testChildAndParentTypeMatrix() {
222
+ let dictInput: NSDictionary = [
223
+ "dictChild": ["k": 1] as NSDictionary,
224
+ "arrayChild": ["leaf", [2] as NSArray, sentinel(), NSNull()] as NSArray,
225
+ "primitiveChild": "text",
226
+ "nullChild": NSNull(),
227
+ ]
228
+ let arrayRoot: NSArray = [
229
+ ["inner": sentinel()] as NSDictionary,
230
+ ["nested"] as NSArray,
231
+ 3,
232
+ sentinel(),
233
+ NSNull(),
234
+ ]
235
+
236
+ let decodedDict = decode(dictInput) as! NSDictionary
237
+ XCTAssertEqual(decodedDict["dictChild"] as? NSDictionary, ["k": 1] as NSDictionary)
238
+ let arrayChild = decodedDict["arrayChild"] as! NSArray
239
+ XCTAssertEqual(arrayChild[0] as? String, "leaf")
240
+ XCTAssertTrue(arrayChild[2] is NSNull)
241
+ XCTAssertTrue(decodedDict["nullChild"] is NSNull)
242
+
243
+ let decodedArray = decode(arrayRoot) as! NSArray
244
+ XCTAssertTrue((decodedArray[0] as! NSDictionary)["inner"] is NSNull)
245
+ XCTAssertTrue(decodedArray[3] is NSNull)
246
+ XCTAssertTrue(decodedArray[4] is NSNull)
247
+ }
248
+
249
+ func testContainersReturnNewMutableCopiesEvenWithoutSentinels() {
250
+ let dict = ["k": "v"] as NSDictionary
251
+ let array = ["a", 1] as NSArray
252
+
253
+ let decodedDict = decode(dict)
254
+ let decodedArray = decode(array)
255
+
256
+ XCTAssertFalse(decodedDict as AnyObject === dict)
257
+ XCTAssertFalse(decodedArray as AnyObject === array)
258
+ XCTAssertTrue(decodedDict is NSMutableDictionary)
259
+ XCTAssertTrue(decodedArray is NSMutableArray)
260
+ XCTAssertEqual(decodedDict as? NSDictionary, dict)
261
+ XCTAssertEqual(decodedArray as? NSArray, array)
262
+ }
263
+
264
+ func testCountOneNumberFalseIsNotSentinel() {
265
+ let input: NSDictionary = [
266
+ "a": ["__rnfbNull": false] as NSDictionary,
267
+ "b": ["__rnfbNull": NSNumber(value: 0)] as NSDictionary,
268
+ ]
269
+ XCTAssertEqual(decode(input) as? NSDictionary, input)
270
+ }
271
+
272
+ func testDeepNestingDoesNotOverflow() {
273
+ let depth = 1000
274
+ var leaf: Any = ["value": sentinel()] as NSDictionary
275
+ for _ in 0..<depth {
276
+ leaf = ["child": leaf] as NSDictionary
277
+ }
278
+
279
+ var cursor = decode(leaf) as! NSDictionary
280
+ for _ in 0..<depth {
281
+ XCTAssertTrue(cursor is NSMutableDictionary)
282
+ cursor = cursor["child"] as! NSDictionary
283
+ }
284
+ XCTAssertTrue(cursor["value"] is NSNull)
285
+ }
286
+ }
@@ -0,0 +1,312 @@
1
+ /**
2
+ * Copyright (c) 2016-present Invertase Limited & Contributors
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this library except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ *
16
+ */
17
+
18
+ #import <XCTest/XCTest.h>
19
+ #import <objc/runtime.h>
20
+
21
+ #import "RNFBNullSentinelInterceptor.h"
22
+
23
+ /** Set by the original (pre-swizzle) IMPs of every runtime-created stand-in class. */
24
+ static id gLastJsonSeenByOriginalIMP = nil;
25
+
26
+ static NSString *const kSpecSelector = @"JS_NativeRNFBTurboFunctions_SpecHttpsCallableData:";
27
+ /** Prefix matches JS_NativeRNFBTurbo* but lacks _Spec: must not be wrapped. */
28
+ static NSString *const kNotASpecSelector = @"JS_NativeRNFBTurboFunctions_NotASpecMethod:";
29
+ static NSString *const kUnrelatedSelector = @"unrelatedConverter:";
30
+ static NSString *const kCxxConvertSpecSelector = @"JS_NativeRNFBTurboApp_SpecData:";
31
+ static NSString *const kCxxConvertClassName = @"RCTCxxConvert";
32
+
33
+ @interface RNFBNullSentinelInterceptorTests : XCTestCase
34
+ @end
35
+
36
+ /**
37
+ * Each test that needs a class builds its own uniquely named one at runtime and disposes of it in
38
+ * tearDown, so no test observes IMP wrapping or a global RCTCxxConvert registered by another test.
39
+ * That makes the suite independent of XCTest execution order and safe to run singly.
40
+ */
41
+ @implementation RNFBNullSentinelInterceptorTests {
42
+ // Class pointers are stored as NSValue so ARC never retains/releases a class after disposal.
43
+ // Per-test stand-ins: scheduled blocks may capture these, so tearDown spares them on a drain
44
+ // timeout.
45
+ NSMutableArray<NSValue *> *_standInClasses;
46
+ // Fake global RCTCxxConvert: nothing queued captures it, so tearDown always disposes it.
47
+ NSMutableArray<NSValue *> *_globalClasses;
48
+ // Class pointer -> the IMPs of the block-backed methods this test added to it.
49
+ NSMutableDictionary<NSValue *, NSArray<NSValue *> *> *_originalIMPsByClass;
50
+ // Heap-allocated dispatch_once_t storage (see -newOnceToken), freed in tearDown.
51
+ NSMutableArray<NSValue *> *_onceTokens;
52
+ }
53
+
54
+ - (void)setUp {
55
+ [super setUp];
56
+ gLastJsonSeenByOriginalIMP = nil;
57
+ _standInClasses = [NSMutableArray array];
58
+ _globalClasses = [NSMutableArray array];
59
+ _originalIMPsByClass = [NSMutableDictionary dictionary];
60
+ _onceTokens = [NSMutableArray array];
61
+ }
62
+
63
+ - (void)tearDown {
64
+ // schedule...WithOnceToken: dispatches a block to the main queue that captures the once token
65
+ // pointer and the stand-in class. Drain the main queue (FIFO, so every block queued by the test
66
+ // has run once the drain block does) before disposing those or freeing the token. If the drain
67
+ // times out, leak them instead of risking a use-after-free in a late block. The global
68
+ // RCTCxxConvert is never captured by a queued block, so it is always disposed; otherwise a
69
+ // leaked registration would fail every later RCTCxxConvert test.
70
+ BOOL drained = [self drainMainQueue];
71
+ [self disposeClasses:_globalClasses];
72
+ if (drained) {
73
+ [self disposeClasses:_standInClasses];
74
+ for (NSValue *tokenValue in _onceTokens) {
75
+ free(tokenValue.pointerValue);
76
+ }
77
+ }
78
+ [_standInClasses removeAllObjects];
79
+ [_globalClasses removeAllObjects];
80
+ [_originalIMPsByClass removeAllObjects];
81
+ [_onceTokens removeAllObjects];
82
+ gLastJsonSeenByOriginalIMP = nil;
83
+ [super tearDown];
84
+ }
85
+
86
+ /**
87
+ * Disposes of the given classes and removes their block-backed IMPs. Each block IMP must be
88
+ * removed exactly once (a second imp_removeBlock crashes), so gather the unique set per class: the
89
+ * original stand-in IMPs plus any wrapper IMPs the interceptor swizzled in.
90
+ */
91
+ - (void)disposeClasses:(NSArray<NSValue *> *)classValues {
92
+ for (NSValue *classValue in classValues) {
93
+ __unsafe_unretained Class cls = (__bridge Class)classValue.pointerValue;
94
+ NSMutableSet<NSValue *> *blockIMPs =
95
+ [NSMutableSet setWithArray:_originalIMPsByClass[classValue] ?: @[]];
96
+ unsigned int count = 0;
97
+ Method *methods = class_copyMethodList(object_getClass(cls), &count);
98
+ for (unsigned int i = 0; i < count; i++) {
99
+ [blockIMPs
100
+ addObject:[NSValue valueWithPointer:(const void *)method_getImplementation(methods[i])]];
101
+ }
102
+ free(methods);
103
+ objc_disposeClassPair(cls);
104
+ for (NSValue *value in blockIMPs) {
105
+ imp_removeBlock((IMP)value.pointerValue);
106
+ }
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Creates and registers a class named `name` whose class methods (one per selector name) record
112
+ * the received json in gLastJsonSeenByOriginalIMP and return it. Disposed in tearDown. `global`
113
+ * marks a class registered under a well-known global name (RCTCxxConvert).
114
+ */
115
+ - (Class)registerClassNamed:(NSString *)name
116
+ selectorNames:(NSArray<NSString *> *)selectorNames
117
+ global:(BOOL)global {
118
+ Class cls = objc_allocateClassPair([NSObject class], name.UTF8String, 0);
119
+ XCTAssertTrue(cls != Nil, @"could not allocate class %@", name);
120
+ if (cls == Nil) {
121
+ return Nil;
122
+ }
123
+ Class meta = object_getClass(cls);
124
+ NSMutableArray<NSValue *> *imps = [NSMutableArray array];
125
+ for (NSString *selectorName in selectorNames) {
126
+ IMP imp = imp_implementationWithBlock(^id(__unused id receiver, id json) {
127
+ gLastJsonSeenByOriginalIMP = json;
128
+ return json;
129
+ });
130
+ BOOL added = class_addMethod(meta, NSSelectorFromString(selectorName), imp, "@@:@");
131
+ XCTAssertTrue(added, @"could not add class method %@ to %@", selectorName, name);
132
+ [imps addObject:[NSValue valueWithPointer:(const void *)imp]];
133
+ }
134
+ objc_registerClassPair(cls);
135
+ NSValue *classValue = [NSValue valueWithPointer:(__bridge const void *)cls];
136
+ _originalIMPsByClass[classValue] = imps;
137
+ [global ? _globalClasses : _standInClasses addObject:classValue];
138
+ return cls;
139
+ }
140
+
141
+ /** Stand-in for RCTCxxConvert codegen categories, uniquely named per call. */
142
+ - (Class)makeStandInConvertClass {
143
+ static NSUInteger counter = 0; // XCTest runs test methods serially on one thread.
144
+ NSString *name =
145
+ [NSString stringWithFormat:@"RNFBNullSentinelStandInConvert_%lu", (unsigned long)++counter];
146
+ return [self registerClassNamed:name
147
+ selectorNames:@[ kSpecSelector, kNotASpecSelector, kUnrelatedSelector ]
148
+ global:NO];
149
+ }
150
+
151
+ - (NSDictionary *)nullSentinel {
152
+ return @{@"__rnfbNull" : @YES};
153
+ }
154
+
155
+ - (NSDictionary *)unrelatedOneKeyDictionary {
156
+ return @{@".sv" : @"timestamp"};
157
+ }
158
+
159
+ - (IMP)classMethodIMP:(Class)cls selector:(SEL)selector {
160
+ Method method = class_getClassMethod(cls, selector);
161
+ XCTAssertTrue(method != NULL, @"missing class method %@", NSStringFromSelector(selector));
162
+ return method_getImplementation(method);
163
+ }
164
+
165
+ /** Invokes the class method's current IMP, so swizzled wrappers are exercised. */
166
+ - (id)callConvert:(Class)cls selectorName:(NSString *)selectorName json:(id)json {
167
+ typedef id (*ConvertFunc)(id, SEL, id);
168
+ SEL selector = NSSelectorFromString(selectorName);
169
+ ConvertFunc convert = (ConvertFunc)[self classMethodIMP:cls selector:selector];
170
+ return convert(cls, selector, json);
171
+ }
172
+
173
+ /**
174
+ * Heap-allocated once token. A stack token would dangle if a dispatched block ran after the test
175
+ * method returned. Freed in tearDown, after the main queue is drained.
176
+ */
177
+ - (dispatch_once_t *)newOnceToken {
178
+ dispatch_once_t *token = calloc(1, sizeof(dispatch_once_t));
179
+ [_onceTokens addObject:[NSValue valueWithPointer:token]];
180
+ return token;
181
+ }
182
+
183
+ /** Returns YES once every main-queue block queued before this call has run. */
184
+ - (BOOL)drainMainQueue {
185
+ XCTestExpectation *drained = [self expectationWithDescription:@"main queue drained"];
186
+ dispatch_async(dispatch_get_main_queue(), ^{
187
+ [drained fulfill];
188
+ });
189
+ XCTWaiterResult result = [XCTWaiter waitForExpectations:@[ drained ] timeout:2.0];
190
+ XCTAssertEqual(result, XCTWaiterResultCompleted, @"main queue did not drain in time");
191
+ return result == XCTWaiterResultCompleted;
192
+ }
193
+
194
+ - (void)testSwizzleTurboModuleConversions_nullSentinel_handsNSNullToOriginalIMP {
195
+ Class standIn = [self makeStandInConvertClass];
196
+ SEL selector = NSSelectorFromString(kSpecSelector);
197
+ IMP before = [self classMethodIMP:standIn selector:selector];
198
+
199
+ [RNFBNullSentinelInterceptor swizzleTurboModuleConversions:standIn];
200
+
201
+ IMP after = [self classMethodIMP:standIn selector:selector];
202
+ XCTAssertNotEqual(before, after);
203
+
204
+ gLastJsonSeenByOriginalIMP = nil;
205
+ id result = [self callConvert:standIn selectorName:kSpecSelector json:[self nullSentinel]];
206
+ XCTAssertEqualObjects(gLastJsonSeenByOriginalIMP, [NSNull null]);
207
+ XCTAssertEqualObjects(result, [NSNull null]);
208
+ }
209
+
210
+ - (void)testSwizzleTurboModuleConversions_unrelatedOneKeyDictionary_unchanged {
211
+ Class standIn = [self makeStandInConvertClass];
212
+ [RNFBNullSentinelInterceptor swizzleTurboModuleConversions:standIn];
213
+
214
+ gLastJsonSeenByOriginalIMP = nil;
215
+ NSDictionary *payload = [self unrelatedOneKeyDictionary];
216
+ id result = [self callConvert:standIn selectorName:kSpecSelector json:payload];
217
+
218
+ XCTAssertEqualObjects(gLastJsonSeenByOriginalIMP, payload);
219
+ XCTAssertEqualObjects(result, payload);
220
+ }
221
+
222
+ - (void)testScheduleSwizzle_doesNotSwizzleUntilMainQueueRuns {
223
+ Class standIn = [self makeStandInConvertClass];
224
+ SEL selector = NSSelectorFromString(kSpecSelector);
225
+ IMP before = [self classMethodIMP:standIn selector:selector];
226
+
227
+ dispatch_once_t *onceToken = [self newOnceToken];
228
+ [RNFBNullSentinelInterceptor scheduleSwizzleOnMainQueueWithOnceToken:onceToken
229
+ turboConvertClass:standIn];
230
+
231
+ IMP afterSchedule = [self classMethodIMP:standIn selector:selector];
232
+ XCTAssertEqual(before, afterSchedule, @"swizzle must not run synchronously from schedule");
233
+
234
+ [self drainMainQueue];
235
+
236
+ IMP afterDrain = [self classMethodIMP:standIn selector:selector];
237
+ XCTAssertNotEqual(before, afterDrain, @"swizzle must run once the main queue drains");
238
+
239
+ gLastJsonSeenByOriginalIMP = nil;
240
+ [self callConvert:standIn selectorName:kSpecSelector json:[self nullSentinel]];
241
+ XCTAssertEqualObjects(gLastJsonSeenByOriginalIMP, [NSNull null]);
242
+ }
243
+
244
+ - (void)testScheduleSwizzle_secondCallDoesNotDoubleWrap {
245
+ Class standIn = [self makeStandInConvertClass];
246
+ SEL selector = NSSelectorFromString(kSpecSelector);
247
+
248
+ dispatch_once_t *onceToken = [self newOnceToken];
249
+ [RNFBNullSentinelInterceptor scheduleSwizzleOnMainQueueWithOnceToken:onceToken
250
+ turboConvertClass:standIn];
251
+ [self drainMainQueue];
252
+ IMP afterFirst = [self classMethodIMP:standIn selector:selector];
253
+
254
+ [RNFBNullSentinelInterceptor scheduleSwizzleOnMainQueueWithOnceToken:onceToken
255
+ turboConvertClass:standIn];
256
+ [self drainMainQueue];
257
+ IMP afterSecond = [self classMethodIMP:standIn selector:selector];
258
+
259
+ XCTAssertEqual(afterFirst, afterSecond, @"dispatch_once must prevent a second IMP wrap");
260
+ }
261
+
262
+ // Nil arm of `if (cxxConvertClass)`.
263
+ // Precondition: the unit host has no React runtime (RNFBAppUnitTests.xcodeproj links no React), so
264
+ // RCTCxxConvert is absent. The original missing-case test made the same assumption. The only test
265
+ // that registers a global RCTCxxConvert (the "present" test) disposes of it in tearDown, so the
266
+ // class is also absent here regardless of which test ran first.
267
+ - (void)testSwizzleRCTConvertMethods_missingRCTCxxConvert_isNoOp {
268
+ XCTAssertNil(NSClassFromString(kCxxConvertClassName));
269
+ XCTAssertNoThrow([RNFBNullSentinelInterceptor swizzleRCTConvertMethods]);
270
+ }
271
+
272
+ // Non-nil arm of `if (cxxConvertClass)`. Registers a global RCTCxxConvert stand-in (disposed in
273
+ // tearDown). Same no-React-in-the-host precondition as the missing-class test above.
274
+ - (void)testSwizzleRCTConvertMethods_whenRCTCxxConvertPresent_swizzlesSpecMethods {
275
+ XCTAssertNil(NSClassFromString(kCxxConvertClassName),
276
+ @"no RCTCxxConvert may exist before this test registers its stand-in");
277
+ Class convertClass = [self registerClassNamed:kCxxConvertClassName
278
+ selectorNames:@[ kCxxConvertSpecSelector ]
279
+ global:YES];
280
+ XCTAssertNotNil(convertClass);
281
+ if (convertClass == Nil) {
282
+ return;
283
+ }
284
+
285
+ SEL selector = NSSelectorFromString(kCxxConvertSpecSelector);
286
+ IMP before = [self classMethodIMP:convertClass selector:selector];
287
+ [RNFBNullSentinelInterceptor swizzleRCTConvertMethods];
288
+ IMP after = [self classMethodIMP:convertClass selector:selector];
289
+ XCTAssertNotEqual(before, after);
290
+
291
+ gLastJsonSeenByOriginalIMP = nil;
292
+ id result = [self callConvert:convertClass
293
+ selectorName:kCxxConvertSpecSelector
294
+ json:[self nullSentinel]];
295
+ XCTAssertEqualObjects(gLastJsonSeenByOriginalIMP, [NSNull null]);
296
+ XCTAssertEqualObjects(result, [NSNull null]);
297
+ }
298
+
299
+ - (void)testSwizzleTurboModuleConversions_prefixWithoutSpec_leavesIMPUnchanged {
300
+ Class standIn = [self makeStandInConvertClass];
301
+ SEL notASpec = NSSelectorFromString(kNotASpecSelector);
302
+ SEL unrelated = NSSelectorFromString(kUnrelatedSelector);
303
+ IMP notASpecBefore = [self classMethodIMP:standIn selector:notASpec];
304
+ IMP unrelatedBefore = [self classMethodIMP:standIn selector:unrelated];
305
+
306
+ [RNFBNullSentinelInterceptor swizzleTurboModuleConversions:standIn];
307
+
308
+ XCTAssertEqual(notASpecBefore, [self classMethodIMP:standIn selector:notASpec]);
309
+ XCTAssertEqual(unrelatedBefore, [self classMethodIMP:standIn selector:unrelated]);
310
+ }
311
+
312
+ @end