@react-native-firebase/app 26.3.2 → 26.4.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 (79) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/RNFBApp.podspec +1 -1
  3. package/android/src/main/java/io/invertase/firebase/common/RNFBHandleCollisionException.java +31 -0
  4. package/android/src/main/java/io/invertase/firebase/common/RNFBHandleMap.java +150 -0
  5. package/android/src/main/java/io/invertase/firebase/common/TaskExecutorService.java +27 -3
  6. package/android/src/reactnative/java/io/invertase/firebase/app/NativeRNFBTurboApp.java +3 -1
  7. package/android/src/reactnative/java/io/invertase/firebase/app/ReactNativeFirebaseVersion.java +1 -1
  8. package/android/src/reactnative/java/io/invertase/firebase/common/RCTConvertFirebase.java +3 -2
  9. package/android/src/reactnative/java/io/invertase/firebase/common/ReactNativeFirebaseJSON.java +5 -3
  10. package/android/src/test/java/io/invertase/firebase/app/NativeRNFBTurboAppTest.java +26 -1
  11. package/android/src/test/java/io/invertase/firebase/common/RNFBHandleMapTest.java +275 -0
  12. package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebaseEventEmitterTest.java +1 -1
  13. package/android/src/test/java/io/invertase/firebase/common/ReactNativeFirebaseJSONTest.java +108 -0
  14. package/android/src/test/java/io/invertase/firebase/common/TaskExecutorServiceTest.java +97 -0
  15. package/android/src/test/java/io/invertase/firebase/utils/NativeRNFBTurboUtilsTest.java +0 -2
  16. package/dist/module/common/Base64.js +13 -21
  17. package/dist/module/common/Base64.js.map +1 -1
  18. package/dist/module/common/index.js +17 -9
  19. package/dist/module/common/index.js.map +1 -1
  20. package/dist/module/internal/asyncStorage.js +7 -11
  21. package/dist/module/internal/asyncStorage.js.map +1 -1
  22. package/dist/module/internal/logger.js +44 -40
  23. package/dist/module/internal/logger.js.map +1 -1
  24. package/dist/module/internal/nativeModuleAndroidIos.js +2 -29
  25. package/dist/module/internal/nativeModuleAndroidIos.js.map +1 -1
  26. package/dist/module/internal/nativeModuleDebug.js +74 -0
  27. package/dist/module/internal/nativeModuleDebug.js.map +1 -0
  28. package/dist/module/internal/nativeModuleWeb.js +9 -29
  29. package/dist/module/internal/nativeModuleWeb.js.map +1 -1
  30. package/dist/module/internal/registry/app.js +1 -1
  31. package/dist/module/internal/registry/app.js.map +1 -1
  32. package/dist/module/internal/registry/modular.js +2 -2
  33. package/dist/module/internal/registry/modular.js.map +1 -1
  34. package/dist/module/internal/registry/nativeModule.js +3 -3
  35. package/dist/module/internal/registry/nativeModule.js.map +1 -1
  36. package/dist/module/internal/web/RNFBAppModule.js +16 -15
  37. package/dist/module/internal/web/RNFBAppModule.js.map +1 -1
  38. package/dist/module/internal/web/memidb/lib/FakeEventTarget.js +3 -1
  39. package/dist/module/internal/web/memidb/lib/FakeEventTarget.js.map +1 -1
  40. package/dist/module/version.js +1 -1
  41. package/dist/typescript/lib/common/Base64.d.ts.map +1 -1
  42. package/dist/typescript/lib/common/index.d.ts.map +1 -1
  43. package/dist/typescript/lib/internal/asyncStorage.d.ts.map +1 -1
  44. package/dist/typescript/lib/internal/logger.d.ts.map +1 -1
  45. package/dist/typescript/lib/internal/nativeModuleAndroidIos.d.ts.map +1 -1
  46. package/dist/typescript/lib/internal/nativeModuleDebug.d.ts +2 -0
  47. package/dist/typescript/lib/internal/nativeModuleDebug.d.ts.map +1 -0
  48. package/dist/typescript/lib/internal/nativeModuleWeb.d.ts.map +1 -1
  49. package/dist/typescript/lib/internal/web/RNFBAppModule.d.ts +7 -7
  50. package/dist/typescript/lib/internal/web/RNFBAppModule.d.ts.map +1 -1
  51. package/dist/typescript/lib/version.d.ts +1 -1
  52. package/firebase-schema.json +1 -1
  53. package/firebase_spm.rb +240 -95
  54. package/ios/RNFBApp/RNFBAppModule.mm +42 -16
  55. package/ios/RNFBApp/RNFBHandleMap.h +73 -0
  56. package/ios/RNFBApp/RNFBHandleMap.m +116 -0
  57. package/ios/RNFBApp/RNFBSharedUtils.m +3 -2
  58. package/ios/RNFBApp/RNFBUtilsModule.mm +2 -3
  59. package/ios/RNFBApp/RNFBVersion.m +1 -1
  60. package/ios/RNFBApp.xcodeproj/project.pbxproj +6 -0
  61. package/ios/RNFBAppUnitTests/RNFBAppUnitTests.xcodeproj/project.pbxproj +237 -0
  62. package/ios/RNFBAppUnitTests/RNFBAppUnitTests.xcodeproj/xcshareddata/xcschemes/RNFBAppUnitTests.xcscheme +69 -0
  63. package/ios/RNFBAppUnitTests/RNFBHandleMapTests.m +238 -0
  64. package/lib/common/Base64.ts +10 -16
  65. package/lib/common/index.ts +20 -9
  66. package/lib/internal/asyncStorage.ts +7 -11
  67. package/lib/internal/logger.ts +53 -43
  68. package/lib/internal/nativeModuleAndroidIos.ts +2 -40
  69. package/lib/internal/nativeModuleDebug.ts +97 -0
  70. package/lib/internal/nativeModuleWeb.ts +10 -40
  71. package/lib/internal/registry/app.ts +1 -1
  72. package/lib/internal/registry/modular.ts +2 -2
  73. package/lib/internal/registry/nativeModule.ts +3 -3
  74. package/lib/internal/web/RNFBAppModule.ts +16 -15
  75. package/lib/internal/web/memidb/lib/FakeEventTarget.js +3 -1
  76. package/lib/version.ts +1 -1
  77. package/package.json +3 -2
  78. package/plugin/build/ios/appDelegate.js +18 -8
  79. package/plugin/src/ios/appDelegate.ts +44 -14
package/firebase_spm.rb CHANGED
@@ -313,10 +313,11 @@ end
313
313
  # release restructures `Pod::Installer`), it prints a `pod install`-time
314
314
  # warning telling you to call this explicitly instead:
315
315
  #
316
- # post_install do |installer|
317
- # react_native_post_install(installer, ...)
316
+ # post_integrate do |installer|
318
317
  # rnfirebase_add_spm_embed_phase(installer)
319
318
  # end
319
+ #
320
+ # Use `post_install` only if your CocoaPods version has no `post_integrate`.
320
321
  def rnfirebase_add_spm_embed_phase(installer)
321
322
  return unless RNFirebaseSPM.active?
322
323
 
@@ -386,7 +387,7 @@ def rnfirebase_verify_spm_embed_phase_applied!(installer)
386
387
 
387
388
  Without it, this app will crash at launch with a missing-library dyld error, because Firebase's Swift Package frameworks never get copied into the app bundle.
388
389
 
389
- Add `rnfirebase_add_spm_embed_phase(installer)` to your Podfile's post_install block as a fallback, then run `pod install` again. # rubocop:disable Layout/LineLength
390
+ Add `rnfirebase_add_spm_embed_phase(installer)` to your Podfile's post_integrate block as a fallback, then run `pod install` again. # rubocop:disable Layout/LineLength
390
391
  MESSAGE
391
392
  end
392
393
 
@@ -444,6 +445,41 @@ def rnfirebase_fail_if_spm_static_linkage!(installer)
444
445
  MESSAGE
445
446
  end
446
447
 
448
+ # Expo's precompiled-module pre-install hook runs after Podfile pre-install
449
+ # hooks and changes React-Core-dependent pod targets to static libraries.
450
+ # That is required for Expo's own source-built pods, but RNFB's Firebase SPM
451
+ # products are automatic libraries: making more than one RNFB pod static
452
+ # absorbs the same Firebase objects into each archive and the app link fails
453
+ # with duplicate symbols.
454
+ #
455
+ # Restore only Expo's generated prebuilt-RNCore + dynamic-framework path, and
456
+ # only RNFB targets that Expo actually changed to `static_library`. Bare
457
+ # consumers never define `Expo::PrecompiledModules`; source-built Expo installs
458
+ # make `enabled?` false; static/no-framework Podfiles do not report `:dynamic`.
459
+ # Existing dynamic RNFB targets and explicit static frameworks are untouched.
460
+ def rnfirebase_restore_dynamic_linkage_after_expo_prebuilt!(installer)
461
+ return unless RNFirebaseSPM.active?
462
+ return unless defined?(Expo::PrecompiledModules)
463
+ return unless Expo::PrecompiledModules.respond_to?(:enabled?) &&
464
+ Expo::PrecompiledModules.respond_to?(:linkage)
465
+ return unless Expo::PrecompiledModules.enabled?
466
+ return unless Expo::PrecompiledModules.linkage(installer) == :dynamic
467
+
468
+ static_library = Pod::BuildType.static_library
469
+ dynamic_framework = Pod::BuildType.dynamic_framework
470
+ restored_targets = installer.pod_targets.select do |target|
471
+ target.name.start_with?('RNFB') && target.build_type == static_library
472
+ end
473
+
474
+ restored_targets.each do |target|
475
+ target.define_singleton_method(:build_type) { dynamic_framework }
476
+ end
477
+ return if restored_targets.empty? || !defined?(Pod::UI)
478
+
479
+ names = restored_targets.map(&:name).join(', ')
480
+ Pod::UI.puts "[react-native-firebase] Restored dynamic framework linkage after Expo prebuilt downgrade: #{names}"
481
+ end
482
+
447
483
  # CocoaPods `TargetUUIDGenerator` replaces `@generated_uuids` with leftover
448
484
  # `@available_uuids` before Podfile `post_install`. RN's SPM integration then
449
485
  # calls `project.new` for Firebase package product deps; with an empty/short
@@ -527,19 +563,103 @@ end
527
563
  # runs automatically on every `pod install`/`pod update`, without requiring
528
564
  # any Podfile change from consumers.
529
565
  #
530
- # We wrap `Pod::Installer#run_podfile_post_install_hooks` -- the method
531
- # CocoaPods calls, unconditionally, on every install (it's what runs the
532
- # Podfile's own `post_install do |installer| ... end` block, if any, but the
533
- # *wrapper* method itself always runs even when the Podfile defines no
534
- # `post_install` at all). This is the same point in the install lifecycle
535
- # where consumers previously called `rnfirebase_add_spm_embed_phase`
536
- # manually, so behavior is unchanged -- only *how* it gets invoked differs.
566
+ # User-project SPM mutations (embed phase, FirebaseCore on the app target,
567
+ # signature workaround, build settings). Must run *after* CocoaPods has
568
+ # integrated `[CP] Embed Pods Frameworks` into the app target -- see
569
+ # `rnfirebase_hook_cocoapods_post_install!`. Extracted so both the
570
+ # `post_integrate` hook (current CocoaPods) and the `post_install` fallback
571
+ # (older CocoaPods without `run_podfile_post_integrate_hooks`) share one body.
572
+ def rnfirebase_run_spm_user_project_hooks(installer)
573
+ begin
574
+ rnfirebase_add_spm_embed_phase(installer)
575
+ rescue StandardError => e
576
+ if defined?(Pod::UI)
577
+ Pod::UI.warn "[react-native-firebase] Couldn't embed Firebase SPM frameworks " \
578
+ "automatically (#{e.class}: #{e.message}). Add `rnfirebase_add_spm_embed_phase(installer)` " \
579
+ 'to your Podfile\'s post_integrate block as a fallback.'
580
+ end
581
+ end
582
+ # Deliberately outside the `rescue` above: dynamic framework embedding is
583
+ # load-bearing (without it, the app crashes at launch with a missing-library
584
+ # dyld error), so if the phase still isn't on a target that needs it --
585
+ # whether the call raised, silently no-opped, or only partially applied --
586
+ # this must abort `pod install` rather than let a broken install continue.
587
+ rnfirebase_verify_spm_embed_phase_applied!(installer)
588
+ begin
589
+ rnfirebase_add_spm_core_to_app_target(installer)
590
+ rescue StandardError => e
591
+ if defined?(Pod::UI)
592
+ Pod::UI.warn "[react-native-firebase] Couldn't link FirebaseCore into the app target " \
593
+ "automatically (#{e.class}: #{e.message}). " \
594
+ 'Add `rnfirebase_add_spm_core_to_app_target(installer)` ' \
595
+ 'to your Podfile\'s post_integrate block as a fallback if your own native code calls ' \
596
+ 'FIRApp/FIROptions APIs directly.'
597
+ end
598
+ end
599
+ begin
600
+ rnfirebase_remove_spm_core_from_app_target(installer)
601
+ rescue StandardError => e
602
+ if defined?(Pod::UI)
603
+ Pod::UI.warn "[react-native-firebase] Couldn't remove a stale FirebaseCore SPM link from the " \
604
+ "app target automatically (#{e.class}: #{e.message}). If you previously used SPM and have " \
605
+ 'since set `$RNFirebaseDisableSPM = true`, remove the "firebase-ios-sdk" Swift Package ' \
606
+ 'dependency from your app target manually in Xcode.'
607
+ end
608
+ end
609
+ begin
610
+ rnfirebase_fix_spm_archive_signature_collision(installer)
611
+ rescue StandardError => e
612
+ if defined?(Pod::UI)
613
+ Pod::UI.warn '[react-native-firebase] Couldn\'t add the Firebase/Google SPM binary ' \
614
+ "xcframework signature workaround automatically (#{e.class}: #{e.message}). If your " \
615
+ 'Release archive fails with `"...xcframework-ios.signature" couldn\'t be copied to ' \
616
+ '"Signatures" because an item with the same name already exists`, add a Run Script ' \
617
+ 'build phase to your app target that runs `rm -f ' \
618
+ '"\\${CONFIGURATION_BUILD_DIR}"/<TheNameFromTheErrorMessage>.xcframework-ios.signature`.'
619
+ end
620
+ end
621
+ begin
622
+ rnfirebase_apply_spm_build_settings(installer)
623
+ rescue StandardError => e
624
+ if defined?(Pod::UI)
625
+ Pod::UI.warn "[react-native-firebase] Couldn't apply Firebase SPM build settings " \
626
+ "automatically (#{e.class}: #{e.message}). " \
627
+ 'Add `rnfirebase_apply_spm_build_settings(installer)` ' \
628
+ 'to your Podfile\'s post_integrate block as a fallback if Release builds crash at launch with ' \
629
+ 'missing FIRComponent registrations, or Xcode reports that a Firebase module such as ' \
630
+ '`FirebaseCoreInternal`/`FirebaseSharedSwift` cannot be resolved.'
631
+ end
632
+ end
633
+ end
634
+
635
+ # We wrap `Pod::Installer#generate_pods_project` (restore RNFB dynamic
636
+ # frameworks after Expo's prebuilt downgrade and CocoaPods' transitive-static
637
+ # validation, but before product/link-input generation),
638
+ # `Pod::Installer#run_podfile_post_install_hooks` (Pods project: UUID counter,
639
+ # RN SPM integrity, static-linkage guard) and, when present,
640
+ # `Pod::Installer#run_podfile_post_integrate_hooks` (user project: embed
641
+ # Firebase SPM frameworks + link FirebaseCore onto the app target).
642
+ #
643
+ # CocoaPods runs `post_install` *before* writing projects and *before*
644
+ # `integrate_user_project`. That integrator is what adds
645
+ # `[CP] Embed Pods Frameworks` on a clean app target and then *saves* the
646
+ # user `.pbxproj`. Helpers that key off that phase (or that `project.save`
647
+ # the app target) must therefore run in `post_integrate`, which CocoaPods
648
+ # calls at the end of `integrate_user_project` after that save.
649
+ #
650
+ # Expo CNG `expo prebuild --clean` always starts from a template with no CP
651
+ # embed phase. Running the user-project helpers from `post_install` silently
652
+ # skipped every native target, then integrate wrote `[CP] Embed Pods
653
+ # Frameworks` without `[RNFB] Embed Firebase SPM Frameworks` or a
654
+ # FirebaseCore `PBXBuildFile` -- undefined `_OBJC_CLASS_$_FIRApp` from
655
+ # AppDelegate (#9158 / CPRN-301). A committed bare-RN `ios/` folder that
656
+ # already had the CP phase from a prior `pod install` could look fine at
657
+ # `post_install` and hid this on #9164.
537
658
  #
538
659
  # This file is `require`d from each RNFB podspec, which CocoaPods evaluates
539
660
  # early, during dependency resolution (itself one of the first steps inside
540
661
  # `Installer#install!`). That's early enough for the patch installed here to
541
- # affect the *later*, fresh call to `run_podfile_post_install_hooks` made
542
- # further down in that same `install!` run.
662
+ # affect the later hook calls in that same `install!` run.
543
663
  #
544
664
  # Why hook CocoaPods instead of React Native: `Pod::Installer` is a stable,
545
665
  # semantically-versioned public class that the wider CocoaPods plugin
@@ -547,23 +667,27 @@ end
547
667
  # changed in years. That makes it a meaningfully safer patch target than
548
668
  # RN's private, unversioned `react-native/scripts/cocoapods/spm.rb` helper,
549
669
  # which isn't part of any documented RN contract. If CocoaPods ever
550
- # renames/removes this method, the guards below no-op instead of raising,
551
- # and print a `pod install`-time warning (a visible integration error,
552
- # rather than a silent runtime dyld crash) telling you to call
670
+ # renames/removes these methods, the guards below no-op instead of raising,
671
+ # and print a `pod install`-time warning telling you to call
553
672
  # `rnfirebase_add_spm_embed_phase(installer)` from your own Podfile as a
554
- # fallback.
673
+ # fallback. Older CocoaPods without `run_podfile_post_integrate_hooks` keeps
674
+ # the user-project helpers on `post_install`.
555
675
  #
556
676
  # `installer_class` is only ever overridden by tests -- there's no real
557
677
  # `Pod::Installer` outside of a full CocoaPods environment.
558
678
  def rnfirebase_hook_cocoapods_post_install!(installer_class = (Pod::Installer if defined?(Pod::Installer)))
679
+ generate_method = :generate_pods_project
680
+ generate_original_method = :rnfirebase_original_generate_pods_project
559
681
  hook_method = :run_podfile_post_install_hooks
560
682
  original_method = :rnfirebase_original_run_podfile_post_install_hooks
683
+ integrate_hook_method = :run_podfile_post_integrate_hooks
684
+ integrate_original_method = :rnfirebase_original_run_podfile_post_integrate_hooks
561
685
 
562
686
  unless installer_class
563
687
  if defined?(Pod::UI)
564
688
  Pod::UI.warn '[react-native-firebase] `Pod::Installer` isn\'t defined -- automatic Firebase SPM setup ' \
565
689
  '(dynamic framework embedding, etc.) was not hooked into `pod install`. Add ' \
566
- '`rnfirebase_add_spm_embed_phase(installer)` to your Podfile\'s post_install block as a fallback.'
690
+ '`rnfirebase_add_spm_embed_phase(installer)` to your Podfile\'s post_integrate block as a fallback.'
567
691
  end
568
692
  return
569
693
  end
@@ -573,19 +697,84 @@ def rnfirebase_hook_cocoapods_post_install!(installer_class = (Pod::Installer if
573
697
  if defined?(Pod::UI)
574
698
  Pod::UI.warn "[react-native-firebase] `Pod::Installer##{hook_method}` doesn't exist (a CocoaPods " \
575
699
  'release may have renamed or removed it) -- automatic Firebase SPM setup was not hooked into ' \
576
- '`pod install`. Add `rnfirebase_add_spm_embed_phase(installer)` to your Podfile\'s post_install ' \
700
+ '`pod install`. Add `rnfirebase_add_spm_embed_phase(installer)` to your Podfile\'s post_integrate ' \
577
701
  'block as a fallback.'
578
702
  end
579
703
  return
580
704
  end
581
705
 
706
+ already_hooked_post_install = installer_class.method_defined?(original_method) ||
707
+ installer_class.private_method_defined?(original_method)
708
+ already_hooked_generate = installer_class.method_defined?(generate_original_method) ||
709
+ installer_class.private_method_defined?(generate_original_method)
710
+ already_hooked_post_integrate = installer_class.method_defined?(integrate_original_method) ||
711
+ installer_class.private_method_defined?(integrate_original_method)
712
+
713
+ generate_was_private = installer_class.private_method_defined?(generate_method)
714
+ generate_available = generate_was_private || installer_class.method_defined?(generate_method)
715
+ integrate_was_private = installer_class.private_method_defined?(integrate_hook_method)
716
+ post_integrate_available = integrate_was_private || installer_class.method_defined?(integrate_hook_method)
717
+
718
+ generate_unavailable_warning = :@rnfirebase_generate_pods_project_unavailable_warning
719
+ if !already_hooked_generate &&
720
+ !generate_available &&
721
+ defined?(Expo::PrecompiledModules) &&
722
+ Expo::PrecompiledModules.respond_to?(:enabled?) &&
723
+ Expo::PrecompiledModules.enabled? &&
724
+ defined?(Pod::UI) &&
725
+ !installer_class.instance_variable_defined?(generate_unavailable_warning)
726
+ Pod::UI.warn '[react-native-firebase] `Pod::Installer#generate_pods_project` does not exist ' \
727
+ '(a CocoaPods release may have renamed or removed it) -- Expo prebuilt RNFB ' \
728
+ 'dynamic-linkage restoration was not hooked into `pod install`. Use the CocoaPods ' \
729
+ 'version required by your Expo SDK and report this version combination if the app ' \
730
+ 'links duplicate Firebase symbols.'
731
+ installer_class.instance_variable_set(generate_unavailable_warning, true)
732
+ end
733
+
582
734
  # Already hooked -- e.g. a second RNFB podspec also `require`d this same
583
- # file within one `pod install` process (this file is required by path,
584
- # and a hoisted/symlinked dependency layout can resolve to the "same"
585
- # file more than once). This is expected and idempotent, not a failure --
586
- # every multi-podspec RNFB install hits this exact path -- so it's
587
- # deliberately silent rather than warning on every normal install.
588
- return if installer_class.method_defined?(original_method) || installer_class.private_method_defined?(original_method)
735
+ # file within one `pod install` process. Expected and idempotent.
736
+ return if (already_hooked_generate || !generate_available) &&
737
+ already_hooked_post_install &&
738
+ (already_hooked_post_integrate || !post_integrate_available)
739
+
740
+ unless already_hooked_generate || !generate_available
741
+ installer_class.class_eval do
742
+ alias_method generate_original_method, generate_method
743
+
744
+ define_method(generate_method) do
745
+ # Expo's patched pre-install implementation has already downgraded
746
+ # React-Core-dependent targets, and CocoaPods has already validated
747
+ # that graph. Restore RNFB immediately before CocoaPods reads each
748
+ # pod target's build type to generate products and app link inputs.
749
+ begin
750
+ rnfirebase_restore_dynamic_linkage_after_expo_prebuilt!(self)
751
+ rescue StandardError => e
752
+ if defined?(Pod::UI)
753
+ Pod::UI.warn '[react-native-firebase] Expo prebuilt RNFB dynamic-linkage restoration failed ' \
754
+ "(#{e.class}: #{e.message}). Pod install aborted to avoid a partial restore."
755
+ end
756
+ raise
757
+ end
758
+ send(generate_original_method)
759
+ end
760
+ end
761
+ installer_class.send(:private, generate_method) if generate_was_private
762
+ end
763
+
764
+ unless already_hooked_post_integrate || !post_integrate_available
765
+ installer_class.class_eval do
766
+ alias_method integrate_original_method, integrate_hook_method
767
+
768
+ define_method(integrate_hook_method) do
769
+ result = send(integrate_original_method)
770
+ rnfirebase_run_spm_user_project_hooks(self)
771
+ result
772
+ end
773
+ end
774
+ installer_class.send(:private, integrate_hook_method) if integrate_was_private
775
+ end
776
+
777
+ return if already_hooked_post_install
589
778
 
590
779
  installer_class.class_eval do
591
780
  alias_method original_method, hook_method
@@ -599,7 +788,7 @@ def rnfirebase_hook_cocoapods_post_install!(installer_class = (Pod::Installer if
599
788
  rnfirebase_fail_if_spm_static_linkage!(self)
600
789
  # Soft ensure (warn on unexpected errors) -- paired with the hard
601
790
  # `rnfirebase_verify_pods_project_uuid_integrity!` after original
602
- # post_install, same pattern as embed-phase add + verify below.
791
+ # post_install.
603
792
  begin
604
793
  rnfirebase_ensure_pods_uuid_counter_safe!(self)
605
794
  rescue StandardError => e
@@ -614,71 +803,10 @@ def rnfirebase_hook_cocoapods_post_install!(installer_class = (Pod::Installer if
614
803
  # Deliberately not rescued: if RN SPM overwrote `rootObject`, continuing
615
804
  # would only delay the failure to Xcode with a worse diagnostic.
616
805
  rnfirebase_verify_pods_project_uuid_integrity!(self)
617
- begin
618
- rnfirebase_add_spm_embed_phase(self)
619
- rescue StandardError => e
620
- if defined?(Pod::UI)
621
- Pod::UI.warn "[react-native-firebase] Couldn't embed Firebase SPM frameworks " \
622
- "automatically (#{e.class}: #{e.message}). Add `rnfirebase_add_spm_embed_phase(installer)` " \
623
- 'to your Podfile\'s post_install block as a fallback.'
624
- end
625
- end
626
- # Deliberately outside the `rescue` above, and not itself wrapped in a
627
- # rescue-and-warn like the softer checks in this method: dynamic
628
- # framework embedding is load-bearing (without it, the app crashes at
629
- # launch with a missing-library dyld error), so if the phase still
630
- # isn't actually on a target that needs it after the call above --
631
- # whether that call raised, silently no-opped, or only partially
632
- # applied -- this must abort `pod install` with a clear message
633
- # rather than let a broken install continue, the same way
634
- # `rnfirebase_fail_if_spm_static_linkage!` above is deliberately not
635
- # rescued either.
636
- rnfirebase_verify_spm_embed_phase_applied!(self)
637
- begin
638
- rnfirebase_add_spm_core_to_app_target(self)
639
- rescue StandardError => e
640
- if defined?(Pod::UI)
641
- Pod::UI.warn "[react-native-firebase] Couldn't link FirebaseCore into the app target " \
642
- "automatically (#{e.class}: #{e.message}). " \
643
- 'Add `rnfirebase_add_spm_core_to_app_target(installer)` ' \
644
- 'to your Podfile\'s post_install block as a fallback if your own native code calls ' \
645
- 'FIRApp/FIROptions APIs directly.'
646
- end
647
- end
648
- begin
649
- rnfirebase_remove_spm_core_from_app_target(self)
650
- rescue StandardError => e
651
- if defined?(Pod::UI)
652
- Pod::UI.warn "[react-native-firebase] Couldn't remove a stale FirebaseCore SPM link from the " \
653
- "app target automatically (#{e.class}: #{e.message}). If you previously used SPM and have " \
654
- 'since set `$RNFirebaseDisableSPM = true`, remove the "firebase-ios-sdk" Swift Package ' \
655
- 'dependency from your app target manually in Xcode.'
656
- end
657
- end
658
- begin
659
- rnfirebase_fix_spm_archive_signature_collision(self)
660
- rescue StandardError => e
661
- if defined?(Pod::UI)
662
- Pod::UI.warn '[react-native-firebase] Couldn\'t add the Firebase/Google SPM binary ' \
663
- "xcframework signature workaround automatically (#{e.class}: #{e.message}). If your " \
664
- 'Release archive fails with `"...xcframework-ios.signature" couldn\'t be copied to ' \
665
- '"Signatures" because an item with the same name already exists`, add a Run Script ' \
666
- 'build phase to your app target that runs `rm -f ' \
667
- '"\\${CONFIGURATION_BUILD_DIR}"/<TheNameFromTheErrorMessage>.xcframework-ios.signature`.'
668
- end
669
- end
670
- begin
671
- rnfirebase_apply_spm_build_settings(self)
672
- rescue StandardError => e
673
- if defined?(Pod::UI)
674
- Pod::UI.warn "[react-native-firebase] Couldn't apply Firebase SPM build settings " \
675
- "automatically (#{e.class}: #{e.message}). " \
676
- 'Add `rnfirebase_apply_spm_build_settings(installer)` ' \
677
- 'to your Podfile\'s post_install block as a fallback if Release builds crash at launch with ' \
678
- 'missing FIRComponent registrations, or Xcode reports that a Firebase module such as ' \
679
- '`FirebaseCoreInternal`/`FirebaseSharedSwift` cannot be resolved.'
680
- end
681
- end
806
+ # Current CocoaPods: user-project mutations run from post_integrate
807
+ # (after `[CP] Embed Pods Frameworks` exists). Older CocoaPods without
808
+ # that hook keeps them here.
809
+ rnfirebase_run_spm_user_project_hooks(self) unless post_integrate_available
682
810
  result
683
811
  end
684
812
  end
@@ -687,14 +815,15 @@ rescue StandardError => e
687
815
  if defined?(Pod::UI)
688
816
  Pod::UI.warn "[react-native-firebase] Couldn't hook CocoaPods to auto-embed Firebase SPM " \
689
817
  "frameworks (#{e.class}: #{e.message}). Add `rnfirebase_add_spm_embed_phase(installer)` " \
690
- 'to your Podfile\'s post_install block as a fallback.'
818
+ 'to your Podfile\'s post_integrate block as a fallback.'
691
819
  end
692
820
  end
693
821
 
694
822
  # Adds a direct SPM product dependency on `FirebaseCore` to the *app's own*
695
823
  # native target(s) -- not just RNFB's pod targets. Runs automatically on every
696
- # `pod install`/`pod update` alongside `rnfirebase_add_spm_embed_phase` -- see
697
- # `rnfirebase_hook_cocoapods_post_install!` below -- so you normally never
824
+ # `pod install`/`pod update` from `rnfirebase_run_spm_user_project_hooks`
825
+ # (post_integrate on current CocoaPods; post_install fallback otherwise) --
826
+ # see `rnfirebase_hook_cocoapods_post_install!` -- so you normally never
698
827
  # need to call this yourself.
699
828
  #
700
829
  # Why this needs to exist: every react-native-firebase app is required to
@@ -1044,13 +1173,29 @@ def rnfirebase_apply_spm_build_settings(installer)
1044
1173
  project.save if project_modified
1045
1174
  end
1046
1175
 
1047
- installer.pods_project.targets.each do |target|
1176
+ # User-project settings are `project.save`d above. Pods settings used to
1177
+ # live only on the in-memory `installer.pods_project` during `post_install`,
1178
+ # when CocoaPods still writes that file. The user-project hooks now run
1179
+ # from `post_integrate` (after that write), so these mutations are lost
1180
+ # unless we save. Xcode 26 then keeps explicit modules on RNFB pod
1181
+ # targets and fails Swift compiles that import Firebase SPM internals
1182
+ # (`FirebaseCoreInternal`, `FirebaseSharedSwift`) with "compilation
1183
+ # search paths unable to resolve module dependency".
1184
+ pods_project = installer.pods_project
1185
+ return unless pods_project
1186
+
1187
+ pods_modified = false
1188
+ pods_project.targets.each do |target|
1048
1189
  target.build_configurations.each do |config|
1049
1190
  explicit_modules_settings.each do |setting|
1050
- config.build_settings[setting] = 'NO'
1191
+ unless config.build_settings[setting] == 'NO'
1192
+ config.build_settings[setting] = 'NO'
1193
+ pods_modified = true
1194
+ end
1051
1195
  end
1052
1196
  end
1053
1197
  end
1198
+ pods_project.save if pods_modified
1054
1199
  end
1055
1200
 
1056
1201
  rnfirebase_hook_cocoapods_post_install!
@@ -41,8 +41,13 @@
41
41
  #endif
42
42
 
43
43
  @interface RNFBAppModule () <NativeRNFBTurboAppSpec, RCTInvalidating>
44
+
45
+ + (void)setCustomDomain:(nullable NSString *)authDomain forAppName:(NSString *)appName;
46
+
44
47
  @end
45
48
 
49
+ static NSMutableDictionary<NSString *, NSString *> *customAuthDomains;
50
+
46
51
  @implementation RNFBAppModule
47
52
 
48
53
  #pragma mark -
@@ -102,11 +107,11 @@ RCT_EXPORT_MODULE(NativeRNFBTurboApp)
102
107
  return constants;
103
108
  }
104
109
 
105
- - (facebook::react::ModuleConstants<JS::NativeRNFBTurboApp::Constants::Builder>)constantsToExport {
110
+ - (facebook::react::ModuleConstants<JS::NativeRNFBTurboApp::Constants>)constantsToExport {
106
111
  return [_RCTTypedModuleConstants newWithUnsafeDictionary:[self appConstantsDictionary]];
107
112
  }
108
113
 
109
- - (facebook::react::ModuleConstants<JS::NativeRNFBTurboApp::Constants::Builder>)getConstants {
114
+ - (facebook::react::ModuleConstants<JS::NativeRNFBTurboApp::Constants>)getConstants {
110
115
  return [self constantsToExport];
111
116
  }
112
117
 
@@ -200,6 +205,9 @@ RCT_EXPORT_MODULE(NativeRNFBTurboApp)
200
205
  RCTUnsafeExecuteOnMainQueueSync(^{
201
206
  FIRApp *firApp;
202
207
  NSString *appName = [appConfig valueForKey:@"name"];
208
+ NSString *authDomain = [options valueForKey:@"authDomain"];
209
+ NSString *jsAppName = (appName.length > 0) ? appName : DEFAULT_APP_DISPLAY_NAME;
210
+ BOOL isDefaultApp = !appName || [appName isEqualToString:DEFAULT_APP_DISPLAY_NAME];
203
211
 
204
212
  NSString *appId = [options valueForKey:@"appId"];
205
213
  NSString *messagingSenderId = [options valueForKey:@"messagingSenderId"];
@@ -223,17 +231,16 @@ RCT_EXPORT_MODULE(NativeRNFBTurboApp)
223
231
  firOptions.appGroupID = [options valueForKey:@"appGroupId"];
224
232
  }
225
233
 
226
- if ([options valueForKey:@"authDomain"] != nil) {
227
- DLog(@"RNFBAuth app: %@ customAuthDomain: %@", appName, [options valueForKey:@"authDomain"]);
228
- if (customAuthDomains == nil) {
229
- customAuthDomains = [[NSMutableDictionary alloc] init];
230
- }
231
- customAuthDomains[appName] = [options valueForKey:@"authDomain"];
232
- }
233
234
  @try {
234
- if (!appName || [appName isEqualToString:DEFAULT_APP_DISPLAY_NAME]) {
235
- [FIRApp configureWithOptions:firOptions];
236
- firApp = [FIRApp defaultApp];
235
+ if (isDefaultApp) {
236
+ // Native bootstrap often already called [FIRApp configure]. Still accept a JS/bridge
237
+ // initializeApp for the default app so customAuthDomains can be keyed by [DEFAULT].
238
+ if ([FIRApp defaultApp] != nil) {
239
+ firApp = [FIRApp defaultApp];
240
+ } else {
241
+ [FIRApp configureWithOptions:firOptions];
242
+ firApp = [FIRApp defaultApp];
243
+ }
237
244
  } else {
238
245
  [FIRApp configureWithName:appName options:firOptions];
239
246
  firApp = [FIRApp appNamed:appName];
@@ -242,6 +249,9 @@ RCT_EXPORT_MODULE(NativeRNFBTurboApp)
242
249
  return [RNFBSharedUtils rejectPromiseWithExceptionDict:reject exception:exception];
243
250
  }
244
251
 
252
+ // Store under the JS bridge app name ([DEFAULT]), never native __FIRAPP_DEFAULT.
253
+ [RNFBAppModule setCustomDomain:authDomain forAppName:jsAppName];
254
+
245
255
  firApp.dataCollectionDefaultEnabled =
246
256
  (BOOL)[appConfig valueForKey:@"automaticDataCollectionEnabled"];
247
257
 
@@ -249,11 +259,25 @@ RCT_EXPORT_MODULE(NativeRNFBTurboApp)
249
259
  });
250
260
  }
251
261
 
252
- static NSMutableDictionary<NSString *, NSString *> *customAuthDomains;
253
-
254
262
  + (NSString *)getCustomDomain:(NSString *)appName {
255
- DLog(@"authDomains: %@", customAuthDomains);
256
- return customAuthDomains[appName];
263
+ @synchronized(self) {
264
+ DLog(@"authDomains: %@", customAuthDomains);
265
+ return customAuthDomains[appName];
266
+ }
267
+ }
268
+
269
+ + (void)setCustomDomain:(nullable NSString *)authDomain forAppName:(NSString *)appName {
270
+ @synchronized(self) {
271
+ if (authDomain != nil) {
272
+ DLog(@"RNFBAuth app: %@ customAuthDomain: %@", appName, authDomain);
273
+ if (customAuthDomains == nil) {
274
+ customAuthDomains = [[NSMutableDictionary alloc] init];
275
+ }
276
+ customAuthDomains[appName] = authDomain;
277
+ } else {
278
+ [customAuthDomains removeObjectForKey:appName];
279
+ }
280
+ }
257
281
  }
258
282
 
259
283
  - (void)setLogLevel:(NSString *)logLevel {
@@ -288,10 +312,12 @@ static NSMutableDictionary<NSString *, NSString *> *customAuthDomains;
288
312
 
289
313
  [firApp deleteApp:^(BOOL success) {
290
314
  if (success) {
315
+ [RNFBAppModule setCustomDomain:nil forAppName:appName];
291
316
  resolve([NSNull null]);
292
317
  } else {
293
318
  [firApp deleteApp:^(BOOL success2) {
294
319
  if (success2) {
320
+ [RNFBAppModule setCustomDomain:nil forAppName:appName];
295
321
  resolve([NSNull null]);
296
322
  } else {
297
323
  reject(@"app/delete-app-failed", @"Failed to delete the specified app.", nil);
@@ -0,0 +1,73 @@
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 <Foundation/Foundation.h>
19
+
20
+ NS_ASSUME_NONNULL_BEGIN
21
+
22
+ FOUNDATION_EXPORT NSErrorDomain const RNFBHandleMapErrorDomain;
23
+
24
+ typedef NS_ERROR_ENUM(RNFBHandleMapErrorDomain, RNFBHandleMapErrorCode){
25
+ RNFBHandleMapErrorCollision = 1,
26
+ };
27
+
28
+ /**
29
+ * Thread-safe id → handle registry. The lock only moves pointers; this class never invokes methods
30
+ * on stored values (no SDK cancel/remove). Callers `take` or `takeAll`, then act on the returned
31
+ * object(s) outside the lock. Keys are typically NSNumber or NSString.
32
+ *
33
+ * `put` is unique: an occupied id fails with `RNFBHandleMapErrorCollision`. There is no upsert.
34
+ * Use `putIfAbsent` (first wins), `putIfAbsentOrSame` (first wins or same instance), or
35
+ * `putReplacing` (last wins) for atomic upsert.
36
+ *
37
+ * Do not lock on a `RNFBHandleMap` instance. This type uses a private lock object; callers that
38
+ * synchronize on the map can deadlock. Never `@synchronized(handleMap)` around these methods.
39
+ */
40
+ @interface RNFBHandleMap : NSObject
41
+
42
+ /// Registers `value` under `key`. Returns NO and sets `error` if `key` is already occupied.
43
+ - (BOOL)put:(id)key value:(id)value error:(NSError *_Nullable *_Nullable)error;
44
+
45
+ /// Stores `value` under `key` only when the key is free (first wins). Returns NO if occupied.
46
+ - (BOOL)putIfAbsent:(id)key value:(id)value;
47
+
48
+ /// Under one lock: stores when free, or YES when existing is the same instance (`==`). NO if
49
+ /// occupied by a different value.
50
+ - (BOOL)putIfAbsentOrSame:(id)key value:(id)value;
51
+
52
+ /// Atomically replaces the mapping for `key` (last wins). Returns the previous value, or nil.
53
+ - (nullable id)putReplacing:(id)key value:(id)value;
54
+
55
+ /// Peeks at the handle for `key` without removing it (for emit while still registered).
56
+ - (nullable id)get:(id)key;
57
+
58
+ /// Removes and returns the handle for `key`, or nil if absent.
59
+ - (nullable id)take:(id)key;
60
+
61
+ /// When `key` is mapped and `condition` returns YES for the handle, removes and returns it;
62
+ /// otherwise returns nil and leaves the map unchanged. Lookup, condition, and removal run under one
63
+ /// lock — use for check-and-take that must not race with concurrent `put` / `take` on the same key.
64
+ /// Keep `condition` lightweight (no SDK cancel/remove).
65
+ - (nullable id)takeIf:(id)key when:(BOOL (^)(id value))condition;
66
+
67
+ /// Snapshot of current values, then clear. Cancel/remove each returned object after this method
68
+ /// returns, not under this map's lock.
69
+ - (NSArray *)takeAll;
70
+
71
+ @end
72
+
73
+ NS_ASSUME_NONNULL_END