diff --git a/.github/workflows/abtesting.yml b/.github/workflows/abtesting.yml index a8701041b72..b4ec5bef578 100644 --- a/.github/workflows/abtesting.yml +++ b/.github/workflows/abtesting.yml @@ -15,7 +15,7 @@ on: - '.github/workflows/common_catalyst.yml' - 'Gemfile*' schedule: - # Run every day at 1am(PST) - cron uses UTC times + # Run every day at 2am (PDT) / 5am (EDT) - cron uses UTC times - cron: '0 9 * * *' concurrency: @@ -51,7 +51,7 @@ jobs: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Setup quickstart env: LEGACY: true @@ -86,7 +86,7 @@ jobs: run: scripts/decrypt_gha_secret.sh scripts/gha-encrypted/qs-abtesting.plist.gpg \ quickstart-ios/abtesting/GoogleService-Info.plist "$plist_secret" - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Build swift quickstart env: LEGACY: true @@ -116,7 +116,7 @@ jobs: - name: Setup Bundler run: scripts/setup_bundler.sh - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: PodLibLint ABTesting Cron run: | scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb \ diff --git a/.github/workflows/analytics.yml b/.github/workflows/analytics.yml index bb8d5c44141..6e835ee1eff 100644 --- a/.github/workflows/analytics.yml +++ b/.github/workflows/analytics.yml @@ -9,7 +9,7 @@ on: - '.github/workflows/analytics.yml' - 'Gemfile*' schedule: - # Run every day at 1am (PST) - cron uses UTC times + # Run every day at 2am (PDT) / 5am (EDT) - cron uses UTC times - cron: '0 9 * * *' concurrency: diff --git a/.github/workflows/appdistribution.yml b/.github/workflows/appdistribution.yml index 026e95f81ac..808e7ffe697 100644 --- a/.github/workflows/appdistribution.yml +++ b/.github/workflows/appdistribution.yml @@ -14,7 +14,7 @@ on: - '.github/workflows/common_catalyst.yml' - 'Gemfile*' schedule: - # Run every day at 1am (PST) - cron uses UTC times + # Run every day at 2am (PDT) / 5am (EDT) - cron uses UTC times - cron: '0 9 * * *' concurrency: @@ -54,6 +54,8 @@ jobs: steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 + - name: Setup Xcode + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Setup Bundler run: scripts/setup_bundler.sh - name: PodLibLint App Distribution Cron diff --git a/.github/workflows/archiving.yml b/.github/workflows/archiving.yml index c70afe14b8e..c3b58ebbb69 100644 --- a/.github/workflows/archiving.yml +++ b/.github/workflows/archiving.yml @@ -6,7 +6,7 @@ on: paths: - '.github/workflows/archiving.yml' schedule: - # Run every day at 2am (PST) - cron uses UTC times + # Run every day at 3am (PDT) / 6am (EDT) - cron uses UTC times # This is set to 3 hours after zip workflow finishes so zip testing can run after. - cron: '0 10 * * *' @@ -27,6 +27,8 @@ jobs: pod: ["FirebaseAppDistribution", "FirebaseInAppMessaging", "FirebasePerformance"] steps: - uses: actions/checkout@v4 + - name: Set Xcode version + run: sudo xcode-select -s /Applications/Xcode_16.4.app - uses: mikehardy/buildcache-action@c87cea0ccd718971d6cc39e672c4f26815b6c126 with: cache_key: cron-${{ matrix.os }} @@ -50,6 +52,8 @@ jobs: pod: ["FirebaseABTesting", "FirebaseAuth", "FirebaseCore", "FirebaseCrashlytics", "FirebaseDatabase", "FirebaseFirestore", "FirebaseFunctions", "FirebaseMessaging", "FirebaseRemoteConfig", "FirebaseStorage"] steps: - uses: actions/checkout@v4 + - name: Set Xcode version + run: sudo xcode-select -s /Applications/Xcode_16.4.app - uses: mikehardy/buildcache-action@c87cea0ccd718971d6cc39e672c4f26815b6c126 with: cache_key: pods-${{ matrix.os }} diff --git a/.github/workflows/auth.yml b/.github/workflows/auth.yml index 03e8b2526f2..6a0447b399d 100644 --- a/.github/workflows/auth.yml +++ b/.github/workflows/auth.yml @@ -16,7 +16,7 @@ on: - 'scripts/gha-encrypted/AuthSample/SwiftApplication.plist.gpg' - 'Gemfile*' schedule: - # Run every day at 1am (PST) - cron uses UTC times + # Run every day at 2am (PDT) / 5am (EDT) - cron uses UTC times - cron: '0 9 * * *' env: @@ -156,6 +156,8 @@ jobs: steps: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 + - name: Setup Xcode + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Setup Bundler run: scripts/setup_bundler.sh - name: Configure test keychain diff --git a/.github/workflows/client_app.yml b/.github/workflows/client_app.yml index 150d879edca..12d64a91d67 100644 --- a/.github/workflows/client_app.yml +++ b/.github/workflows/client_app.yml @@ -13,7 +13,7 @@ on: - "IntegrationTesting/ClientApp/**" - "Gemfile*" schedule: - # Run every day at 12am (PST) - cron uses UTC times + # Run every day at 1am (PDT) / 4am (EDT) - cron uses UTC times - cron: "0 8 * * *" env: @@ -27,12 +27,16 @@ jobs: client-app-spm: if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' strategy: - # TODO: Add Xcode matrix when Xcode 16 is ubiquitous on CI runners. matrix: #TODO(ncooke3): Add multi-platform support: tvOS, macOS, catalyst platform: [iOS] scheme: [ClientApp] os: [macos-14, macos-15] + include: + - os: macos-14 + xcode: Xcode_16.2 + - os: macos-15 + xcode: Xcode_16.4 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -40,8 +44,8 @@ jobs: with: cache_key: ${{ matrix.os }} - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer - - name: Build Client App –– ${{ matrix.platform }} + run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer + - name: Build Client App - ${{ matrix.platform }} run: scripts/third_party/travis/retry.sh ./scripts/build.sh ${{ matrix.scheme }} ${{ matrix.platform }} xcodebuild client-app-spm-source-firestore: @@ -50,12 +54,16 @@ jobs: FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 FIREBASE_SOURCE_FIRESTORE: 1 strategy: - # TODO: Add Xcode matrix when Xcode 16 is ubiquitous on CI runners. matrix: #TODO(ncooke3): Add multi-platform support: tvOS, macOS, catalyst platform: [iOS] scheme: [ClientApp] os: [macos-14, macos-15] + include: + - os: macos-14 + xcode: Xcode_16.2 + - os: macos-15 + xcode: Xcode_16.4 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -63,18 +71,22 @@ jobs: with: cache_key: ${{ matrix.os }} - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer - - name: Build Client App –– ${{ matrix.platform }} + run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer + - name: Build Client App - ${{ matrix.platform }} run: scripts/third_party/travis/retry.sh ./scripts/build.sh ${{ matrix.scheme }} ${{ matrix.platform }} xcodebuild client-app-cocoapods: # Don't run on private repo unless it is a PR. if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' strategy: - # TODO: Add Xcode matrix when Xcode 16 is ubiquitous on CI runners. matrix: scheme: [ClientApp-CocoaPods] os: [macos-14, macos-15] + include: + - os: macos-14 + xcode: Xcode_16.2 + - os: macos-15 + xcode: Xcode_16.4 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -85,7 +97,7 @@ jobs: - name: Setup Bundler run: scripts/setup_bundler.sh - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - name: Prereqs run: scripts/install_prereqs.sh ClientApp iOS xcodebuild - name: Build diff --git a/.github/workflows/cocoapods-integration.yml b/.github/workflows/cocoapods-integration.yml index 0a86f76e18b..8853b405a55 100644 --- a/.github/workflows/cocoapods-integration.yml +++ b/.github/workflows/cocoapods-integration.yml @@ -8,7 +8,7 @@ on: - '.github/workflows/cocoapods-integration.yml' - 'Gemfile*' schedule: - # Run every day at 2am (PST) - cron uses UTC times + # Run every day at 3am (PDT) / 6am (EDT) - cron uses UTC times - cron: '0 10 * * *' concurrency: @@ -26,6 +26,8 @@ jobs: - uses: mikehardy/buildcache-action@c87cea0ccd718971d6cc39e672c4f26815b6c126 with: cache_key: ${{ matrix.os }} + - name: Xcode + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Get realpath run: brew install coreutils - name: Build and test diff --git a/.github/workflows/combine.yml b/.github/workflows/combine.yml index 268d7c21d8c..37e6741ca04 100644 --- a/.github/workflows/combine.yml +++ b/.github/workflows/combine.yml @@ -42,7 +42,7 @@ on: # - 'Firestore/**' # (Disabled to avoid building Firestore in presubmits) schedule: - # Run every day at 11pm (PST) - cron uses UTC times + # Run every day at 12am (PDT) / 3am (EDT) - cron uses UTC times - cron: '0 7 * * *' concurrency: @@ -67,6 +67,9 @@ jobs: - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 + - name: Xcode + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer + - name: Install xcpretty run: gem install xcpretty @@ -88,6 +91,8 @@ jobs: with: cache_key: ${{ matrix.os }} - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 + - name: Xcode + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Setup Bundler run: scripts/setup_bundler.sh - name: Install xcpretty diff --git a/.github/workflows/common.yml b/.github/workflows/common.yml index 1a82d76b36f..b79680f09b4 100644 --- a/.github/workflows/common.yml +++ b/.github/workflows/common.yml @@ -67,7 +67,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Generate Swift Package.resolved id: swift_package_resolve run: swift package resolve @@ -104,10 +104,6 @@ jobs: key: ${{needs.spm-package-resolved.outputs.cache_key}} - name: Xcode run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - - name: Install visionOS, if needed. - if: matrix.platform == 'visionOS' - run: ls $(xcode-select -p)/Platforms/XROS.platform || \ - { xcodebuild -downloadPlatform visionOS } - name: Run setup command, if needed. if: inputs.setup_command != '' run: ${{ inputs.setup_command }} diff --git a/.github/workflows/common_catalyst.yml b/.github/workflows/common_catalyst.yml index 6948e4e3197..da3911b064c 100644 --- a/.github/workflows/common_catalyst.yml +++ b/.github/workflows/common_catalyst.yml @@ -38,7 +38,7 @@ jobs: - name: Setup Bundler run: scripts/setup_bundler.sh - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # v3 with: timeout_minutes: 120 diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index f380a85947b..0c30320895a 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -14,7 +14,7 @@ on: - '.github/workflows/common_catalyst.yml' - 'Gemfile*' schedule: - # Run every day at 2am (PST) - cron uses UTC times + # Run every day at 3am (PDT) / 6am (EDT) - cron uses UTC times - cron: '0 10 * * *' concurrency: diff --git a/.github/workflows/core_extension.yml b/.github/workflows/core_extension.yml index 15b86f38538..b36caf5c85c 100644 --- a/.github/workflows/core_extension.yml +++ b/.github/workflows/core_extension.yml @@ -14,7 +14,7 @@ on: - '.github/workflows/common_cocoapods.yml' - 'Gemfile*' schedule: - # Run every day at 2am (PST) - cron uses UTC times + # Run every day at 3am (PDT) / 6am (EDT) - cron uses UTC times - cron: '0 10 * * *' jobs: @@ -39,7 +39,7 @@ jobs: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Setup Bundler run: scripts/setup_bundler.sh - name: PodLibLint CoreInternal Cron diff --git a/.github/workflows/core_internal.yml b/.github/workflows/core_internal.yml index d1b8dd6cf73..f536f6c4656 100644 --- a/.github/workflows/core_internal.yml +++ b/.github/workflows/core_internal.yml @@ -15,7 +15,7 @@ on: - '.github/workflows/common_catalyst.yml' - 'Gemfile*' schedule: - # Run every day at 2am (PST) - cron uses UTC times + # Run every day at 3am (PDT) / 6am (EDT) - cron uses UTC times - cron: '0 10 * * *' jobs: @@ -54,7 +54,7 @@ jobs: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Setup Bundler run: scripts/setup_bundler.sh - name: PodLibLint CoreInternal Cron diff --git a/.github/workflows/crashlytics.yml b/.github/workflows/crashlytics.yml index a32d474a61b..13c307610ea 100644 --- a/.github/workflows/crashlytics.yml +++ b/.github/workflows/crashlytics.yml @@ -16,7 +16,7 @@ on: - 'Interop/Analytics/Public/*.h' - 'Gemfile*' schedule: - # Run every day at 10am (PST) - cron uses UTC times + # Run every day at 7pm (PDT) / 10pm (EDT) - cron uses UTC times - cron: '0 2 * * *' concurrency: @@ -53,7 +53,7 @@ jobs: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Setup quickstart run: scripts/setup_quickstart.sh crashlytics env: @@ -86,7 +86,7 @@ jobs: with: python-version: '3.11' - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Setup quickstart run: scripts/setup_quickstart.sh crashlytics env: @@ -130,7 +130,7 @@ jobs: - name: Setup Bundler run: scripts/setup_bundler.sh - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # v3 with: timeout_minutes: 120 diff --git a/.github/workflows/database.yml b/.github/workflows/database.yml index 928c3bf28ad..74a5bebe067 100644 --- a/.github/workflows/database.yml +++ b/.github/workflows/database.yml @@ -19,7 +19,7 @@ on: - 'Gemfile*' - 'scripts/run_database_emulator.sh' schedule: - # Run every day at 2am (PST) - cron uses UTC times + # Run every day at 3am (PDT) / 6am (EDT) - cron uses UTC times - cron: '0 10 * * *' concurrency: @@ -84,7 +84,7 @@ jobs: run: scripts/decrypt_gha_secret.sh scripts/gha-encrypted/qs-database.plist.gpg \ quickstart-ios/database/GoogleService-Info.plist "$plist_secret" - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Test objc quickstart run: ([ -z $plist_secret ] || scripts/third_party/travis/retry.sh scripts/test_quickstart.sh Database false) - name: Test swift quickstart @@ -106,7 +106,7 @@ jobs: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Setup Bundler run: scripts/setup_bundler.sh - name: PodLibLint database Cron diff --git a/.github/workflows/firebase_app_check.yml b/.github/workflows/firebase_app_check.yml index ff30db15a66..fcec95dad4e 100644 --- a/.github/workflows/firebase_app_check.yml +++ b/.github/workflows/firebase_app_check.yml @@ -14,7 +14,7 @@ on: - '.github/workflows/common_catalyst.yml' - 'Gemfile*' schedule: - # Run every day at 11pm (PST) - cron uses UTC times + # Run every day at 12am (PDT) / 3am (EDT) - cron uses UTC times - cron: '0 7 * * *' concurrency: @@ -58,7 +58,7 @@ jobs: with: cache_key: ${{ matrix.diagnostics }} - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Initialize xcodebuild run: scripts/setup_spm_tests.sh - name: iOS Unit Tests @@ -84,7 +84,7 @@ jobs: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Setup Bundler run: scripts/setup_bundler.sh - name: PodLibLint FirebaseAppCheck Cron diff --git a/.github/workflows/firebaseai.yml b/.github/workflows/firebaseai.yml index 7ad2a9dff29..16e3fdb08c5 100644 --- a/.github/workflows/firebaseai.yml +++ b/.github/workflows/firebaseai.yml @@ -13,7 +13,7 @@ on: # Do not run for documentation-only PRs. - '!**.md' schedule: - # Run every day at 11pm (PST) - cron uses UTC times + # Run every day at 12am (PDT) / 3am (EDT) - cron uses UTC times - cron: '0 7 * * *' workflow_dispatch: @@ -82,10 +82,17 @@ jobs: setup_command: scripts/update_vertexai_responses.sh quickstart: - runs-on: macos-15 + strategy: + matrix: + include: + - os: macos-15 + xcode: Xcode_16.4 + runs-on: ${{ matrix.os }} env: BRANCH_NAME: ${{ github.head_ref || github.ref_name || 'main' }} steps: - uses: actions/checkout@v4 + - name: Xcode + run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - name: Build Quickstart run: scripts/quickstart_build_spm.sh FirebaseAI diff --git a/.github/workflows/firebasepod.yml b/.github/workflows/firebasepod.yml index 8f7fd3af5ef..ac61d4735a6 100644 --- a/.github/workflows/firebasepod.yml +++ b/.github/workflows/firebasepod.yml @@ -11,7 +11,7 @@ on: - '.github/workflows/firebasepod.yml' - 'Gemfile*' schedule: - # Run every day at 1am (PST) - cron uses UTC times + # Run every day at 2am (PDT) / 5am (EDT) - cron uses UTC times - cron: '0 9 * * *' concurrency: @@ -34,7 +34,7 @@ jobs: - name: Setup Bundler run: scripts/setup_bundler.sh - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Prereqs run: scripts/install_prereqs.sh FirebasePod iOS - name: Build diff --git a/.github/workflows/firestore.yml b/.github/workflows/firestore.yml index 3e927287a2e..dbb144924d8 100644 --- a/.github/workflows/firestore.yml +++ b/.github/workflows/firestore.yml @@ -18,7 +18,7 @@ on: workflow_dispatch: pull_request: schedule: - # Run every day at 12am (PST) - cron uses UTC times + # Run every day at 1am (PDT) / 4am (EDT) - cron uses UTC times - cron: '0 8 * * *' concurrency: @@ -375,6 +375,9 @@ jobs: - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 + - name: Select Xcode + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer + - name: Setup build run: scripts/install_prereqs.sh Firestore ${{ matrix.target }} xcodebuild @@ -578,7 +581,7 @@ jobs: with: cache_key: ${{ matrix.target }} - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Initialize xcodebuild run: scripts/setup_spm_tests.sh - name: Build Test - Binary diff --git a/.github/workflows/functions.yml b/.github/workflows/functions.yml index c7a8b6e54ca..658a78f1952 100644 --- a/.github/workflows/functions.yml +++ b/.github/workflows/functions.yml @@ -20,7 +20,7 @@ on: - 'Gemfile*' schedule: - # Run every day at 1am (PST) - cron uses UTC times + # Run every day at 2am (PDT) / 5am (EDT) - cron uses UTC times - cron: '0 9 * * *' concurrency: diff --git a/.github/workflows/generate_issues.yml b/.github/workflows/generate_issues.yml index 53473cabb05..b4e598c355c 100644 --- a/.github/workflows/generate_issues.yml +++ b/.github/workflows/generate_issues.yml @@ -7,7 +7,7 @@ on: - '.github/workflows/generate_issues.yml' - '.github/actions/testing_report_generation**' schedule: - # Run every day at 4am (PST) - cron uses UTC times + # Run every day at 5am (PDT) / 8am (EDT) - cron uses UTC times - cron: '0 12 * * *' permissions: diff --git a/.github/workflows/inappmessaging.yml b/.github/workflows/inappmessaging.yml index 6650fbb8fc9..edcf7b25b8d 100644 --- a/.github/workflows/inappmessaging.yml +++ b/.github/workflows/inappmessaging.yml @@ -14,7 +14,7 @@ on: - '.github/workflows/common_cocoapods.yml' - 'Gemfile*' schedule: - # Run every day at 10pm (PST) - cron uses UTC times + # Run every day at 11pm (PDT) / 2am (EDT) - cron uses UTC times - cron: '0 6 * * *' concurrency: @@ -46,7 +46,7 @@ jobs: # TODO(#8682): Reenable iPad after fixing Xcode 13 test failures. # platform: [iOS, iPad] platform: [iOS] - xcode: [Xcode_16.2] + xcode: [Xcode_16.4] steps: - uses: actions/checkout@v4 - uses: mikehardy/buildcache-action@c87cea0ccd718971d6cc39e672c4f26815b6c126 @@ -78,7 +78,7 @@ jobs: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Setup Bundler run: scripts/setup_bundler.sh - name: PodLibLint InAppMessaging Cron @@ -98,7 +98,7 @@ jobs: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Setup quickstart run: scripts/setup_quickstart.sh inappmessaging - name: install secret googleservice-info.plist diff --git a/.github/workflows/installations.yml b/.github/workflows/installations.yml index cf4f6795b18..8a87c9dd341 100644 --- a/.github/workflows/installations.yml +++ b/.github/workflows/installations.yml @@ -14,7 +14,7 @@ on: - '.github/workflows/common_catalyst.yml' - 'Gemfile*' schedule: - # Run every day at 10pm (PST) - cron uses UTC times + # Run every day at 11pm (PDT) / 2am (EDT) - cron uses UTC times - cron: '0 6 * * *' concurrency: @@ -56,7 +56,7 @@ jobs: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Setup quickstart run: scripts/setup_quickstart.sh installations - name: Copy mock plist @@ -78,7 +78,7 @@ jobs: with: python-version: '3.11' - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Setup quickstart run: scripts/setup_quickstart.sh installations - name: Copy mock plist @@ -112,7 +112,7 @@ jobs: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Setup Bundler run: scripts/setup_bundler.sh - name: Configure test keychain diff --git a/.github/workflows/messaging.yml b/.github/workflows/messaging.yml index 838ce5fe4d9..24f6a7de304 100644 --- a/.github/workflows/messaging.yml +++ b/.github/workflows/messaging.yml @@ -22,7 +22,7 @@ on: # Rebuild on Ruby infrastructure changes - 'Gemfile*' schedule: - # Run every day at 10pm (PST) - cron uses UTC times + # Run every day at 11pm (PDT) / 2am (EDT) - cron uses UTC times - cron: '0 6 * * *' concurrency: @@ -89,7 +89,7 @@ jobs: matrix: include: - os: macos-15 - xcode: Xcode_16.2 + xcode: Xcode_16.4 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -122,7 +122,7 @@ jobs: with: python-version: '3.11' - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Setup quickstart run: scripts/setup_quickstart.sh messaging - name: Install Secret GoogleService-Info.plist @@ -150,7 +150,7 @@ jobs: os: [macos-14, macos-15] include: - os: macos-15 - xcode: Xcode_16.2 + xcode: Xcode_16.4 tests: --test-specs=unit - os: macos-14 xcode: Xcode_16.2 @@ -187,7 +187,7 @@ jobs: - name: Prereqs run: scripts/install_prereqs.sh MessagingSample iOS - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Build run: ([ -z $plist_secret ] || scripts/build.sh MessagingSample iOS) @@ -212,7 +212,7 @@ jobs: - name: Prereqs run: scripts/install_prereqs.sh SwiftUISample iOS - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Build run: ([ -z $plist_secret ] || scripts/build.sh SwiftUISample iOS) @@ -237,7 +237,7 @@ jobs: - name: Prereqs run: scripts/install_prereqs.sh MessagingSampleStandaloneWatchApp watchOS - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Build run: ([ -z $plist_secret ] || scripts/build.sh MessagingSampleStandaloneWatchApp watchOS) diff --git a/.github/workflows/mlmodeldownloader.yml b/.github/workflows/mlmodeldownloader.yml index 3eddabeadec..d4bb6f1a5c7 100644 --- a/.github/workflows/mlmodeldownloader.yml +++ b/.github/workflows/mlmodeldownloader.yml @@ -14,7 +14,7 @@ on: - '.github/workflows/common_catalyst.yml' - 'Gemfile*' schedule: - # Run every day at 11pm (PST) - cron uses UTC times + # Run every day at 12am (PDT) / 3am (EDT) - cron uses UTC times - cron: '0 7 * * *' concurrency: @@ -57,7 +57,7 @@ jobs: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Setup Bundler run: scripts/setup_bundler.sh - name: Configure test keychain @@ -83,7 +83,7 @@ jobs: cache_key: build-test${{ matrix.os }} - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Setup Bundler run: scripts/setup_bundler.sh - name: Install GoogleService-Info.plist diff --git a/.github/workflows/notice_generation.yml b/.github/workflows/notice_generation.yml index a7a87cf4251..6d769c509d1 100644 --- a/.github/workflows/notice_generation.yml +++ b/.github/workflows/notice_generation.yml @@ -10,7 +10,7 @@ on: - '.github/workflows/notice_generation.yml' - '.github/actions/notices_generation**' schedule: - # Run every day at 2am (PST) - cron uses UTC times + # Run every day at 3am (PDT) / 6am (EDT) - cron uses UTC times - cron: '0 10 * * *' jobs: generate_a_notice: diff --git a/.github/workflows/performance-integration-tests.yml b/.github/workflows/performance-integration-tests.yml index 1092ba0df0f..95d486d39e6 100644 --- a/.github/workflows/performance-integration-tests.yml +++ b/.github/workflows/performance-integration-tests.yml @@ -13,7 +13,6 @@ on: # - https://crontab.guru/ schedule: # Runs every 4 hours. - # TODO: Validate when the timer starts after job is triggered. - cron: '0 */4 * * *' concurrency: diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml index b1073dfd11f..d788c81a968 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -22,7 +22,7 @@ on: # Rebuild on Ruby infrastructure changes - 'Gemfile*' schedule: - # Run every day at 11pm (PST) - cron uses UTC times + # Run every day at 12am (PDT) / 3am (EDT) - cron uses UTC times # Specified in format 'minutes hours day month dayofweek' - cron: '0 7 * * *' @@ -60,7 +60,7 @@ jobs: cache_key: ${{ matrix.target }}${{ matrix.test }} - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Setup Bundler run: scripts/setup_bundler.sh - name: Install xcpretty @@ -87,7 +87,7 @@ jobs: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Setup quickstart run: scripts/setup_quickstart.sh performance - name: Install Secret GoogleService-Info.plist @@ -113,7 +113,7 @@ jobs: with: python-version: '3.11' - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Setup quickstart run: scripts/setup_quickstart.sh performance - name: Install Secret GoogleService-Info.plist @@ -145,7 +145,7 @@ jobs: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Setup Bundler run: scripts/setup_bundler.sh - name: PodLibLint Performance Cron diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 2cb87713f68..de7187ddb9b 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -9,7 +9,7 @@ on: types: [closed] workflow_dispatch: schedule: - # Run every day at 9pm (PST) - cron uses UTC times + # Run every day at 10pm (PDT) / 1am (EDT) - cron uses UTC times - cron: '0 5 * * *' env: @@ -242,7 +242,7 @@ jobs: - uses: actions/checkout@v4 - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - name: Setup testing repo and quickstart run: BOT_TOKEN="${botaccess}" scripts/setup_quickstart.sh Authentication prerelease_testing - name: Install Secret GoogleService-Info.plist diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index de6631fc6b4..3b28e2fb8ec 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,7 +8,7 @@ on: - 'Gemfile*' workflow_dispatch: schedule: - # Run every day at 9pm (PST) - cron uses UTC times + # Run every day at 10pm (PDT) / 1am (EDT) - cron uses UTC times - cron: '0 5 * * *' env: @@ -86,6 +86,8 @@ jobs: targeted_pod: FirebaseCore steps: - uses: actions/checkout@v4 + - name: Set Xcode version + run: sudo xcode-select -s /Applications/Xcode_16.4.app - uses: actions/download-artifact@v4.1.7 with: name: firebase-ios-sdk @@ -119,6 +121,8 @@ jobs: targeted_pod: ${{ matrix.podspec }} steps: - uses: actions/checkout@v4 + - name: Set Xcode version + run: sudo xcode-select -s /Applications/Xcode_16.4.app - uses: actions/download-artifact@v4.1.7 with: name: firebase-ios-sdk @@ -152,6 +156,8 @@ jobs: runs-on: macos-14 steps: - uses: actions/checkout@v4 + - name: Set Xcode version + run: sudo xcode-select -s /Applications/Xcode_16.4.app - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 - name: Setup testing repo and quickstart env: @@ -186,9 +192,9 @@ jobs: runs-on: macos-15 steps: - uses: actions/checkout@v4 + - name: Set Xcode version + run: sudo xcode-select -s /Applications/Xcode_16.4.app - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 - - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer - name: Setup testing repo and quickstart run: BOT_TOKEN="${botaccess}" scripts/setup_quickstart.sh Authentication nightly_release_testing - name: Install Secret GoogleService-Info.plist @@ -218,6 +224,8 @@ jobs: runs-on: macos-14 steps: - uses: actions/checkout@v4 + - name: Set Xcode version + run: sudo xcode-select -s /Applications/Xcode_16.4.app - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 - name: Setup testing repo and quickstart env: @@ -259,6 +267,8 @@ jobs: runs-on: macos-14 steps: - uses: actions/checkout@v4 + - name: Set Xcode version + run: sudo xcode-select -s /Applications/Xcode_16.4.app - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 - name: Setup testing repo and quickstart run: BOT_TOKEN="${botaccess}" scripts/setup_quickstart.sh database nightly_release_testing @@ -291,6 +301,8 @@ jobs: runs-on: macos-14 steps: - uses: actions/checkout@v4 + - name: Set Xcode version + run: sudo xcode-select -s /Applications/Xcode_16.4.app - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 - name: Setup testing repo and quickstart run: BOT_TOKEN="${botaccess}" scripts/setup_quickstart.sh firestore nightly_release_testing @@ -360,6 +372,8 @@ jobs: runs-on: macos-14 steps: - uses: actions/checkout@v4 + - name: Set Xcode version + run: sudo xcode-select -s /Applications/Xcode_16.4.app - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 - name: Setup testing repo and quickstart run: BOT_TOKEN="${botaccess}" scripts/setup_quickstart.sh inappmessaging nightly_release_testing @@ -394,6 +408,8 @@ jobs: runs-on: macos-14 steps: - uses: actions/checkout@v4 + - name: Set Xcode version + run: sudo xcode-select -s /Applications/Xcode_16.4.app - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 - name: Setup testing repo and quickstart run: BOT_TOKEN="${botaccess}" scripts/setup_quickstart.sh messaging nightly_release_testing @@ -426,6 +442,8 @@ jobs: runs-on: macos-14 steps: - uses: actions/checkout@v4 + - name: Set Xcode version + run: sudo xcode-select -s /Applications/Xcode_16.4.app - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 - name: Setup testing repo and quickstart run: BOT_TOKEN="${botaccess}" scripts/setup_quickstart.sh config nightly_release_testing @@ -457,6 +475,8 @@ jobs: runs-on: macos-14 steps: - uses: actions/checkout@v4 + - name: Set Xcode version + run: sudo xcode-select -s /Applications/Xcode_16.4.app - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 - name: Setup testing repo and quickstart run: BOT_TOKEN="${botaccess}" scripts/setup_quickstart.sh storage nightly_release_testing @@ -487,6 +507,8 @@ jobs: runs-on: macos-14 steps: - uses: actions/checkout@v4 + - name: Set Xcode version + run: sudo xcode-select -s /Applications/Xcode_16.4.app - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 - name: Setup testing repo and quickstart run: BOT_TOKEN="${botaccess}" scripts/setup_quickstart.sh Performance nightly_release_testing diff --git a/.github/workflows/remoteconfig.yml b/.github/workflows/remoteconfig.yml index 199fd669f41..6b7ca4d68b3 100644 --- a/.github/workflows/remoteconfig.yml +++ b/.github/workflows/remoteconfig.yml @@ -17,7 +17,7 @@ on: - 'scripts/generate_access_token.sh' - 'scripts/gha-encrypted/RemoteConfigSwiftAPI/**' schedule: - # Run every day at 12am (PST) - cron uses UTC times + # Run every day at 1am (PDT) / 4am (EDT) - cron uses UTC times - cron: '0 8 * * *' concurrency: diff --git a/.github/workflows/sessions-integration-tests.yml b/.github/workflows/sessions-integration-tests.yml index 4d11e4dc065..ccc8d9f15fd 100644 --- a/.github/workflows/sessions-integration-tests.yml +++ b/.github/workflows/sessions-integration-tests.yml @@ -31,7 +31,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_16.4.app/Contents/Developer - uses: mikehardy/buildcache-action@c87cea0ccd718971d6cc39e672c4f26815b6c126 with: cache_key: sessions-integration-tests diff --git a/.github/workflows/sessions.yml b/.github/workflows/sessions.yml index d3ec5adb7f1..f0df72c44c2 100644 --- a/.github/workflows/sessions.yml +++ b/.github/workflows/sessions.yml @@ -15,7 +15,7 @@ on: - '.github/workflows/common_catalyst.yml' - 'Gemfile*' schedule: - # Run every day at 9am (PST) - cron uses UTC times + # Run every day at 6pm (PDT) / 9pm (EDT) - cron uses UTC times - cron: '0 1 * * *' concurrency: diff --git a/.github/workflows/shared-swift.yml b/.github/workflows/shared-swift.yml index b1292ebb253..fa13d6e9a4c 100644 --- a/.github/workflows/shared-swift.yml +++ b/.github/workflows/shared-swift.yml @@ -14,7 +14,7 @@ on: - 'Gemfile*' schedule: - # Run every day at 3am (PST) - cron uses UTC times + # Run every day at 4am (PDT) / 7am (EDT) - cron uses UTC times - cron: '0 11 * * *' concurrency: diff --git a/.github/workflows/spm.yml b/.github/workflows/spm.yml index 5b807927410..6d583ebd780 100644 --- a/.github/workflows/spm.yml +++ b/.github/workflows/spm.yml @@ -12,7 +12,7 @@ on: - 'SwiftPM-PlatformExclude' - 'Gemfile*' schedule: - # Run every day at 12am (PST) - cron uses UTC times + # Run every day at 1am (PDT) / 4am (EDT) - cron uses UTC times - cron: '0 8 * * *' # This workflow builds and tests the Swift Package Manager. Only iOS runs on PRs @@ -98,7 +98,7 @@ jobs: - os: macos-14 xcode: Xcode_16.2 - os: macos-15 - xcode: Xcode_16.2 + xcode: Xcode_16.4 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -127,7 +127,7 @@ jobs: target: [tvOS, macOS, catalyst] include: - os: macos-15 - xcode: Xcode_16.2 + xcode: Xcode_16.4 - os: macos-14 xcode: Xcode_16.2 runs-on: ${{ matrix.os }} diff --git a/.github/workflows/storage.yml b/.github/workflows/storage.yml index 036037b5ca7..fd109429a5e 100644 --- a/.github/workflows/storage.yml +++ b/.github/workflows/storage.yml @@ -16,7 +16,7 @@ on: # Rebuild on Ruby infrastructure changes. - 'Gemfile*' schedule: - # Run every day at 12am (PST) - cron uses UTC times + # Run every day at 1am (PDT) / 4am (EDT) - cron uses UTC times - cron: '0 8 * * *' concurrency: @@ -88,7 +88,7 @@ jobs: # xcode: Xcode_14.2 # TODO: the legacy ObjC quickstart doesn't build with Xcode 15. - swift: swift os: macos-15 - xcode: Xcode_16.2 + xcode: Xcode_16.4 env: plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} signin_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} @@ -153,7 +153,7 @@ jobs: - os: macos-14 xcode: Xcode_16.2 - os: macos-15 - xcode: Xcode_16.2 + xcode: Xcode_16.4 runs-on: ${{ matrix.build-env.os }} needs: pod_lib_lint steps: diff --git a/.github/workflows/symbolcollision.yml b/.github/workflows/symbolcollision.yml index 00fcde3c3e9..298f7fd4f10 100644 --- a/.github/workflows/symbolcollision.yml +++ b/.github/workflows/symbolcollision.yml @@ -10,7 +10,7 @@ on: - 'SymbolCollisionTest/**' - 'Gemfile*' schedule: - # Run every day at 12am (PST) - cron uses UTC times + # Run every day at 1am (PDT) / 4am (EDT) - cron uses UTC times - cron: '0 8 * * *' concurrency: diff --git a/.github/workflows/watchos-sample.yml b/.github/workflows/watchos-sample.yml index 665dfbea350..b8589288863 100644 --- a/.github/workflows/watchos-sample.yml +++ b/.github/workflows/watchos-sample.yml @@ -18,7 +18,7 @@ on: # Rebuild on Ruby infrastructure changes - 'Gemfile*' schedule: - # Run every day at 10pm (PST) - cron uses UTC times + # Run every day at 11pm (PDT) / 2am (EDT) - cron uses UTC times - cron: '0 6 * * *' concurrency: diff --git a/.github/workflows/zip.yml b/.github/workflows/zip.yml index 9879e942885..10a02312ce0 100644 --- a/.github/workflows/zip.yml +++ b/.github/workflows/zip.yml @@ -12,7 +12,7 @@ on: # Don't run based on any markdown only changes. - '!ReleaseTooling/*.md' schedule: - # Run every day at 8pm(PST) - cron uses UTC times + # Run every day at 9pm (PDT) / 12am (EDT) - cron uses UTC times - cron: '0 4 * * *' workflow_dispatch: @@ -114,9 +114,7 @@ jobs: artifact: [Firebase-actions-dir, Firebase-actions-dir-dynamic] build-env: - os: macos-15 - xcode: Xcode_16.2 - # - os: macos-15 - # xcode: Xcode_16.4 + xcode: Xcode_16.4 runs-on: ${{ matrix.build-env.os }} steps: - uses: actions/checkout@v4 @@ -125,6 +123,8 @@ jobs: with: name: ${{ matrix.artifact }} - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 + - name: Xcode + run: sudo xcode-select -s /Applications/${{ matrix.build-env.xcode }}.app/Contents/Developer - name: Setup Bundler run: ./scripts/setup_bundler.sh - name: Move frameworks @@ -132,8 +132,6 @@ jobs: mkdir -p "${HOME}"/ios_frameworks/ find "${GITHUB_WORKSPACE}" -name "Firebase*latest.zip" -exec unzip -d "${HOME}"/ios_frameworks/ {} + - uses: actions/checkout@v4 - - name: Xcode - run: sudo xcode-select -s /Applications/${{ matrix.build-env.xcode }}.app/Contents/Developer - name: Setup quickstart env: LEGACY: true @@ -176,7 +174,7 @@ jobs: artifact: [Firebase-actions-dir, Firebase-actions-dir-dynamic] include: - os: macos-15 - xcode: Xcode_16.2 + xcode: Xcode_16.4 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -185,14 +183,14 @@ jobs: with: name: ${{ matrix.artifact }} - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 + - name: Xcode + run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - name: Setup Bundler run: ./scripts/setup_bundler.sh - name: Move frameworks run: | mkdir -p "${HOME}"/ios_frameworks/ find "${GITHUB_WORKSPACE}" -name "Firebase*latest.zip" -exec unzip -d "${HOME}"/ios_frameworks/ {} + - - name: Xcode - run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - name: Setup Swift Quickstart run: SAMPLE="$SDK" TARGET="${SDK}Example" NON_FIREBASE_SDKS="FBSDKLoginKit FBSDKCoreKit FBSDKCoreKit_Basics FBAEMKit" scripts/setup_quickstart_framework.sh \ "${HOME}"/ios_frameworks/Firebase/NonFirebaseSDKs/* \ @@ -226,9 +224,7 @@ jobs: artifact: [Firebase-actions-dir, Firebase-actions-dir-dynamic] build-env: - os: macos-15 - xcode: Xcode_16.2 - # - os: macos-15 - # xcode: Xcode_16.4 + xcode: Xcode_16.4 runs-on: ${{ matrix.build-env.os }} steps: - uses: actions/checkout@v4 @@ -237,14 +233,14 @@ jobs: with: name: ${{ matrix.artifact }} - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 + - name: Xcode + run: sudo xcode-select -s /Applications/${{ matrix.build-env.xcode }}.app/Contents/Developer - name: Setup Bundler run: ./scripts/setup_bundler.sh - name: Move frameworks run: | mkdir -p "${HOME}"/ios_frameworks/ find "${GITHUB_WORKSPACE}" -name "Firebase*latest.zip" -exec unzip -d "${HOME}"/ios_frameworks/ {} + - - name: Xcode - run: sudo xcode-select -s /Applications/${{ matrix.build-env.xcode }}.app/Contents/Developer - name: Setup Swift Quickstart run: SAMPLE="$SDK" TARGET="${SDK}Example" scripts/setup_quickstart_framework.sh \ @@ -277,9 +273,7 @@ jobs: artifact: [Firebase-actions-dir, Firebase-actions-dir-dynamic] build-env: - os: macos-15 - xcode: Xcode_16.2 - # - os: macos-15 - # xcode: Xcode_16.4 + xcode: Xcode_16.4 runs-on: ${{ matrix.build-env.os }} steps: - uses: actions/checkout@v4 @@ -288,6 +282,8 @@ jobs: with: name: ${{ matrix.artifact }} - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 + - name: Xcode + run: sudo xcode-select -s /Applications/${{ matrix.build-env.xcode }}.app/Contents/Developer - name: Setup Bundler run: ./scripts/setup_bundler.sh - name: Move frameworks @@ -295,8 +291,6 @@ jobs: mkdir -p "${HOME}"/ios_frameworks/ find "${GITHUB_WORKSPACE}" -name "Firebase*latest.zip" -exec unzip -d "${HOME}"/ios_frameworks/ {} + - uses: actions/checkout@v4 - - name: Xcode - run: sudo xcode-select -s /Applications/${{ matrix.build-env.xcode }}.app/Contents/Developer - name: Setup quickstart env: LEGACY: true @@ -359,6 +353,8 @@ jobs: with: name: ${{ matrix.artifact }} - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 + - name: Xcode + run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - name: Setup Bundler run: ./scripts/setup_bundler.sh - name: Move frameworks @@ -366,8 +362,6 @@ jobs: mkdir -p "${HOME}"/ios_frameworks/ find "${GITHUB_WORKSPACE}" -name "Firebase*latest.zip" -exec unzip -d "${HOME}"/ios_frameworks/ {} + - uses: actions/checkout@v4 - - name: Xcode - run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - name: Setup quickstart run: SAMPLE="$SDK" TARGET="${SDK}Example" NON_FIREBASE_SDKS="FirebaseDatabaseUI" scripts/setup_quickstart_framework.sh \ "${HOME}"/ios_frameworks/Firebase/FirebaseDatabase/* \ @@ -403,9 +397,7 @@ jobs: artifact: [Firebase-actions-dir, Firebase-actions-dir-dynamic] build-env: - os: macos-15 - xcode: Xcode_16.2 - # - os: macos-15 - # xcode: Xcode_16.4 + xcode: Xcode_16.4 runs-on: ${{ matrix.build-env.os }} steps: - uses: actions/checkout@v4 @@ -414,6 +406,8 @@ jobs: with: name: ${{ matrix.artifact }} - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 + - name: Xcode + run: sudo xcode-select -s /Applications/${{ matrix.build-env.xcode }}.app/Contents/Developer - name: Setup Bundler run: ./scripts/setup_bundler.sh - name: Move frameworks @@ -427,8 +421,12 @@ jobs: "${HOME}"/ios_frameworks/Firebase/FirebaseFirestore/* \ "${HOME}"/ios_frameworks/Firebase/FirebaseAuth/* \ "${HOME}"/ios_frameworks/Firebase/FirebaseAnalytics/* - - name: Xcode - run: sudo xcode-select -s /Applications/${{ matrix.build-env.xcode }}.app/Contents/Developer + - name: Upload build logs on failure + if: ${{ failure() }} + uses: actions/upload-artifact@v4 + with: + name: build_logs_firestore_${{ matrix.artifact }}_${{ matrix.build-env.os }} + path: sdk_zip/build_logs/ - name: Install Secret GoogleService-Info.plist run: scripts/decrypt_gha_secret.sh scripts/gha-encrypted/qs-firestore.plist.gpg \ quickstart-ios/firestore/GoogleService-Info.plist "$plist_secret" @@ -487,9 +485,7 @@ jobs: artifact: [Firebase-actions-dir, Firebase-actions-dir-dynamic] build-env: - os: macos-15 - xcode: Xcode_16.2 - # - os: macos-15 - # xcode: Xcode_16.4 + xcode: Xcode_16.4 runs-on: ${{ matrix.build-env.os }} steps: - uses: actions/checkout@v4 @@ -498,6 +494,8 @@ jobs: with: name: ${{ matrix.artifact }} - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 + - name: Xcode + run: sudo xcode-select -s /Applications/${{ matrix.build-env.xcode }}.app/Contents/Developer - name: Setup Bundler run: ./scripts/setup_bundler.sh - name: Move frameworks @@ -509,8 +507,6 @@ jobs: run: SAMPLE="$SDK" TARGET="${SDK}Example" scripts/setup_quickstart_framework.sh \ "${HOME}"/ios_frameworks/Firebase/FirebaseInAppMessaging/* \ "${HOME}"/ios_frameworks/Firebase/FirebaseAnalytics/* - - name: Xcode - run: sudo xcode-select -s /Applications/${{ matrix.build-env.xcode }}.app/Contents/Developer - name: Setup swift quickstart run: SAMPLE="$SDK" TARGET="${SDK}ExampleSwift" scripts/setup_quickstart_framework.sh - name: Install Secret GoogleService-Info.plist @@ -542,9 +538,7 @@ jobs: artifact: [Firebase-actions-dir, Firebase-actions-dir-dynamic] build-env: - os: macos-15 - xcode: Xcode_16.2 - # - os: macos-15 - # xcode: Xcode_16.4 + xcode: Xcode_16.4 runs-on: ${{ matrix.build-env.os }} steps: - uses: actions/checkout@v4 @@ -553,6 +547,8 @@ jobs: with: name: ${{ matrix.artifact }} - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 + - name: Xcode + run: sudo xcode-select -s /Applications/${{ matrix.build-env.xcode }}.app/Contents/Developer - name: Setup Bundler run: ./scripts/setup_bundler.sh - name: Move frameworks @@ -564,8 +560,6 @@ jobs: run: SAMPLE="$SDK" TARGET="${SDK}Example" scripts/setup_quickstart_framework.sh \ "${HOME}"/ios_frameworks/Firebase/FirebaseMessaging/* \ "${HOME}"/ios_frameworks/Firebase/FirebaseAnalytics/* - - name: Xcode - run: sudo xcode-select -s /Applications/${{ matrix.build-env.xcode }}.app/Contents/Developer - name: Setup swift quickstart run: SAMPLE="$SDK" TARGET="${SDK}ExampleSwift" scripts/setup_quickstart_framework.sh - name: Install Secret GoogleService-Info.plist @@ -597,9 +591,7 @@ jobs: artifact: [Firebase-actions-dir, Firebase-actions-dir-dynamic] build-env: - os: macos-15 - xcode: Xcode_16.2 - # - os: macos-15 - # xcode: Xcode_16.4 + xcode: Xcode_16.4 runs-on: ${{ matrix.build-env.os }} steps: - uses: actions/checkout@v4 @@ -608,6 +600,8 @@ jobs: with: name: ${{ matrix.artifact }} - uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 # v1 + - name: Xcode + run: sudo xcode-select -s /Applications/${{ matrix.build-env.xcode }}.app/Contents/Developer - name: Setup Bundler run: ./scripts/setup_bundler.sh - name: Move frameworks @@ -622,8 +616,6 @@ jobs: "${HOME}"/ios_frameworks/Firebase/FirebaseStorage/* \ "${HOME}"/ios_frameworks/Firebase/FirebaseAuth/* \ "${HOME}"/ios_frameworks/Firebase/FirebaseAnalytics/* - - name: Xcode - run: sudo xcode-select -s /Applications/${{ matrix.build-env.xcode }}.app/Contents/Developer - name: Setup swift quickstart env: LEGACY: true diff --git a/Crashlytics/UnitTests/Mocks/FIRCLSMockFileManager.m b/Crashlytics/UnitTests/Mocks/FIRCLSMockFileManager.m index b89d05bbff8..955e701d065 100644 --- a/Crashlytics/UnitTests/Mocks/FIRCLSMockFileManager.m +++ b/Crashlytics/UnitTests/Mocks/FIRCLSMockFileManager.m @@ -41,7 +41,9 @@ - (BOOL)removeItemAtPath:(NSString *)path { // If we set up the expectation, and we went over the expected count or removes, fulfill the // expectation if (self.removeExpectation && self.removeCount >= self.expectedRemoveCount) { - [self.removeExpectation fulfill]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self.removeExpectation fulfill]; + }); } return YES; diff --git a/Firebase.podspec b/Firebase.podspec index 6374a742fa2..415d933d8e0 100644 --- a/Firebase.podspec +++ b/Firebase.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'Firebase' - s.version = '12.1.0' + s.version = '12.2.0' s.summary = 'Firebase' s.description = <<-DESC @@ -36,14 +36,14 @@ Simplify your app development, grow your user base, and monetize more effectivel ss.ios.deployment_target = '15.0' ss.osx.deployment_target = '10.15' ss.tvos.deployment_target = '15.0' - ss.ios.dependency 'FirebaseAnalytics', '~> 12.1.0' - ss.osx.dependency 'FirebaseAnalytics', '~> 12.1.0' - ss.tvos.dependency 'FirebaseAnalytics', '~> 12.1.0' + ss.ios.dependency 'FirebaseAnalytics', '~> 12.2.0' + ss.osx.dependency 'FirebaseAnalytics', '~> 12.2.0' + ss.tvos.dependency 'FirebaseAnalytics', '~> 12.2.0' ss.dependency 'Firebase/CoreOnly' end s.subspec 'CoreOnly' do |ss| - ss.dependency 'FirebaseCore', '~> 12.1.0' + ss.dependency 'FirebaseCore', '~> 12.2.0' ss.source_files = 'CoreOnly/Sources/Firebase.h' ss.preserve_paths = 'CoreOnly/Sources/module.modulemap' if ENV['FIREBASE_POD_REPO_FOR_DEV_POD'] then @@ -70,7 +70,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'ABTesting' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseABTesting', '~> 12.1.0' + ss.dependency 'FirebaseABTesting', '~> 12.2.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '15.0' ss.osx.deployment_target = '10.15' @@ -80,13 +80,13 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'AppDistribution' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.ios.dependency 'FirebaseAppDistribution', '~> 12.1.0-beta' + ss.ios.dependency 'FirebaseAppDistribution', '~> 12.2.0-beta' ss.ios.deployment_target = '15.0' end s.subspec 'AppCheck' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseAppCheck', '~> 12.1.0' + ss.dependency 'FirebaseAppCheck', '~> 12.2.0' ss.ios.deployment_target = '15.0' ss.osx.deployment_target = '10.15' ss.tvos.deployment_target = '15.0' @@ -95,7 +95,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'Auth' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseAuth', '~> 12.1.0' + ss.dependency 'FirebaseAuth', '~> 12.2.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '15.0' ss.osx.deployment_target = '10.15' @@ -105,7 +105,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'Crashlytics' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseCrashlytics', '~> 12.1.0' + ss.dependency 'FirebaseCrashlytics', '~> 12.2.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '15.0' ss.osx.deployment_target = '10.15' @@ -115,7 +115,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'Database' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseDatabase', '~> 12.1.0' + ss.dependency 'FirebaseDatabase', '~> 12.2.0' # Standard platforms PLUS watchOS 7. ss.ios.deployment_target = '15.0' ss.osx.deployment_target = '10.15' @@ -125,7 +125,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'Firestore' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseFirestore', '~> 12.1.0' + ss.dependency 'FirebaseFirestore', '~> 12.2.0' ss.ios.deployment_target = '15.0' ss.osx.deployment_target = '10.15' ss.tvos.deployment_target = '15.0' @@ -133,7 +133,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'Functions' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseFunctions', '~> 12.1.0' + ss.dependency 'FirebaseFunctions', '~> 12.2.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '15.0' ss.osx.deployment_target = '10.15' @@ -143,20 +143,20 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'InAppMessaging' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.ios.dependency 'FirebaseInAppMessaging', '~> 12.1.0-beta' - ss.tvos.dependency 'FirebaseInAppMessaging', '~> 12.1.0-beta' + ss.ios.dependency 'FirebaseInAppMessaging', '~> 12.2.0-beta' + ss.tvos.dependency 'FirebaseInAppMessaging', '~> 12.2.0-beta' ss.ios.deployment_target = '15.0' ss.tvos.deployment_target = '15.0' end s.subspec 'Installations' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseInstallations', '~> 12.1.0' + ss.dependency 'FirebaseInstallations', '~> 12.2.0' end s.subspec 'Messaging' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseMessaging', '~> 12.1.0' + ss.dependency 'FirebaseMessaging', '~> 12.2.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '15.0' ss.osx.deployment_target = '10.15' @@ -166,7 +166,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'MLModelDownloader' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseMLModelDownloader', '~> 12.1.0-beta' + ss.dependency 'FirebaseMLModelDownloader', '~> 12.2.0-beta' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '15.0' ss.osx.deployment_target = '10.15' @@ -176,15 +176,15 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'Performance' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.ios.dependency 'FirebasePerformance', '~> 12.1.0' - ss.tvos.dependency 'FirebasePerformance', '~> 12.1.0' + ss.ios.dependency 'FirebasePerformance', '~> 12.2.0' + ss.tvos.dependency 'FirebasePerformance', '~> 12.2.0' ss.ios.deployment_target = '15.0' ss.tvos.deployment_target = '15.0' end s.subspec 'RemoteConfig' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseRemoteConfig', '~> 12.1.0' + ss.dependency 'FirebaseRemoteConfig', '~> 12.2.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '15.0' ss.osx.deployment_target = '10.15' @@ -194,7 +194,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'Storage' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseStorage', '~> 12.1.0' + ss.dependency 'FirebaseStorage', '~> 12.2.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '15.0' ss.osx.deployment_target = '10.15' diff --git a/FirebaseABTesting.podspec b/FirebaseABTesting.podspec index ea2ec8c25a2..d6cfb519ae4 100644 --- a/FirebaseABTesting.podspec +++ b/FirebaseABTesting.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseABTesting' - s.version = '12.1.0' + s.version = '12.2.0' s.summary = 'Firebase ABTesting' s.description = <<-DESC @@ -51,7 +51,7 @@ Firebase Cloud Messaging and Firebase Remote Config in your app. s.pod_target_xcconfig = { 'HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}"' } - s.dependency 'FirebaseCore', '~> 12.1.0' + s.dependency 'FirebaseCore', '~> 12.2.0' s.test_spec 'unit' do |unit_tests| unit_tests.scheme = { :code_coverage => true } diff --git a/FirebaseAI.podspec b/FirebaseAI.podspec index 24c91ff90ac..9b5787bf038 100644 --- a/FirebaseAI.podspec +++ b/FirebaseAI.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAI' - s.version = '12.1.0' + s.version = '12.2.0' s.summary = 'Firebase AI SDK' s.description = <<-DESC @@ -43,10 +43,10 @@ Build AI-powered apps and features with the Gemini API using the Firebase AI SDK s.tvos.framework = 'UIKit' s.watchos.framework = 'WatchKit' - s.dependency 'FirebaseAppCheckInterop', '~> 12.1.0' - s.dependency 'FirebaseAuthInterop', '~> 12.1.0' - s.dependency 'FirebaseCore', '~> 12.1.0' - s.dependency 'FirebaseCoreExtension', '~> 12.1.0' + s.dependency 'FirebaseAppCheckInterop', '~> 12.2.0' + s.dependency 'FirebaseAuthInterop', '~> 12.2.0' + s.dependency 'FirebaseCore', '~> 12.2.0' + s.dependency 'FirebaseCoreExtension', '~> 12.2.0' s.test_spec 'unit' do |unit_tests| unit_tests_dir = 'FirebaseAI/Tests/Unit/' diff --git a/FirebaseAI/CHANGELOG.md b/FirebaseAI/CHANGELOG.md index 47b2627da67..b5f3199a797 100644 --- a/FirebaseAI/CHANGELOG.md +++ b/FirebaseAI/CHANGELOG.md @@ -1,5 +1,16 @@ +# 12.2.0 +- [feature] Added support for returning thought summaries, which are synthesized + versions of a model's internal reasoning process. (#15096) +- [feature] Added a new configuration option to use limited-use App + Check tokens for attesting Firebase AI Logic requests. This enhances + security against replay attacks. To use this feature, configure it + explicitly via the new `useLimitedUseAppCheckTokens` parameter when + initializing `FirebaseAI`. We recommend migrating to limited-use + tokens now, so your app will be ready to take advantage of replay + protection when it becomes available for Firebase AI Logic. (#15099) + # 12.0.0 -- [added] Added support for Grounding with Google Search. (#15014) +- [feature] Added support for Grounding with Google Search. (#15014) - [removed] Removed `CountTokensResponse.totalBillableCharacters` which was deprecated in 11.15.0. Use `totalTokens` instead. (#15056) diff --git a/FirebaseAI/Sources/Chat.swift b/FirebaseAI/Sources/Chat.swift index 42da2ef4a6d..80e908a8f57 100644 --- a/FirebaseAI/Sources/Chat.swift +++ b/FirebaseAI/Sources/Chat.swift @@ -147,31 +147,48 @@ public final class Chat: Sendable { } private func aggregatedChunks(_ chunks: [ModelContent]) -> ModelContent { - var parts: [any Part] = [] + var parts: [InternalPart] = [] var combinedText = "" - for aggregate in chunks { - // Loop through all the parts, aggregating the text and adding the images. - for part in aggregate.parts { - switch part { - case let textPart as TextPart: - combinedText += textPart.text - - default: - // Don't combine it, just add to the content. If there's any text pending, add that as - // a part. + var combinedThoughts = "" + + func flush() { + if !combinedThoughts.isEmpty { + parts.append(InternalPart(.text(combinedThoughts), isThought: true, thoughtSignature: nil)) + combinedThoughts = "" + } + if !combinedText.isEmpty { + parts.append(InternalPart(.text(combinedText), isThought: nil, thoughtSignature: nil)) + combinedText = "" + } + } + + // Loop through all the parts, aggregating the text. + for part in chunks.flatMap({ $0.internalParts }) { + // Only text parts may be combined. + if case let .text(text) = part.data, part.thoughtSignature == nil { + // Thought summaries must not be combined with regular text. + if part.isThought ?? false { + // If we were combining regular text, flush it before handling "thoughts". if !combinedText.isEmpty { - parts.append(TextPart(combinedText)) - combinedText = "" + flush() } - - parts.append(part) + combinedThoughts += text + } else { + // If we were combining "thoughts", flush it before handling regular text. + if !combinedThoughts.isEmpty { + flush() + } + combinedText += text } + } else { + // This is a non-combinable part (not text), flush any pending text. + flush() + parts.append(part) } } - if !combinedText.isEmpty { - parts.append(TextPart(combinedText)) - } + // Flush any remaining text. + flush() return ModelContent(role: "model", parts: parts) } diff --git a/FirebaseAI/Sources/FirebaseAI.swift b/FirebaseAI/Sources/FirebaseAI.swift index 48f7183d4e6..d4c2e9d545d 100644 --- a/FirebaseAI/Sources/FirebaseAI.swift +++ b/FirebaseAI/Sources/FirebaseAI.swift @@ -32,13 +32,33 @@ public final class FirebaseAI: Sendable { /// ``FirebaseApp``. /// - backend: The backend API for the Firebase AI SDK; if not specified, uses the default /// ``Backend/googleAI()`` (Gemini Developer API). + /// - useLimitedUseAppCheckTokens: When sending tokens to the backend, this option enables + /// the usage of App Check's limited-use tokens instead of the standard cached tokens. + /// + /// A new limited-use tokens will be generated for each request; providing a smaller attack + /// surface for malicious parties to hijack tokens. When used alongside replay protection, + /// limited-use tokens are also _consumed_ after each request, ensuring they can't be used + /// again. + /// + /// _This flag is set to `false` by default._ + /// + /// > Important: Replay protection is not currently supported for the FirebaseAI backend. + /// > While this feature is being developed, you can still migrate to using + /// > limited-use tokens. Because limited-use tokens are backwards compatible, you can still + /// > use them without replay protection. Due to their shorter TTL over standard App Check + /// > tokens, they still provide a security benefit. + /// > + /// > Migrating to limited-use tokens sooner minimizes disruption when support for replay + /// > protection is added. /// - Returns: A `FirebaseAI` instance, configured with the custom `FirebaseApp`. public static func firebaseAI(app: FirebaseApp? = nil, - backend: Backend = .googleAI()) -> FirebaseAI { + backend: Backend = .googleAI(), + useLimitedUseAppCheckTokens: Bool = false) -> FirebaseAI { let instance = createInstance( app: app, location: backend.location, - apiConfig: backend.apiConfig + apiConfig: backend.apiConfig, + useLimitedUseAppCheckTokens: useLimitedUseAppCheckTokens ) // Verify that the `FirebaseAI` instance is always configured with the production endpoint since // this is the public API surface for creating an instance. @@ -90,7 +110,8 @@ public final class FirebaseAI: Sendable { tools: tools, toolConfig: toolConfig, systemInstruction: systemInstruction, - requestOptions: requestOptions + requestOptions: requestOptions, + useLimitedUseAppCheckTokens: useLimitedUseAppCheckTokens ) } @@ -126,7 +147,8 @@ public final class FirebaseAI: Sendable { apiConfig: apiConfig, generationConfig: generationConfig, safetySettings: safetySettings, - requestOptions: requestOptions + requestOptions: requestOptions, + useLimitedUseAppCheckTokens: useLimitedUseAppCheckTokens ) } @@ -141,6 +163,8 @@ public final class FirebaseAI: Sendable { let apiConfig: APIConfig + let useLimitedUseAppCheckTokens: Bool + /// A map of active `FirebaseAI` instances keyed by the `FirebaseApp` name and the `location`, /// in the format `appName:location`. private nonisolated(unsafe) static var instances: [InstanceKey: FirebaseAI] = [:] @@ -156,7 +180,8 @@ public final class FirebaseAI: Sendable { ) static func createInstance(app: FirebaseApp?, location: String?, - apiConfig: APIConfig) -> FirebaseAI { + apiConfig: APIConfig, + useLimitedUseAppCheckTokens: Bool) -> FirebaseAI { guard let app = app ?? FirebaseApp.app() else { fatalError("No instance of the default Firebase app was found.") } @@ -166,16 +191,27 @@ public final class FirebaseAI: Sendable { // Unlock before the function returns. defer { os_unfair_lock_unlock(&instancesLock) } - let instanceKey = InstanceKey(appName: app.name, location: location, apiConfig: apiConfig) + let instanceKey = InstanceKey( + appName: app.name, + location: location, + apiConfig: apiConfig, + useLimitedUseAppCheckTokens: useLimitedUseAppCheckTokens + ) if let instance = instances[instanceKey] { return instance } - let newInstance = FirebaseAI(app: app, location: location, apiConfig: apiConfig) + let newInstance = FirebaseAI( + app: app, + location: location, + apiConfig: apiConfig, + useLimitedUseAppCheckTokens: useLimitedUseAppCheckTokens + ) instances[instanceKey] = newInstance return newInstance } - init(app: FirebaseApp, location: String?, apiConfig: APIConfig) { + init(app: FirebaseApp, location: String?, apiConfig: APIConfig, + useLimitedUseAppCheckTokens: Bool) { guard let projectID = app.options.projectID else { fatalError("The Firebase app named \"\(app.name)\" has no project ID in its configuration.") } @@ -195,6 +231,7 @@ public final class FirebaseAI: Sendable { ) self.apiConfig = apiConfig self.location = location + self.useLimitedUseAppCheckTokens = useLimitedUseAppCheckTokens } func modelResourceName(modelName: String) -> String { @@ -249,5 +286,6 @@ public final class FirebaseAI: Sendable { let appName: String let location: String? let apiConfig: APIConfig + let useLimitedUseAppCheckTokens: Bool } } diff --git a/FirebaseAI/Sources/GenerateContentResponse.swift b/FirebaseAI/Sources/GenerateContentResponse.swift index 1cc9874e795..0756d2afd9a 100644 --- a/FirebaseAI/Sources/GenerateContentResponse.swift +++ b/FirebaseAI/Sources/GenerateContentResponse.swift @@ -57,30 +57,19 @@ public struct GenerateContentResponse: Sendable { public let usageMetadata: UsageMetadata? /// The response's content as text, if it exists. + /// + /// - Note: This does not include thought summaries; see ``thoughtSummary`` for more details. public var text: String? { - guard let candidate = candidates.first else { - AILog.error( - code: .generateContentResponseNoCandidates, - "Could not get text from a response that had no candidates." - ) - return nil - } - let textValues: [String] = candidate.content.parts.compactMap { part in - switch part { - case let textPart as TextPart: - return textPart.text - default: - return nil - } - } - guard textValues.count > 0 else { - AILog.error( - code: .generateContentResponseNoText, - "Could not get a text part from the first candidate." - ) - return nil - } - return textValues.joined(separator: " ") + return text(isThought: false) + } + + /// A summary of the model's thinking process, if available. + /// + /// - Important: Thought summaries are only available when `includeThoughts` is enabled in the + /// ``ThinkingConfig``. For more information, see the + /// [Thinking](https://firebase.google.com/docs/ai-logic/thinking) documentation. + public var thoughtSummary: String? { + return text(isThought: true) } /// Returns function calls found in any `Part`s of the first candidate of the response, if any. @@ -89,12 +78,10 @@ public struct GenerateContentResponse: Sendable { return [] } return candidate.content.parts.compactMap { part in - switch part { - case let functionCallPart as FunctionCallPart: - return functionCallPart - default: + guard let functionCallPart = part as? FunctionCallPart, !part.isThought else { return nil } + return functionCallPart } } @@ -107,7 +94,12 @@ public struct GenerateContentResponse: Sendable { """) return [] } - return candidate.content.parts.compactMap { $0 as? InlineDataPart } + return candidate.content.parts.compactMap { part in + guard let inlineDataPart = part as? InlineDataPart, !part.isThought else { + return nil + } + return inlineDataPart + } } /// Initializer for SwiftUI previews or tests. @@ -117,6 +109,30 @@ public struct GenerateContentResponse: Sendable { self.promptFeedback = promptFeedback self.usageMetadata = usageMetadata } + + func text(isThought: Bool) -> String? { + guard let candidate = candidates.first else { + AILog.error( + code: .generateContentResponseNoCandidates, + "Could not get text from a response that had no candidates." + ) + return nil + } + let textValues: [String] = candidate.content.parts.compactMap { part in + guard let textPart = part as? TextPart, part.isThought == isThought else { + return nil + } + return textPart.text + } + guard textValues.count > 0 else { + AILog.error( + code: .generateContentResponseNoText, + "Could not get a text part from the first candidate." + ) + return nil + } + return textValues.joined(separator: " ") + } } /// A struct representing a possible reply to a content generation prompt. Each content generation diff --git a/FirebaseAI/Sources/GenerativeAIService.swift b/FirebaseAI/Sources/GenerativeAIService.swift index e1538af997f..5b676162c94 100644 --- a/FirebaseAI/Sources/GenerativeAIService.swift +++ b/FirebaseAI/Sources/GenerativeAIService.swift @@ -30,9 +30,12 @@ struct GenerativeAIService { private let urlSession: URLSession - init(firebaseInfo: FirebaseInfo, urlSession: URLSession) { + private let useLimitedUseAppCheckTokens: Bool + + init(firebaseInfo: FirebaseInfo, urlSession: URLSession, useLimitedUseAppCheckTokens: Bool) { self.firebaseInfo = firebaseInfo self.urlSession = urlSession + self.useLimitedUseAppCheckTokens = useLimitedUseAppCheckTokens } func loadRequest(request: T) async throws -> T.Response { @@ -177,7 +180,7 @@ struct GenerativeAIService { urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") if let appCheck = firebaseInfo.appCheck { - let tokenResult = await appCheck.getToken(forcingRefresh: false) + let tokenResult = try await fetchAppCheckToken(appCheck: appCheck) urlRequest.setValue(tokenResult.token, forHTTPHeaderField: "X-Firebase-AppCheck") if let error = tokenResult.error { AILog.error( @@ -207,6 +210,53 @@ struct GenerativeAIService { return urlRequest } + private func fetchAppCheckToken(appCheck: AppCheckInterop) async throws + -> FIRAppCheckTokenResultInterop { + if useLimitedUseAppCheckTokens { + if let token = await getLimitedUseAppCheckToken(appCheck: appCheck) { + return token + } + + let errorMessage = + "The provided App Check token provider doesn't implement getLimitedUseToken(), but requireLimitedUseTokens was enabled." + + #if Debug + fatalError(errorMessage) + #else + throw NSError( + domain: "\(Constants.baseErrorDomain).\(Self.self)", + code: AILog.MessageCode.appCheckTokenFetchFailed.rawValue, + userInfo: [NSLocalizedDescriptionKey: errorMessage] + ) + #endif + } + + return await appCheck.getToken(forcingRefresh: false) + } + + private func getLimitedUseAppCheckToken(appCheck: AppCheckInterop) async + -> FIRAppCheckTokenResultInterop? { + // At the moment, `await` doesn’t get along with Objective-C’s optional protocol methods. + await withCheckedContinuation { (continuation: CheckedContinuation< + FIRAppCheckTokenResultInterop?, + Never + >) in + guard + useLimitedUseAppCheckTokens, + // `getLimitedUseToken(completion:)` is an optional protocol method. Optional binding + // is performed to make sure `continuation` is called even if the method’s not implemented. + let limitedUseTokenClosure = appCheck.getLimitedUseToken + else { + return continuation.resume(returning: nil) + } + + limitedUseTokenClosure { tokenResult in + // The placeholder token should be used in the case of App Check error. + continuation.resume(returning: tokenResult) + } + } + } + private func httpResponse(urlResponse: URLResponse) throws -> HTTPURLResponse { // The following condition should always be true: "Whenever you make HTTP URL load requests, any // response objects you get back from the URLSession, NSURLConnection, or NSURLDownload class diff --git a/FirebaseAI/Sources/GenerativeModel.swift b/FirebaseAI/Sources/GenerativeModel.swift index 8d3f5e043a7..2807022de59 100644 --- a/FirebaseAI/Sources/GenerativeModel.swift +++ b/FirebaseAI/Sources/GenerativeModel.swift @@ -76,6 +76,8 @@ public final class GenerativeModel: Sendable { /// only text content is supported. /// - requestOptions: Configuration parameters for sending requests to the backend. /// - urlSession: The `URLSession` to use for requests; defaults to `URLSession.shared`. + /// - useLimitedUseAppCheckTokens: Use App Check's limited-use tokens instead of the standard + /// cached tokens. init(modelName: String, modelResourceName: String, firebaseInfo: FirebaseInfo, @@ -86,13 +88,15 @@ public final class GenerativeModel: Sendable { toolConfig: ToolConfig? = nil, systemInstruction: ModelContent? = nil, requestOptions: RequestOptions, - urlSession: URLSession = GenAIURLSession.default) { + urlSession: URLSession = GenAIURLSession.default, + useLimitedUseAppCheckTokens: Bool = false) { self.modelName = modelName self.modelResourceName = modelResourceName self.apiConfig = apiConfig generativeAIService = GenerativeAIService( firebaseInfo: firebaseInfo, - urlSession: urlSession + urlSession: urlSession, + useLimitedUseAppCheckTokens: useLimitedUseAppCheckTokens ) self.generationConfig = generationConfig self.safetySettings = safetySettings diff --git a/FirebaseAI/Sources/ModelContent.swift b/FirebaseAI/Sources/ModelContent.swift index 7d82bd76445..1a0aa6f5f09 100644 --- a/FirebaseAI/Sources/ModelContent.swift +++ b/FirebaseAI/Sources/ModelContent.swift @@ -31,41 +31,62 @@ extension [ModelContent] { } } -/// A type describing data in media formats interpretable by an AI model. Each generative AI -/// request or response contains an `Array` of ``ModelContent``s, and each ``ModelContent`` value -/// may comprise multiple heterogeneous ``Part``s. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -public struct ModelContent: Equatable, Sendable { - enum InternalPart: Equatable, Sendable { +struct InternalPart: Equatable, Sendable { + enum OneOfData: Equatable, Sendable { case text(String) - case inlineData(mimetype: String, Data) - case fileData(mimetype: String, uri: String) + case inlineData(InlineData) + case fileData(FileData) case functionCall(FunctionCall) case functionResponse(FunctionResponse) } + let data: OneOfData + + let isThought: Bool? + + let thoughtSignature: String? + + init(_ data: OneOfData, isThought: Bool?, thoughtSignature: String?) { + self.data = data + self.isThought = isThought + self.thoughtSignature = thoughtSignature + } +} + +/// A type describing data in media formats interpretable by an AI model. Each generative AI +/// request or response contains an `Array` of ``ModelContent``s, and each ``ModelContent`` value +/// may comprise multiple heterogeneous ``Part``s. +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +public struct ModelContent: Equatable, Sendable { /// The role of the entity creating the ``ModelContent``. For user-generated client requests, /// for example, the role is `user`. public let role: String? /// The data parts comprising this ``ModelContent`` value. public var parts: [any Part] { - var convertedParts = [any Part]() - for part in internalParts { - switch part { + return internalParts.map { part -> any Part in + switch part.data { case let .text(text): - convertedParts.append(TextPart(text)) - case let .inlineData(mimetype, data): - convertedParts.append(InlineDataPart(data: data, mimeType: mimetype)) - case let .fileData(mimetype, uri): - convertedParts.append(FileDataPart(uri: uri, mimeType: mimetype)) + return TextPart(text, isThought: part.isThought, thoughtSignature: part.thoughtSignature) + case let .inlineData(inlineData): + return InlineDataPart( + inlineData, isThought: part.isThought, thoughtSignature: part.thoughtSignature + ) + case let .fileData(fileData): + return FileDataPart( + fileData, isThought: part.isThought, thoughtSignature: part.thoughtSignature + ) case let .functionCall(functionCall): - convertedParts.append(FunctionCallPart(functionCall)) + return FunctionCallPart( + functionCall, isThought: part.isThought, thoughtSignature: part.thoughtSignature + ) case let .functionResponse(functionResponse): - convertedParts.append(FunctionResponsePart(functionResponse)) + return FunctionResponsePart( + functionResponse, isThought: part.isThought, thoughtSignature: part.thoughtSignature + ) } } - return convertedParts } // TODO: Refactor this @@ -78,17 +99,35 @@ public struct ModelContent: Equatable, Sendable { for part in parts { switch part { case let textPart as TextPart: - convertedParts.append(.text(textPart.text)) + convertedParts.append(InternalPart( + .text(textPart.text), + isThought: textPart._isThought, + thoughtSignature: textPart.thoughtSignature + )) case let inlineDataPart as InlineDataPart: - let inlineData = inlineDataPart.inlineData - convertedParts.append(.inlineData(mimetype: inlineData.mimeType, inlineData.data)) + convertedParts.append(InternalPart( + .inlineData(inlineDataPart.inlineData), + isThought: inlineDataPart._isThought, + thoughtSignature: inlineDataPart.thoughtSignature + )) case let fileDataPart as FileDataPart: - let fileData = fileDataPart.fileData - convertedParts.append(.fileData(mimetype: fileData.mimeType, uri: fileData.fileURI)) + convertedParts.append(InternalPart( + .fileData(fileDataPart.fileData), + isThought: fileDataPart._isThought, + thoughtSignature: fileDataPart.thoughtSignature + )) case let functionCallPart as FunctionCallPart: - convertedParts.append(.functionCall(functionCallPart.functionCall)) + convertedParts.append(InternalPart( + .functionCall(functionCallPart.functionCall), + isThought: functionCallPart._isThought, + thoughtSignature: functionCallPart.thoughtSignature + )) case let functionResponsePart as FunctionResponsePart: - convertedParts.append(.functionResponse(functionResponsePart.functionResponse)) + convertedParts.append(InternalPart( + .functionResponse(functionResponsePart.functionResponse), + isThought: functionResponsePart._isThought, + thoughtSignature: functionResponsePart.thoughtSignature + )) default: fatalError() } @@ -102,6 +141,11 @@ public struct ModelContent: Equatable, Sendable { let content = parts.flatMap { $0.partsValue } self.init(role: role, parts: content) } + + init(role: String?, parts: [InternalPart]) { + self.role = role + internalParts = parts + } } // MARK: Codable Conformances @@ -121,7 +165,29 @@ extension ModelContent: Codable { } @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -extension ModelContent.InternalPart: Codable { +extension InternalPart: Codable { + enum CodingKeys: String, CodingKey { + case isThought = "thought" + case thoughtSignature + } + + public func encode(to encoder: Encoder) throws { + try data.encode(to: encoder) + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(isThought, forKey: .isThought) + try container.encodeIfPresent(thoughtSignature, forKey: .thoughtSignature) + } + + public init(from decoder: Decoder) throws { + data = try OneOfData(from: decoder) + let container = try decoder.container(keyedBy: CodingKeys.self) + isThought = try container.decodeIfPresent(Bool.self, forKey: .isThought) + thoughtSignature = try container.decodeIfPresent(String.self, forKey: .thoughtSignature) + } +} + +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +extension InternalPart.OneOfData: Codable { enum CodingKeys: String, CodingKey { case text case inlineData @@ -135,10 +201,10 @@ extension ModelContent.InternalPart: Codable { switch self { case let .text(text): try container.encode(text, forKey: .text) - case let .inlineData(mimetype, bytes): - try container.encode(InlineData(data: bytes, mimeType: mimetype), forKey: .inlineData) - case let .fileData(mimetype: mimetype, url): - try container.encode(FileData(fileURI: url, mimeType: mimetype), forKey: .fileData) + case let .inlineData(inlineData): + try container.encode(inlineData, forKey: .inlineData) + case let .fileData(fileData): + try container.encode(fileData, forKey: .fileData) case let .functionCall(functionCall): try container.encode(functionCall, forKey: .functionCall) case let .functionResponse(functionResponse): @@ -151,11 +217,9 @@ extension ModelContent.InternalPart: Codable { if values.contains(.text) { self = try .text(values.decode(String.self, forKey: .text)) } else if values.contains(.inlineData) { - let inlineData = try values.decode(InlineData.self, forKey: .inlineData) - self = .inlineData(mimetype: inlineData.mimeType, inlineData.data) + self = try .inlineData(values.decode(InlineData.self, forKey: .inlineData)) } else if values.contains(.fileData) { - let fileData = try values.decode(FileData.self, forKey: .fileData) - self = .fileData(mimetype: fileData.mimeType, uri: fileData.fileURI) + self = try .fileData(values.decode(FileData.self, forKey: .fileData)) } else if values.contains(.functionCall) { self = try .functionCall(values.decode(FunctionCall.self, forKey: .functionCall)) } else if values.contains(.functionResponse) { diff --git a/FirebaseAI/Sources/Types/Internal/Imagen/ImageGenerationParameters.swift b/FirebaseAI/Sources/Types/Internal/Imagen/ImageGenerationParameters.swift index 4189e5fbac7..aa1e1b085c8 100644 --- a/FirebaseAI/Sources/Types/Internal/Imagen/ImageGenerationParameters.swift +++ b/FirebaseAI/Sources/Types/Internal/Imagen/ImageGenerationParameters.swift @@ -23,6 +23,7 @@ struct ImageGenerationParameters { let outputOptions: ImageGenerationOutputOptions? let addWatermark: Bool? let includeResponsibleAIFilterReason: Bool? + let includeSafetyAttributes: Bool? } @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) @@ -42,6 +43,7 @@ extension ImageGenerationParameters: Encodable { case outputOptions case addWatermark case includeResponsibleAIFilterReason = "includeRaiReason" + case includeSafetyAttributes } func encode(to encoder: any Encoder) throws { @@ -58,5 +60,6 @@ extension ImageGenerationParameters: Encodable { includeResponsibleAIFilterReason, forKey: .includeResponsibleAIFilterReason ) + try container.encodeIfPresent(includeSafetyAttributes, forKey: .includeSafetyAttributes) } } diff --git a/FirebaseAI/Sources/Types/Internal/Imagen/ImagenSafetyAttributes.swift b/FirebaseAI/Sources/Types/Internal/Imagen/ImagenSafetyAttributes.swift new file mode 100644 index 00000000000..3dcb93d544a --- /dev/null +++ b/FirebaseAI/Sources/Types/Internal/Imagen/ImagenSafetyAttributes.swift @@ -0,0 +1,24 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Foundation + +/// A `safetyAttributes` "prediction" from Imagen. +/// +/// This prediction is currently unused by the SDK and is only checked to be valid JSON. This type +/// is currently only used to avoid logging unsupported prediction types. +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +struct ImagenSafetyAttributes: Decodable { + let safetyAttributes: JSONObject +} diff --git a/FirebaseAI/Sources/Types/Internal/InternalPart.swift b/FirebaseAI/Sources/Types/Internal/InternalPart.swift index d543fb80f38..bb62dd4c0b5 100644 --- a/FirebaseAI/Sources/Types/Internal/InternalPart.swift +++ b/FirebaseAI/Sources/Types/Internal/InternalPart.swift @@ -67,6 +67,9 @@ struct FunctionResponse: Codable, Equatable, Sendable { struct ErrorPart: Part, Error { let error: Error + let isThought = false + let thoughtSignature: String? = nil + init(_ error: Error) { self.error = error } diff --git a/FirebaseAI/Sources/Types/Public/Imagen/ImagenGenerationResponse.swift b/FirebaseAI/Sources/Types/Public/Imagen/ImagenGenerationResponse.swift index f9816908c6d..9ed52c4d0e9 100644 --- a/FirebaseAI/Sources/Types/Public/Imagen/ImagenGenerationResponse.swift +++ b/FirebaseAI/Sources/Types/Public/Imagen/ImagenGenerationResponse.swift @@ -60,6 +60,8 @@ extension ImagenGenerationResponse: Decodable where T: Decodable { images.append(image) } else if let filteredReason = try? predictionsContainer.decode(RAIFilteredReason.self) { filteredReasons.append(filteredReason.raiFilteredReason) + } else if let _ = try? predictionsContainer.decode(ImagenSafetyAttributes.self) { + // Ignore SafetyAttributes "prediction" to avoid logging in `unsupportedPrediction` below. } else if let unsupportedPrediction = try? predictionsContainer.decode(JSONObject.self) { AILog.warning( code: .decodedUnsupportedImagenPredictionType, diff --git a/FirebaseAI/Sources/Types/Public/Imagen/ImagenModel.swift b/FirebaseAI/Sources/Types/Public/Imagen/ImagenModel.swift index e6f96df511a..542f842362c 100644 --- a/FirebaseAI/Sources/Types/Public/Imagen/ImagenModel.swift +++ b/FirebaseAI/Sources/Types/Public/Imagen/ImagenModel.swift @@ -53,12 +53,14 @@ public final class ImagenModel { generationConfig: ImagenGenerationConfig?, safetySettings: ImagenSafetySettings?, requestOptions: RequestOptions, - urlSession: URLSession = GenAIURLSession.default) { + urlSession: URLSession = GenAIURLSession.default, + useLimitedUseAppCheckTokens: Bool = false) { self.modelResourceName = modelResourceName self.apiConfig = apiConfig generativeAIService = GenerativeAIService( firebaseInfo: firebaseInfo, - urlSession: urlSession + urlSession: urlSession, + useLimitedUseAppCheckTokens: useLimitedUseAppCheckTokens ) self.generationConfig = generationConfig self.safetySettings = safetySettings @@ -159,7 +161,8 @@ public final class ImagenModel { ) }, addWatermark: generationConfig?.addWatermark, - includeResponsibleAIFilterReason: true + includeResponsibleAIFilterReason: true, + includeSafetyAttributes: true ) } } diff --git a/FirebaseAI/Sources/Types/Public/Part.swift b/FirebaseAI/Sources/Types/Public/Part.swift index 4890b725f4d..fb743d1025d 100644 --- a/FirebaseAI/Sources/Types/Public/Part.swift +++ b/FirebaseAI/Sources/Types/Public/Part.swift @@ -18,7 +18,14 @@ import Foundation /// /// Within a single value of ``Part``, different data types may not mix. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) -public protocol Part: PartsRepresentable, Codable, Sendable, Equatable {} +public protocol Part: PartsRepresentable, Codable, Sendable, Equatable { + /// Indicates whether this `Part` is a summary of the model's internal thinking process. + /// + /// When `includeThoughts` is set to `true` in ``ThinkingConfig``, the model may return one or + /// more "thought" parts that provide insight into how it reasoned through the prompt to arrive + /// at the final answer. These parts will have `isThought` set to `true`. + var isThought: Bool { get } +} /// A text part containing a string value. @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) @@ -26,8 +33,20 @@ public struct TextPart: Part { /// Text value. public let text: String + public var isThought: Bool { _isThought ?? false } + + let thoughtSignature: String? + + let _isThought: Bool? + public init(_ text: String) { + self.init(text, isThought: nil, thoughtSignature: nil) + } + + init(_ text: String, isThought: Bool?, thoughtSignature: String?) { self.text = text + _isThought = isThought + self.thoughtSignature = thoughtSignature } } @@ -45,6 +64,7 @@ public struct TextPart: Part { @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct InlineDataPart: Part { let inlineData: InlineData + let _isThought: Bool? /// The data provided in the inline data part. public var data: Data { inlineData.data } @@ -52,6 +72,10 @@ public struct InlineDataPart: Part { /// The IANA standard MIME type of the data. public var mimeType: String { inlineData.mimeType } + public var isThought: Bool { _isThought ?? false } + + let thoughtSignature: String? + /// Creates an inline data part from data and a MIME type. /// /// > Important: Supported input types depend on the model on the model being used; see [input @@ -67,11 +91,13 @@ public struct InlineDataPart: Part { /// requirements](https://firebase.google.com/docs/vertex-ai/input-file-requirements) for /// supported values. public init(data: Data, mimeType: String) { - self.init(InlineData(data: data, mimeType: mimeType)) + self.init(InlineData(data: data, mimeType: mimeType), isThought: nil, thoughtSignature: nil) } - init(_ inlineData: InlineData) { + init(_ inlineData: InlineData, isThought: Bool?, thoughtSignature: String?) { self.inlineData = inlineData + _isThought = isThought + self.thoughtSignature = thoughtSignature } } @@ -79,9 +105,12 @@ public struct InlineDataPart: Part { @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct FileDataPart: Part { let fileData: FileData + let _isThought: Bool? + let thoughtSignature: String? public var uri: String { fileData.fileURI } public var mimeType: String { fileData.mimeType } + public var isThought: Bool { _isThought ?? false } /// Constructs a new file data part. /// @@ -93,11 +122,13 @@ public struct FileDataPart: Part { /// requirements](https://firebase.google.com/docs/vertex-ai/input-file-requirements) for /// supported values. public init(uri: String, mimeType: String) { - self.init(FileData(fileURI: uri, mimeType: mimeType)) + self.init(FileData(fileURI: uri, mimeType: mimeType), isThought: nil, thoughtSignature: nil) } - init(_ fileData: FileData) { + init(_ fileData: FileData, isThought: Bool?, thoughtSignature: String?) { self.fileData = fileData + _isThought = isThought + self.thoughtSignature = thoughtSignature } } @@ -105,6 +136,8 @@ public struct FileDataPart: Part { @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct FunctionCallPart: Part { let functionCall: FunctionCall + let _isThought: Bool? + let thoughtSignature: String? /// The name of the function to call. public var name: String { functionCall.name } @@ -112,6 +145,8 @@ public struct FunctionCallPart: Part { /// The function parameters and values. public var args: JSONObject { functionCall.args } + public var isThought: Bool { _isThought ?? false } + /// Constructs a new function call part. /// /// > Note: A `FunctionCallPart` is typically received from the model, rather than created @@ -121,11 +156,13 @@ public struct FunctionCallPart: Part { /// - name: The name of the function to call. /// - args: The function parameters and values. public init(name: String, args: JSONObject) { - self.init(FunctionCall(name: name, args: args)) + self.init(FunctionCall(name: name, args: args), isThought: nil, thoughtSignature: nil) } - init(_ functionCall: FunctionCall) { + init(_ functionCall: FunctionCall, isThought: Bool?, thoughtSignature: String?) { self.functionCall = functionCall + _isThought = isThought + self.thoughtSignature = thoughtSignature } } @@ -137,6 +174,8 @@ public struct FunctionCallPart: Part { @available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) public struct FunctionResponsePart: Part { let functionResponse: FunctionResponse + let _isThought: Bool? + let thoughtSignature: String? /// The name of the function that was called. public var name: String { functionResponse.name } @@ -144,16 +183,22 @@ public struct FunctionResponsePart: Part { /// The function's response or return value. public var response: JSONObject { functionResponse.response } + public var isThought: Bool { _isThought ?? false } + /// Constructs a new `FunctionResponse`. /// /// - Parameters: /// - name: The name of the function that was called. /// - response: The function's response. public init(name: String, response: JSONObject) { - self.init(FunctionResponse(name: name, response: response)) + self.init( + FunctionResponse(name: name, response: response), isThought: nil, thoughtSignature: nil + ) } - init(_ functionResponse: FunctionResponse) { + init(_ functionResponse: FunctionResponse, isThought: Bool?, thoughtSignature: String?) { self.functionResponse = functionResponse + _isThought = isThought + self.thoughtSignature = thoughtSignature } } diff --git a/FirebaseAI/Sources/Types/Public/ThinkingConfig.swift b/FirebaseAI/Sources/Types/Public/ThinkingConfig.swift index c0e8f31465b..a339f8fa1d1 100644 --- a/FirebaseAI/Sources/Types/Public/ThinkingConfig.swift +++ b/FirebaseAI/Sources/Types/Public/ThinkingConfig.swift @@ -37,12 +37,24 @@ public struct ThinkingConfig: Sendable { /// feature or if the specified budget is not within the model's supported range. let thinkingBudget: Int? + /// Whether summaries of the model's "thoughts" are included in responses. + /// + /// When `includeThoughts` is set to `true`, the model will return a summary of its internal + /// thinking process alongside the final answer. This can provide valuable insight into how the + /// model arrived at its conclusion, which is particularly useful for complex or creative tasks. + /// + /// If you don't specify a value for `includeThoughts` (`nil`), the model will use its default + /// behavior (which is typically to not include thought summaries). + let includeThoughts: Bool? + /// Initializes a new `ThinkingConfig`. /// /// - Parameters: /// - thinkingBudget: The maximum number of tokens to be used for the model's thinking process. - public init(thinkingBudget: Int? = nil) { + /// - includeThoughts: If true, summaries of the model's "thoughts" are included in responses. + public init(thinkingBudget: Int? = nil, includeThoughts: Bool? = nil) { self.thinkingBudget = thinkingBudget + self.includeThoughts = includeThoughts } } diff --git a/FirebaseAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift b/FirebaseAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift index d83c300623d..ef0f19be217 100644 --- a/FirebaseAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift +++ b/FirebaseAI/Tests/TestApp/Tests/Integration/GenerateContentIntegrationTests.swift @@ -50,13 +50,18 @@ struct GenerateContentIntegrationTests { @Test(arguments: [ (InstanceConfig.vertexAI_v1beta, ModelNames.gemini2FlashLite), (InstanceConfig.vertexAI_v1beta_global, ModelNames.gemini2FlashLite), - (InstanceConfig.vertexAI_v1beta_staging, ModelNames.gemini2FlashLite), + (InstanceConfig.vertexAI_v1beta_global_appCheckLimitedUse, ModelNames.gemini2FlashLite), (InstanceConfig.googleAI_v1beta, ModelNames.gemini2FlashLite), + (InstanceConfig.googleAI_v1beta_appCheckLimitedUse, ModelNames.gemini2FlashLite), (InstanceConfig.googleAI_v1beta, ModelNames.gemma3_4B), - (InstanceConfig.googleAI_v1beta_staging, ModelNames.gemini2FlashLite), - (InstanceConfig.googleAI_v1beta_staging, ModelNames.gemma3_4B), - (InstanceConfig.googleAI_v1beta_freeTier_bypassProxy, ModelNames.gemini2FlashLite), - (InstanceConfig.googleAI_v1beta_freeTier_bypassProxy, ModelNames.gemma3_4B), + (InstanceConfig.googleAI_v1beta_freeTier, ModelNames.gemini2FlashLite), + (InstanceConfig.googleAI_v1beta_freeTier, ModelNames.gemma3_4B), + // Note: The following configs are commented out for easy one-off manual testing. + // (InstanceConfig.googleAI_v1beta_staging, ModelNames.gemini2FlashLite), + // (InstanceConfig.googleAI_v1beta_staging, ModelNames.gemma3_4B), + // (InstanceConfig.vertexAI_v1beta_staging, ModelNames.gemini2FlashLite), + // (InstanceConfig.googleAI_v1beta_freeTier_bypassProxy, ModelNames.gemini2FlashLite), + // (InstanceConfig.googleAI_v1beta_freeTier_bypassProxy, ModelNames.gemma3_4B), ]) func generateContent(_ config: InstanceConfig, modelName: String) async throws { let model = FirebaseAI.componentInstance(config).generativeModel( @@ -134,47 +139,84 @@ struct GenerateContentIntegrationTests { #expect(candidatesTokensDetails.tokenCount == usageMetadata.candidatesTokenCount) } - @Test(arguments: [ - (InstanceConfig.vertexAI_v1beta, ModelNames.gemini2_5_Flash, 0), - (InstanceConfig.vertexAI_v1beta, ModelNames.gemini2_5_Flash, 24576), - (InstanceConfig.vertexAI_v1beta_global, ModelNames.gemini2_5_Pro, 128), - (InstanceConfig.vertexAI_v1beta_global, ModelNames.gemini2_5_Pro, 32768), - (InstanceConfig.googleAI_v1beta, ModelNames.gemini2_5_Flash, 0), - (InstanceConfig.googleAI_v1beta, ModelNames.gemini2_5_Flash, 24576), - (InstanceConfig.googleAI_v1beta, ModelNames.gemini2_5_Pro, 128), - (InstanceConfig.googleAI_v1beta, ModelNames.gemini2_5_Pro, 32768), - (InstanceConfig.googleAI_v1beta_freeTier, ModelNames.gemini2_5_Flash, 0), - (InstanceConfig.googleAI_v1beta_freeTier, ModelNames.gemini2_5_Flash, 24576), - ]) + @Test( + arguments: [ + (.vertexAI_v1beta, ModelNames.gemini2_5_Flash, ThinkingConfig(thinkingBudget: 0)), + (.vertexAI_v1beta, ModelNames.gemini2_5_Flash, ThinkingConfig(thinkingBudget: 24576)), + (.vertexAI_v1beta, ModelNames.gemini2_5_Flash, ThinkingConfig( + thinkingBudget: 24576, includeThoughts: true + )), + (.vertexAI_v1beta_global, ModelNames.gemini2_5_Pro, ThinkingConfig(thinkingBudget: 128)), + (.vertexAI_v1beta_global, ModelNames.gemini2_5_Pro, ThinkingConfig(thinkingBudget: 32768)), + (.vertexAI_v1beta_global, ModelNames.gemini2_5_Pro, ThinkingConfig( + thinkingBudget: 32768, includeThoughts: true + )), + (.googleAI_v1beta, ModelNames.gemini2_5_Flash, ThinkingConfig(thinkingBudget: 0)), + (.googleAI_v1beta, ModelNames.gemini2_5_Flash, ThinkingConfig(thinkingBudget: 24576)), + (.googleAI_v1beta, ModelNames.gemini2_5_Flash, ThinkingConfig( + thinkingBudget: 24576, includeThoughts: true + )), + (.googleAI_v1beta, ModelNames.gemini2_5_Pro, ThinkingConfig(thinkingBudget: 128)), + (.googleAI_v1beta, ModelNames.gemini2_5_Pro, ThinkingConfig(thinkingBudget: 32768)), + (.googleAI_v1beta, ModelNames.gemini2_5_Pro, ThinkingConfig( + thinkingBudget: 32768, includeThoughts: true + )), + (.googleAI_v1beta_freeTier, ModelNames.gemini2_5_Flash, ThinkingConfig(thinkingBudget: 0)), + ( + .googleAI_v1beta_freeTier, + ModelNames.gemini2_5_Flash, + ThinkingConfig(thinkingBudget: 24576) + ), + (.googleAI_v1beta_freeTier, ModelNames.gemini2_5_Flash, ThinkingConfig( + thinkingBudget: 24576, includeThoughts: true + )), + // Note: The following configs are commented out for easy one-off manual testing. + // (.googleAI_v1beta_freeTier_bypassProxy, ModelNames.gemini2_5_Flash, ThinkingConfig( + // thinkingBudget: 0 + // )), + // (.googleAI_v1beta_freeTier_bypassProxy, ModelNames.gemini2_5_Flash, ThinkingConfig( + // thinkingBudget: 24576 + // )), + // (.googleAI_v1beta_freeTier_bypassProxy, ModelNames.gemini2_5_Flash, ThinkingConfig( + // thinkingBudget: 24576, includeThoughts: true + // )), + ] as [(InstanceConfig, String, ThinkingConfig)] + ) func generateContentThinking(_ config: InstanceConfig, modelName: String, - thinkingBudget: Int) async throws { + thinkingConfig: ThinkingConfig) async throws { let model = FirebaseAI.componentInstance(config).generativeModel( modelName: modelName, generationConfig: GenerationConfig( temperature: 0.0, topP: 0.0, topK: 1, - thinkingConfig: ThinkingConfig(thinkingBudget: thinkingBudget) + thinkingConfig: thinkingConfig ), safetySettings: safetySettings ) + let chat = model.startChat() let prompt = "Where is Google headquarters located? Answer with the city name only." - let response = try await model.generateContent(prompt) + let response = try await chat.sendMessage(prompt) let text = try #require(response.text).trimmingCharacters(in: .whitespacesAndNewlines) #expect(text == "Mountain View") + let candidate = try #require(response.candidates.first) + let thoughtParts = candidate.content.parts.compactMap { $0.isThought ? $0 : nil } + #expect(thoughtParts.isEmpty != (thinkingConfig.includeThoughts ?? false)) + let usageMetadata = try #require(response.usageMetadata) #expect(usageMetadata.promptTokenCount.isEqual(to: 13, accuracy: tokenCountAccuracy)) #expect(usageMetadata.promptTokensDetails.count == 1) let promptTokensDetails = try #require(usageMetadata.promptTokensDetails.first) #expect(promptTokensDetails.modality == .text) #expect(promptTokensDetails.tokenCount == usageMetadata.promptTokenCount) - if thinkingBudget == 0 { - #expect(usageMetadata.thoughtsTokenCount == 0) - } else { + if let thinkingBudget = thinkingConfig.thinkingBudget, thinkingBudget > 0 { + #expect(usageMetadata.thoughtsTokenCount > 0) #expect(usageMetadata.thoughtsTokenCount <= thinkingBudget) + } else { + #expect(usageMetadata.thoughtsTokenCount == 0) } #expect(usageMetadata.candidatesTokenCount.isEqual(to: 3, accuracy: tokenCountAccuracy)) // The `candidatesTokensDetails` field is erroneously omitted when using the Google AI (Gemini @@ -195,12 +237,97 @@ struct GenerateContentIntegrationTests { )) } + @Test( + arguments: [ + (.vertexAI_v1beta_global, ModelNames.gemini2_5_Flash, ThinkingConfig(thinkingBudget: -1)), + (.vertexAI_v1beta_global, ModelNames.gemini2_5_Flash, ThinkingConfig( + thinkingBudget: -1, includeThoughts: true + )), + (.vertexAI_v1beta_global, ModelNames.gemini2_5_Pro, ThinkingConfig(thinkingBudget: -1)), + (.vertexAI_v1beta_global, ModelNames.gemini2_5_Pro, ThinkingConfig( + thinkingBudget: -1, includeThoughts: true + )), + (.googleAI_v1beta, ModelNames.gemini2_5_Flash, ThinkingConfig(thinkingBudget: -1)), + (.googleAI_v1beta, ModelNames.gemini2_5_Flash, ThinkingConfig( + thinkingBudget: -1, includeThoughts: true + )), + (.googleAI_v1beta, ModelNames.gemini2_5_Pro, ThinkingConfig(thinkingBudget: -1)), + (.googleAI_v1beta, ModelNames.gemini2_5_Pro, ThinkingConfig( + thinkingBudget: -1, includeThoughts: true + )), + ] as [(InstanceConfig, String, ThinkingConfig)] + ) + func generateContentThinkingFunctionCalling(_ config: InstanceConfig, modelName: String, + thinkingConfig: ThinkingConfig) async throws { + let getTemperatureDeclaration = FunctionDeclaration( + name: "getTemperature", + description: "Returns the current temperature in Celsius for the specified location", + parameters: [ + "city": .string(), + "region": .string(description: "The province or state"), + "country": .string(), + ] + ) + let model = FirebaseAI.componentInstance(config).generativeModel( + modelName: modelName, + generationConfig: GenerationConfig( + temperature: 0.0, + topP: 0.0, + topK: 1, + thinkingConfig: thinkingConfig + ), + safetySettings: safetySettings, + tools: [.functionDeclarations([getTemperatureDeclaration])], + systemInstruction: ModelContent(parts: """ + You are a weather bot that specializes in reporting outdoor temperatures in Celsius. + + Always use the `getTemperature` function to determine the current temperature in a location. + + Always respond in the format: + - Location: City, Province/State, Country + - Temperature: #C + """) + ) + let chat = model.startChat() + let prompt = "What is the current temperature in Waterloo, Ontario, Canada?" + + let response = try await chat.sendMessage(prompt) + + #expect(response.functionCalls.count == 1) + let temperatureFunctionCall = try #require(response.functionCalls.first) + try #require(temperatureFunctionCall.name == getTemperatureDeclaration.name) + #expect(temperatureFunctionCall.args == [ + "city": .string("Waterloo"), + "region": .string("Ontario"), + "country": .string("Canada"), + ]) + #expect(temperatureFunctionCall.isThought == false) + let thoughtSignature = try #require(temperatureFunctionCall.thoughtSignature) + #expect(!thoughtSignature.isEmpty) + + let temperatureFunctionResponse = FunctionResponsePart( + name: temperatureFunctionCall.name, + response: [ + "temperature": .number(25), + "units": .string("Celsius"), + ] + ) + + let response2 = try await chat.sendMessage(temperatureFunctionResponse) + + #expect(response2.functionCalls.isEmpty) + let finalText = try #require(response2.text).trimmingCharacters(in: .whitespacesAndNewlines) + #expect(finalText.contains("Waterloo")) + #expect(finalText.contains("25")) + } + @Test(arguments: [ InstanceConfig.vertexAI_v1beta, InstanceConfig.vertexAI_v1beta_global, InstanceConfig.googleAI_v1beta, - InstanceConfig.googleAI_v1beta_staging, - InstanceConfig.googleAI_v1beta_freeTier_bypassProxy, + // Note: The following configs are commented out for easy one-off manual testing. + // InstanceConfig.googleAI_v1beta_staging, + // InstanceConfig.googleAI_v1beta_freeTier_bypassProxy, ]) func generateImage(_ config: InstanceConfig) async throws { let generationConfig = GenerationConfig( @@ -296,13 +423,18 @@ struct GenerateContentIntegrationTests { @Test(arguments: [ (InstanceConfig.vertexAI_v1beta, ModelNames.gemini2FlashLite), (InstanceConfig.vertexAI_v1beta_global, ModelNames.gemini2FlashLite), - (InstanceConfig.vertexAI_v1beta_staging, ModelNames.gemini2FlashLite), + (InstanceConfig.vertexAI_v1beta_global_appCheckLimitedUse, ModelNames.gemini2FlashLite), (InstanceConfig.googleAI_v1beta, ModelNames.gemini2FlashLite), + (InstanceConfig.googleAI_v1beta_appCheckLimitedUse, ModelNames.gemini2FlashLite), (InstanceConfig.googleAI_v1beta, ModelNames.gemma3_4B), - (InstanceConfig.googleAI_v1beta_staging, ModelNames.gemini2FlashLite), - (InstanceConfig.googleAI_v1beta_staging, ModelNames.gemma3_4B), - (InstanceConfig.googleAI_v1beta_freeTier_bypassProxy, ModelNames.gemini2FlashLite), - (InstanceConfig.googleAI_v1beta_freeTier_bypassProxy, ModelNames.gemma3_4B), + (InstanceConfig.googleAI_v1beta_freeTier, ModelNames.gemini2FlashLite), + (InstanceConfig.googleAI_v1beta_freeTier, ModelNames.gemma3_4B), + // Note: The following configs are commented out for easy one-off manual testing. + // (InstanceConfig.vertexAI_v1beta_staging, ModelNames.gemini2FlashLite), + // (InstanceConfig.googleAI_v1beta_staging, ModelNames.gemini2FlashLite), + // (InstanceConfig.googleAI_v1beta_staging, ModelNames.gemma3_4B), + // (InstanceConfig.googleAI_v1beta_freeTier_bypassProxy, ModelNames.gemini2FlashLite), + // (InstanceConfig.googleAI_v1beta_freeTier_bypassProxy, ModelNames.gemma3_4B), ]) func generateContentStream(_ config: InstanceConfig, modelName: String) async throws { let expectedResponse = [ diff --git a/FirebaseAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift b/FirebaseAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift index 21554d28250..df06f43c91f 100644 --- a/FirebaseAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift +++ b/FirebaseAI/Tests/TestApp/Tests/Utilities/InstanceConfig.swift @@ -27,12 +27,20 @@ struct InstanceConfig: Equatable, Encodable { location: "global", apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1beta) ) + static let vertexAI_v1beta_global_appCheckLimitedUse = InstanceConfig( + location: "global", + useLimitedUseAppCheckTokens: true, + apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1beta) + ) static let vertexAI_v1beta_staging = InstanceConfig( apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyStaging), version: .v1beta) ) static let googleAI_v1beta = InstanceConfig( apiConfig: APIConfig(service: .googleAI(endpoint: .firebaseProxyProd), version: .v1beta) ) + static let googleAI_v1beta_appCheckLimitedUse = InstanceConfig( + apiConfig: APIConfig(service: .googleAI(endpoint: .firebaseProxyProd), version: .v1beta) + ) static let googleAI_v1beta_staging = InstanceConfig( apiConfig: APIConfig(service: .googleAI(endpoint: .firebaseProxyStaging), version: .v1beta) ) @@ -48,33 +56,54 @@ struct InstanceConfig: Equatable, Encodable { static let allConfigs = [ vertexAI_v1beta, vertexAI_v1beta_global, - vertexAI_v1beta_staging, + vertexAI_v1beta_global_appCheckLimitedUse, googleAI_v1beta, - googleAI_v1beta_staging, - googleAI_v1beta_freeTier_bypassProxy, + googleAI_v1beta_appCheckLimitedUse, + googleAI_v1beta_freeTier, + // Note: The following configs are commented out for easy one-off manual testing. + // vertexAI_v1beta_staging, + // googleAI_v1beta_staging, + // googleAI_v1beta_freeTier_bypassProxy, ] static let vertexAI_v1beta_appCheckNotConfigured = InstanceConfig( appName: FirebaseAppNames.appCheckNotConfigured, apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1beta) ) + static let vertexAI_v1beta_appCheckNotConfigured_limitedUseTokens = InstanceConfig( + appName: FirebaseAppNames.appCheckNotConfigured, + useLimitedUseAppCheckTokens: true, + apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1beta) + ) static let googleAI_v1beta_appCheckNotConfigured = InstanceConfig( appName: FirebaseAppNames.appCheckNotConfigured, apiConfig: APIConfig(service: .googleAI(endpoint: .firebaseProxyProd), version: .v1beta) ) + static let googleAI_v1beta_appCheckNotConfigured_limitedUseTokens = InstanceConfig( + appName: FirebaseAppNames.appCheckNotConfigured, + useLimitedUseAppCheckTokens: true, + apiConfig: APIConfig(service: .googleAI(endpoint: .firebaseProxyProd), version: .v1beta) + ) static let appCheckNotConfiguredConfigs = [ vertexAI_v1beta_appCheckNotConfigured, + vertexAI_v1beta_appCheckNotConfigured_limitedUseTokens, googleAI_v1beta_appCheckNotConfigured, + googleAI_v1beta_appCheckNotConfigured_limitedUseTokens, ] let appName: String? let location: String? + let useLimitedUseAppCheckTokens: Bool let apiConfig: APIConfig - init(appName: String? = nil, location: String? = nil, apiConfig: APIConfig) { + init(appName: String? = nil, + location: String? = nil, + useLimitedUseAppCheckTokens: Bool = false, + apiConfig: APIConfig) { self.appName = appName self.location = location + self.useLimitedUseAppCheckTokens = useLimitedUseAppCheckTokens self.apiConfig = apiConfig } @@ -108,8 +137,12 @@ extension InstanceConfig: CustomTestStringConvertible { " - Bypass Proxy" } let locationSuffix = location.map { " - \($0)" } ?? "" + let appCheckLimitedUseDesignator = useLimitedUseAppCheckTokens ? " - FAC Limited-Use" : "" - return "\(serviceName) (\(versionName))\(freeTierDesignator)\(endpointSuffix)\(locationSuffix)" + return """ + \(serviceName) (\(versionName))\(freeTierDesignator)\(endpointSuffix)\(locationSuffix)\ + \(appCheckLimitedUseDesignator) + """ } } @@ -121,7 +154,8 @@ extension FirebaseAI { return FirebaseAI.createInstance( app: instanceConfig.app, location: location, - apiConfig: instanceConfig.apiConfig + apiConfig: instanceConfig.apiConfig, + useLimitedUseAppCheckTokens: instanceConfig.useLimitedUseAppCheckTokens ) case .googleAI: assert( @@ -131,7 +165,8 @@ extension FirebaseAI { return FirebaseAI.createInstance( app: instanceConfig.app, location: nil, - apiConfig: instanceConfig.apiConfig + apiConfig: instanceConfig.apiConfig, + useLimitedUseAppCheckTokens: instanceConfig.useLimitedUseAppCheckTokens ) } } diff --git a/FirebaseAI/Tests/Unit/Fakes/AppCheckInteropFake.swift b/FirebaseAI/Tests/Unit/Fakes/AppCheckInteropFake.swift index 62fc753ae68..81b6ac1bb7c 100644 --- a/FirebaseAI/Tests/Unit/Fakes/AppCheckInteropFake.swift +++ b/FirebaseAI/Tests/Unit/Fakes/AppCheckInteropFake.swift @@ -40,6 +40,10 @@ class AppCheckInteropFake: NSObject, AppCheckInterop { return AppCheckTokenResultInteropFake(token: token, error: error) } + func getLimitedUseToken() async -> any FIRAppCheckTokenResultInterop { + return AppCheckTokenResultInteropFake(token: "limited_use_\(token)", error: error) + } + func tokenDidChangeNotificationName() -> String { fatalError("\(#function) not implemented.") } @@ -52,9 +56,10 @@ class AppCheckInteropFake: NSObject, AppCheckInterop { fatalError("\(#function) not implemented.") } - private class AppCheckTokenResultInteropFake: NSObject, FIRAppCheckTokenResultInterop { - var token: String - var error: Error? + private class AppCheckTokenResultInteropFake: NSObject, FIRAppCheckTokenResultInterop, + @unchecked Sendable { + let token: String + let error: Error? init(token: String, error: Error?) { self.token = token diff --git a/FirebaseAI/Tests/Unit/GenerativeModelGoogleAITests.swift b/FirebaseAI/Tests/Unit/GenerativeModelGoogleAITests.swift index 103943e6f92..00e0d398855 100644 --- a/FirebaseAI/Tests/Unit/GenerativeModelGoogleAITests.swift +++ b/FirebaseAI/Tests/Unit/GenerativeModelGoogleAITests.swift @@ -262,6 +262,52 @@ final class GenerativeModelGoogleAITests: XCTestCase { ) } + func testGenerateContent_success_thinking_thoughtSummary() async throws { + MockURLProtocol.requestHandler = try GenerativeModelTestUtil.httpRequestHandler( + forResource: "unary-success-thinking-reply-thought-summary", + withExtension: "json", + subdirectory: googleAISubdirectory + ) + + let response = try await model.generateContent(testPrompt) + + XCTAssertEqual(response.candidates.count, 1) + let candidate = try XCTUnwrap(response.candidates.first) + XCTAssertEqual(candidate.content.parts.count, 2) + let thoughtPart = try XCTUnwrap(candidate.content.parts.first as? TextPart) + XCTAssertTrue(thoughtPart.isThought) + XCTAssertTrue(thoughtPart.text.hasPrefix("**Thinking About Google's Headquarters**")) + XCTAssertEqual(thoughtPart.text, response.thoughtSummary) + let textPart = try XCTUnwrap(candidate.content.parts.last as? TextPart) + XCTAssertFalse(textPart.isThought) + XCTAssertEqual(textPart.text, "Mountain View") + XCTAssertEqual(textPart.text, response.text) + } + + func testGenerateContent_success_thinking_functionCall_thoughtSummaryAndSignature() async throws { + MockURLProtocol.requestHandler = try GenerativeModelTestUtil.httpRequestHandler( + forResource: "unary-success-thinking-function-call-thought-summary-signature", + withExtension: "json", + subdirectory: googleAISubdirectory + ) + + let response = try await model.generateContent(testPrompt) + + XCTAssertEqual(response.candidates.count, 1) + let candidate = try XCTUnwrap(response.candidates.first) + XCTAssertEqual(candidate.finishReason, .stop) + XCTAssertEqual(candidate.content.parts.count, 2) + let thoughtPart = try XCTUnwrap(candidate.content.parts.first as? TextPart) + XCTAssertTrue(thoughtPart.isThought) + XCTAssertTrue(thoughtPart.text.hasPrefix("**Thinking Through the New Year's Eve Calculation**")) + let functionCallPart = try XCTUnwrap(candidate.content.parts.last as? FunctionCallPart) + XCTAssertFalse(functionCallPart.isThought) + XCTAssertEqual(functionCallPart.name, "now") + XCTAssertTrue(functionCallPart.args.isEmpty) + let thoughtSignature = try XCTUnwrap(functionCallPart.thoughtSignature) + XCTAssertTrue(thoughtSignature.hasPrefix("CtQOAVSoXO74PmYr9AFu")) + } + func testGenerateContent_failure_invalidAPIKey() async throws { let expectedStatusCode = 400 MockURLProtocol.requestHandler = try GenerativeModelTestUtil.httpRequestHandler( @@ -397,6 +443,72 @@ final class GenerativeModelGoogleAITests: XCTestCase { XCTAssertNil(citation.publicationDate) } + func testGenerateContentStream_successWithThoughtSummary() async throws { + MockURLProtocol.requestHandler = try GenerativeModelTestUtil.httpRequestHandler( + forResource: "streaming-success-thinking-reply-thought-summary", + withExtension: "txt", + subdirectory: googleAISubdirectory + ) + + var thoughtSummary = "" + var text = "" + let stream = try model.generateContentStream("Hi") + for try await response in stream { + let candidate = try XCTUnwrap(response.candidates.first) + XCTAssertEqual(candidate.content.parts.count, 1) + let textPart = try XCTUnwrap(candidate.content.parts.first as? TextPart) + if textPart.isThought { + let newThought = try XCTUnwrap(response.thoughtSummary) + XCTAssertEqual(textPart.text, newThought) + thoughtSummary.append(newThought) + } else { + let newText = try XCTUnwrap(response.text) + XCTAssertEqual(textPart.text, newText) + text.append(newText) + } + } + + XCTAssertTrue(thoughtSummary.hasPrefix("**Exploring Sky Color**")) + XCTAssertTrue(text.hasPrefix("The sky is blue because")) + } + + func testGenerateContentStream_success_thinking_functionCall_thoughtSummary_signature() async throws { + MockURLProtocol.requestHandler = try GenerativeModelTestUtil.httpRequestHandler( + forResource: "streaming-success-thinking-function-call-thought-summary-signature", + withExtension: "txt", + subdirectory: googleAISubdirectory + ) + + var thoughtSummary = "" + var functionCalls: [FunctionCallPart] = [] + let stream = try model.generateContentStream("Hi") + for try await response in stream { + let candidate = try XCTUnwrap(response.candidates.first) + XCTAssertEqual(candidate.content.parts.count, 1) + let part = try XCTUnwrap(candidate.content.parts.first) + if part.isThought { + let textPart = try XCTUnwrap(part as? TextPart) + let newThought = try XCTUnwrap(response.thoughtSummary) + XCTAssertEqual(textPart.text, newThought) + thoughtSummary.append(newThought) + } else { + let functionCallPart = try XCTUnwrap(part as? FunctionCallPart) + XCTAssertEqual(response.functionCalls.count, 1) + let newFunctionCall = try XCTUnwrap(response.functionCalls.first) + XCTAssertEqual(functionCallPart, newFunctionCall) + functionCalls.append(newFunctionCall) + } + } + + XCTAssertTrue(thoughtSummary.hasPrefix("**Calculating the Days**")) + XCTAssertEqual(functionCalls.count, 1) + let functionCall = try XCTUnwrap(functionCalls.first) + XCTAssertEqual(functionCall.name, "now") + XCTAssertTrue(functionCall.args.isEmpty) + let thoughtSignature = try XCTUnwrap(functionCall.thoughtSignature) + XCTAssertTrue(thoughtSignature.hasPrefix("CiIBVKhc7vB+vaaq6rA")) + } + func testGenerateContentStream_failureInvalidAPIKey() async throws { MockURLProtocol.requestHandler = try GenerativeModelTestUtil.httpRequestHandler( forResource: "unary-failure-api-key", diff --git a/FirebaseAI/Tests/Unit/GenerativeModelVertexAITests.swift b/FirebaseAI/Tests/Unit/GenerativeModelVertexAITests.swift index 6557735ccc4..80a383eb461 100644 --- a/FirebaseAI/Tests/Unit/GenerativeModelVertexAITests.swift +++ b/FirebaseAI/Tests/Unit/GenerativeModelVertexAITests.swift @@ -434,6 +434,29 @@ final class GenerativeModelVertexAITests: XCTestCase { XCTAssertEqual(text, "The sum of [1, 2, 3] is") } + func testGenerateContent_success_thinking_thoughtSummary() async throws { + MockURLProtocol.requestHandler = try GenerativeModelTestUtil.httpRequestHandler( + forResource: "unary-success-thinking-reply-thought-summary", + withExtension: "json", + subdirectory: vertexSubdirectory + ) + + let response = try await model.generateContent(testPrompt) + + XCTAssertEqual(response.candidates.count, 1) + let candidate = try XCTUnwrap(response.candidates.first) + XCTAssertEqual(candidate.finishReason, .stop) + XCTAssertEqual(candidate.content.parts.count, 2) + let thoughtPart = try XCTUnwrap(candidate.content.parts.first as? TextPart) + XCTAssertTrue(thoughtPart.isThought) + XCTAssertTrue(thoughtPart.text.hasPrefix("Right, someone needs the city where Google")) + XCTAssertEqual(response.thoughtSummary, thoughtPart.text) + let textPart = try XCTUnwrap(candidate.content.parts.last as? TextPart) + XCTAssertFalse(textPart.isThought) + XCTAssertEqual(textPart.text, "Mountain View") + XCTAssertEqual(response.text, textPart.text) + } + func testGenerateContent_success_image_invalidSafetyRatingsIgnored() async throws { MockURLProtocol.requestHandler = try GenerativeModelTestUtil.httpRequestHandler( forResource: "unary-success-image-invalid-safety-ratings", @@ -478,6 +501,31 @@ final class GenerativeModelVertexAITests: XCTestCase { _ = try await model.generateContent(testPrompt) } + func testGenerateContent_appCheck_validToken_limitedUse() async throws { + let appCheckToken = "test-valid-token" + model = GenerativeModel( + modelName: testModelName, + modelResourceName: testModelResourceName, + firebaseInfo: GenerativeModelTestUtil.testFirebaseInfo( + appCheck: AppCheckInteropFake(token: appCheckToken) + ), + apiConfig: apiConfig, + tools: nil, + requestOptions: RequestOptions(), + urlSession: urlSession, + useLimitedUseAppCheckTokens: true + ) + MockURLProtocol + .requestHandler = try GenerativeModelTestUtil.httpRequestHandler( + forResource: "unary-success-basic-reply-short", + withExtension: "json", + subdirectory: vertexSubdirectory, + appCheckToken: "limited_use_\(appCheckToken)" + ) + + _ = try await model.generateContent(testPrompt) + } + func testGenerateContent_dataCollectionOff() async throws { let appCheckToken = "test-valid-token" model = GenerativeModel( @@ -1330,6 +1378,33 @@ final class GenerativeModelVertexAITests: XCTestCase { XCTAssertFalse(citations.contains { $0.license?.isEmpty ?? false }) } + func testGenerateContentStream_successWithThinking_thoughtSummary() async throws { + MockURLProtocol.requestHandler = try GenerativeModelTestUtil.httpRequestHandler( + forResource: "streaming-success-thinking-reply-thought-summary", + withExtension: "txt", + subdirectory: vertexSubdirectory + ) + + var thoughtSummary = "" + var text = "" + let stream = try model.generateContentStream("Hi") + for try await response in stream { + let candidate = try XCTUnwrap(response.candidates.first) + XCTAssertEqual(candidate.content.parts.count, 1) + let part = try XCTUnwrap(candidate.content.parts.first) + let textPart = try XCTUnwrap(part as? TextPart) + if textPart.isThought { + let newThought = try XCTUnwrap(response.thoughtSummary) + thoughtSummary.append(newThought) + } else { + text.append(textPart.text) + } + } + + XCTAssertTrue(thoughtSummary.hasPrefix("**Understanding the Core Question**")) + XCTAssertTrue(text.hasPrefix("The sky is blue due to a phenomenon")) + } + func testGenerateContentStream_successWithInvalidSafetyRatingsIgnored() async throws { MockURLProtocol.requestHandler = try GenerativeModelTestUtil.httpRequestHandler( forResource: "streaming-success-image-invalid-safety-ratings", diff --git a/FirebaseAI/Tests/Unit/README.md b/FirebaseAI/Tests/Unit/README.md index 9463d595294..88019041f9f 100644 --- a/FirebaseAI/Tests/Unit/README.md +++ b/FirebaseAI/Tests/Unit/README.md @@ -1,3 +1,3 @@ See the Firebase AI SDK -[README](https://github.com/firebase/firebase-ios-sdk/tree/main/FirebaseVertexAI#unit-tests) +[README](https://github.com/firebase/firebase-ios-sdk/tree/main/FirebaseAI#unit-tests) for required setup instructions. diff --git a/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationParametersTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationParametersTests.swift index 494feda9f7a..a96174f3b7d 100644 --- a/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationParametersTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Imagen/ImageGenerationParametersTests.swift @@ -34,7 +34,8 @@ final class ImageGenerationParametersTests: XCTestCase { personGeneration: nil, outputOptions: nil, addWatermark: nil, - includeResponsibleAIFilterReason: true + includeResponsibleAIFilterReason: true, + includeSafetyAttributes: true ) let parameters = ImagenModel.imageGenerationParameters( @@ -57,7 +58,8 @@ final class ImageGenerationParametersTests: XCTestCase { personGeneration: nil, outputOptions: nil, addWatermark: nil, - includeResponsibleAIFilterReason: true + includeResponsibleAIFilterReason: true, + includeSafetyAttributes: true ) let parameters = ImagenModel.imageGenerationParameters( @@ -95,7 +97,8 @@ final class ImageGenerationParametersTests: XCTestCase { compressionQuality: imageFormat.compressionQuality ), addWatermark: addWatermark, - includeResponsibleAIFilterReason: true + includeResponsibleAIFilterReason: true, + includeSafetyAttributes: true ) let parameters = ImagenModel.imageGenerationParameters( @@ -124,7 +127,8 @@ final class ImageGenerationParametersTests: XCTestCase { personGeneration: personFilterLevel.rawValue, outputOptions: nil, addWatermark: nil, - includeResponsibleAIFilterReason: true + includeResponsibleAIFilterReason: true, + includeSafetyAttributes: true ) let parameters = ImagenModel.imageGenerationParameters( @@ -170,7 +174,8 @@ final class ImageGenerationParametersTests: XCTestCase { compressionQuality: imageFormat.compressionQuality ), addWatermark: addWatermark, - includeResponsibleAIFilterReason: true + includeResponsibleAIFilterReason: true, + includeSafetyAttributes: true ) let parameters = ImagenModel.imageGenerationParameters( @@ -200,6 +205,7 @@ final class ImageGenerationParametersTests: XCTestCase { let outputOptions = ImageGenerationOutputOptions(mimeType: mimeType, compressionQuality: nil) let addWatermark = false let includeRAIReason = true + let includeSafetyAttributes = true let parameters = ImageGenerationParameters( sampleCount: sampleCount, storageURI: storageURI, @@ -209,7 +215,8 @@ final class ImageGenerationParametersTests: XCTestCase { personGeneration: personGeneration, outputOptions: outputOptions, addWatermark: addWatermark, - includeResponsibleAIFilterReason: includeRAIReason + includeResponsibleAIFilterReason: includeRAIReason, + includeSafetyAttributes: includeSafetyAttributes ) let jsonData = try encoder.encode(parameters) @@ -220,6 +227,7 @@ final class ImageGenerationParametersTests: XCTestCase { "addWatermark" : \(addWatermark), "aspectRatio" : "\(aspectRatio)", "includeRaiReason" : \(includeRAIReason), + "includeSafetyAttributes" : \(includeSafetyAttributes), "negativePrompt" : "\(negativePrompt)", "outputOptions" : { "mimeType" : "\(mimeType)" @@ -246,7 +254,8 @@ final class ImageGenerationParametersTests: XCTestCase { personGeneration: nil, outputOptions: nil, addWatermark: addWatermark, - includeResponsibleAIFilterReason: nil + includeResponsibleAIFilterReason: nil, + includeSafetyAttributes: nil ) let jsonData = try encoder.encode(parameters) @@ -272,7 +281,8 @@ final class ImageGenerationParametersTests: XCTestCase { personGeneration: nil, outputOptions: nil, addWatermark: nil, - includeResponsibleAIFilterReason: nil + includeResponsibleAIFilterReason: nil, + includeSafetyAttributes: nil ) let jsonData = try encoder.encode(parameters) diff --git a/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationRequestTests.swift b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationRequestTests.swift index eb8b3df83ca..9a48ed7c8a2 100644 --- a/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationRequestTests.swift +++ b/FirebaseAI/Tests/Unit/Types/Imagen/ImagenGenerationRequestTests.swift @@ -25,6 +25,7 @@ final class ImagenGenerationRequestTests: XCTestCase { let aspectRatio = "16:9" let safetyFilterLevel = "block_low_and_above" let includeResponsibleAIFilterReason = true + let includeSafetyAttributes = true lazy var parameters = ImageGenerationParameters( sampleCount: sampleCount, storageURI: nil, @@ -34,7 +35,8 @@ final class ImagenGenerationRequestTests: XCTestCase { personGeneration: nil, outputOptions: nil, addWatermark: nil, - includeResponsibleAIFilterReason: includeResponsibleAIFilterReason + includeResponsibleAIFilterReason: includeResponsibleAIFilterReason, + includeSafetyAttributes: includeSafetyAttributes ) let apiConfig = FirebaseAI.defaultVertexAIAPIConfig @@ -108,6 +110,7 @@ final class ImagenGenerationRequestTests: XCTestCase { "parameters" : { "aspectRatio" : "\(aspectRatio)", "includeRaiReason" : \(includeResponsibleAIFilterReason), + "includeSafetyAttributes" : \(includeSafetyAttributes), "safetySetting" : "\(safetyFilterLevel)", "sampleCount" : \(sampleCount) } @@ -137,6 +140,7 @@ final class ImagenGenerationRequestTests: XCTestCase { "parameters" : { "aspectRatio" : "\(aspectRatio)", "includeRaiReason" : \(includeResponsibleAIFilterReason), + "includeSafetyAttributes" : \(includeSafetyAttributes), "safetySetting" : "\(safetyFilterLevel)", "sampleCount" : \(sampleCount) } diff --git a/FirebaseAI/Tests/Unit/Types/InternalPartTests.swift b/FirebaseAI/Tests/Unit/Types/InternalPartTests.swift new file mode 100644 index 00000000000..2cd5c5fee2a --- /dev/null +++ b/FirebaseAI/Tests/Unit/Types/InternalPartTests.swift @@ -0,0 +1,286 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +@testable import FirebaseAI +import XCTest + +@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *) +final class InternalPartTests: XCTestCase { + let decoder = JSONDecoder() + + func testDecodeTextPartWithThought() throws { + let json = """ + { + "text": "This is a thought.", + "thought": true + } + """ + let jsonData = try XCTUnwrap(json.data(using: .utf8)) + + let part = try decoder.decode(InternalPart.self, from: jsonData) + + XCTAssertEqual(part.isThought, true) + guard case let .text(text) = part.data else { + XCTFail("Decoded part is not a text part.") + return + } + XCTAssertEqual(text, "This is a thought.") + } + + func testDecodeTextPartWithoutThought() throws { + let json = """ + { + "text": "This is not a thought." + } + """ + let jsonData = try XCTUnwrap(json.data(using: .utf8)) + + let part = try decoder.decode(InternalPart.self, from: jsonData) + + XCTAssertNil(part.isThought) + guard case let .text(text) = part.data else { + XCTFail("Decoded part is not a text part.") + return + } + XCTAssertEqual(text, "This is not a thought.") + } + + func testDecodeInlineDataPartWithThought() throws { + let imageBase64 = + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P+/HgAFhAJ/wlseKgAAAABJRU5ErkJggg==" + let mimeType = "image/png" + let json = """ + { + "inlineData": { + "mimeType": "\(mimeType)", + "data": "\(imageBase64)" + }, + "thought": true + } + """ + let jsonData = try XCTUnwrap(json.data(using: .utf8)) + + let part = try decoder.decode(InternalPart.self, from: jsonData) + + XCTAssertEqual(part.isThought, true) + guard case let .inlineData(inlineData) = part.data else { + XCTFail("Decoded part is not an inlineData part.") + return + } + XCTAssertEqual(inlineData.mimeType, mimeType) + XCTAssertEqual(inlineData.data, Data(base64Encoded: imageBase64)) + } + + func testDecodeInlineDataPartWithoutThought() throws { + let imageBase64 = "aGVsbG8=" + let mimeType = "image/png" + let json = """ + { + "inlineData": { + "mimeType": "\(mimeType)", + "data": "\(imageBase64)" + } + } + """ + let jsonData = try XCTUnwrap(json.data(using: .utf8)) + + let part = try decoder.decode(InternalPart.self, from: jsonData) + + XCTAssertNil(part.isThought) + guard case let .inlineData(inlineData) = part.data else { + XCTFail("Decoded part is not an inlineData part.") + return + } + XCTAssertEqual(inlineData.mimeType, mimeType) + XCTAssertEqual(inlineData.data, Data(base64Encoded: imageBase64)) + } + + func testDecodeFileDataPartWithThought() throws { + let uri = "file:///path/to/file.mp3" + let mimeType = "audio/mpeg" + let json = """ + { + "fileData": { + "fileUri": "\(uri)", + "mimeType": "\(mimeType)" + }, + "thought": true + } + """ + let jsonData = try XCTUnwrap(json.data(using: .utf8)) + + let part = try decoder.decode(InternalPart.self, from: jsonData) + + XCTAssertEqual(part.isThought, true) + guard case let .fileData(fileData) = part.data else { + XCTFail("Decoded part is not a fileData part.") + return + } + XCTAssertEqual(fileData.fileURI, uri) + XCTAssertEqual(fileData.mimeType, mimeType) + } + + func testDecodeFileDataPartWithoutThought() throws { + let uri = "file:///path/to/file.mp3" + let mimeType = "audio/mpeg" + let json = """ + { + "fileData": { + "fileUri": "\(uri)", + "mimeType": "\(mimeType)" + } + } + """ + let jsonData = try XCTUnwrap(json.data(using: .utf8)) + + let part = try decoder.decode(InternalPart.self, from: jsonData) + + XCTAssertNil(part.isThought) + guard case let .fileData(fileData) = part.data else { + XCTFail("Decoded part is not a fileData part.") + return + } + XCTAssertEqual(fileData.fileURI, uri) + XCTAssertEqual(fileData.mimeType, mimeType) + } + + func testDecodeFunctionCallPartWithThoughtSignature() throws { + let functionName = "someFunction" + let expectedThoughtSignature = "some_signature" + let json = """ + { + "functionCall": { + "name": "\(functionName)", + "args": { + "arg1": "value1" + }, + }, + "thoughtSignature": "\(expectedThoughtSignature)" + } + """ + let jsonData = try XCTUnwrap(json.data(using: .utf8)) + + let part = try decoder.decode(InternalPart.self, from: jsonData) + + let thoughtSignature = try XCTUnwrap(part.thoughtSignature) + XCTAssertEqual(thoughtSignature, expectedThoughtSignature) + XCTAssertNil(part.isThought) + guard case let .functionCall(functionCall) = part.data else { + XCTFail("Decoded part is not a functionCall part.") + return + } + XCTAssertEqual(functionCall.name, functionName) + XCTAssertEqual(functionCall.args, ["arg1": .string("value1")]) + } + + func testDecodeFunctionCallPartWithoutThoughtSignature() throws { + let functionName = "someFunction" + let json = """ + { + "functionCall": { + "name": "\(functionName)", + "args": { + "arg1": "value1" + } + } + } + """ + let jsonData = try XCTUnwrap(json.data(using: .utf8)) + + let part = try decoder.decode(InternalPart.self, from: jsonData) + + XCTAssertNil(part.isThought) + XCTAssertNil(part.thoughtSignature) + guard case let .functionCall(functionCall) = part.data else { + XCTFail("Decoded part is not a functionCall part.") + return + } + XCTAssertEqual(functionCall.name, functionName) + XCTAssertEqual(functionCall.args, ["arg1": .string("value1")]) + } + + func testDecodeFunctionCallPartWithoutArgs() throws { + let functionName = "someFunction" + let json = """ + { + "functionCall": { + "name": "\(functionName)" + } + } + """ + let jsonData = try XCTUnwrap(json.data(using: .utf8)) + + let part = try decoder.decode(InternalPart.self, from: jsonData) + + XCTAssertNil(part.isThought) + XCTAssertNil(part.thoughtSignature) + guard case let .functionCall(functionCall) = part.data else { + XCTFail("Decoded part is not a functionCall part.") + return + } + XCTAssertEqual(functionCall.name, functionName) + XCTAssertEqual(functionCall.args, JSONObject()) + } + + func testDecodeFunctionResponsePartWithThought() throws { + let functionName = "someFunction" + let json = """ + { + "functionResponse": { + "name": "\(functionName)", + "response": { + "output": "someValue" + } + }, + "thought": true + } + """ + let jsonData = try XCTUnwrap(json.data(using: .utf8)) + + let part = try decoder.decode(InternalPart.self, from: jsonData) + + XCTAssertEqual(part.isThought, true) + guard case let .functionResponse(functionResponse) = part.data else { + XCTFail("Decoded part is not a functionResponse part.") + return + } + XCTAssertEqual(functionResponse.name, functionName) + XCTAssertEqual(functionResponse.response, ["output": .string("someValue")]) + } + + func testDecodeFunctionResponsePartWithoutThought() throws { + let functionName = "someFunction" + let json = """ + { + "functionResponse": { + "name": "\(functionName)", + "response": { + "output": "someValue" + } + } + } + """ + let jsonData = try XCTUnwrap(json.data(using: .utf8)) + + let part = try decoder.decode(InternalPart.self, from: jsonData) + + XCTAssertNil(part.isThought) + guard case let .functionResponse(functionResponse) = part.data else { + XCTFail("Decoded part is not a functionResponse part.") + return + } + XCTAssertEqual(functionResponse.name, functionName) + XCTAssertEqual(functionResponse.response, ["output": .string("someValue")]) + } +} diff --git a/FirebaseAI/Tests/Unit/VertexComponentTests.swift b/FirebaseAI/Tests/Unit/VertexComponentTests.swift index 7202e01f4d6..702c6e50871 100644 --- a/FirebaseAI/Tests/Unit/VertexComponentTests.swift +++ b/FirebaseAI/Tests/Unit/VertexComponentTests.swift @@ -155,12 +155,14 @@ class VertexComponentTests: XCTestCase { let vertex1 = FirebaseAI.createInstance( app: VertexComponentTests.app, location: location, - apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1beta) + apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1beta), + useLimitedUseAppCheckTokens: false ) let vertex2 = FirebaseAI.createInstance( app: VertexComponentTests.app, location: location, - apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1) + apiConfig: APIConfig(service: .vertexAI(endpoint: .firebaseProxyProd), version: .v1), + useLimitedUseAppCheckTokens: false ) // Ensure they are different instances. @@ -181,7 +183,8 @@ class VertexComponentTests: XCTestCase { let vertex = FirebaseAI( app: app1, location: "transitory location", - apiConfig: FirebaseAI.defaultVertexAIAPIConfig + apiConfig: FirebaseAI.defaultVertexAIAPIConfig, + useLimitedUseAppCheckTokens: false ) weakVertex = vertex XCTAssertNotNil(weakVertex) @@ -208,7 +211,12 @@ class VertexComponentTests: XCTestCase { func testModelResourceName_developerAPI_generativeLanguage() throws { let app = try XCTUnwrap(VertexComponentTests.app) let apiConfig = APIConfig(service: .googleAI(endpoint: .googleAIBypassProxy), version: .v1beta) - let vertex = FirebaseAI.createInstance(app: app, location: nil, apiConfig: apiConfig) + let vertex = FirebaseAI.createInstance( + app: app, + location: nil, + apiConfig: apiConfig, + useLimitedUseAppCheckTokens: false + ) let model = "test-model-name" let modelResourceName = vertex.modelResourceName(modelName: model) @@ -222,7 +230,12 @@ class VertexComponentTests: XCTestCase { service: .googleAI(endpoint: .firebaseProxyStaging), version: .v1beta ) - let vertex = FirebaseAI.createInstance(app: app, location: nil, apiConfig: apiConfig) + let vertex = FirebaseAI.createInstance( + app: app, + location: nil, + apiConfig: apiConfig, + useLimitedUseAppCheckTokens: false + ) let model = "test-model-name" let projectID = vertex.firebaseInfo.projectID @@ -253,7 +266,12 @@ class VertexComponentTests: XCTestCase { service: .googleAI(endpoint: .firebaseProxyStaging), version: .v1beta ) - let vertex = FirebaseAI.createInstance(app: app, location: nil, apiConfig: apiConfig) + let vertex = FirebaseAI.createInstance( + app: app, + location: nil, + apiConfig: apiConfig, + useLimitedUseAppCheckTokens: false + ) let modelResourceName = vertex.modelResourceName(modelName: modelName) let expectedSystemInstruction = ModelContent(role: nil, parts: systemInstruction.parts) diff --git a/FirebaseAnalytics.podspec b/FirebaseAnalytics.podspec index 6beee36dd2c..67515303300 100644 --- a/FirebaseAnalytics.podspec +++ b/FirebaseAnalytics.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAnalytics' - s.version = '12.1.0' + s.version = '12.2.0' s.summary = 'Firebase Analytics for iOS' s.description = <<-DESC @@ -13,7 +13,7 @@ Pod::Spec.new do |s| s.authors = 'Google, Inc.' s.source = { - :http => 'https://dl.google.com/firebase/ios/analytics/f8b1e0fb38da4d8c/FirebaseAnalytics-12.1.0.tar.gz' + :http => 'https://dl.google.com/firebase/ios/analytics/20f7f19c421351ed/FirebaseAnalytics-12.2.0.tar.gz' } s.cocoapods_version = '>= 1.12.0' @@ -26,8 +26,8 @@ Pod::Spec.new do |s| s.libraries = 'c++', 'sqlite3', 'z' s.frameworks = 'StoreKit' - s.dependency 'FirebaseCore', '~> 12.1.0' - s.dependency 'FirebaseInstallations', '~> 12.1.0' + s.dependency 'FirebaseCore', '~> 12.2.0' + s.dependency 'FirebaseInstallations', '~> 12.2.0' s.dependency 'GoogleUtilities/AppDelegateSwizzler', '~> 8.1' s.dependency 'GoogleUtilities/MethodSwizzler', '~> 8.1' s.dependency 'GoogleUtilities/NSData+zlib', '~> 8.1' @@ -37,17 +37,17 @@ Pod::Spec.new do |s| s.default_subspecs = 'Default' s.subspec 'Default' do |ss| - ss.dependency 'GoogleAppMeasurement/Default', '12.1.0' + ss.dependency 'GoogleAppMeasurement/Default', '12.2.0' ss.vendored_frameworks = 'Frameworks/FirebaseAnalytics.xcframework' end s.subspec 'Core' do |ss| - ss.dependency 'GoogleAppMeasurement/Core', '12.1.0' + ss.dependency 'GoogleAppMeasurement/Core', '12.2.0' ss.vendored_frameworks = 'Frameworks/FirebaseAnalytics.xcframework' end s.subspec 'IdentitySupport' do |ss| - ss.dependency 'GoogleAppMeasurement/IdentitySupport', '12.1.0' + ss.dependency 'GoogleAppMeasurement/IdentitySupport', '12.2.0' ss.vendored_frameworks = 'Frameworks/FirebaseAnalytics.xcframework' end diff --git a/FirebaseAppCheck.podspec b/FirebaseAppCheck.podspec index 1aad2a45a7d..86d023c4c3d 100644 --- a/FirebaseAppCheck.podspec +++ b/FirebaseAppCheck.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAppCheck' - s.version = '12.1.0' + s.version = '12.2.0' s.summary = 'Firebase App Check SDK.' s.description = <<-DESC @@ -45,8 +45,8 @@ Pod::Spec.new do |s| s.tvos.weak_framework = 'DeviceCheck' s.dependency 'AppCheckCore', '~> 11.0' - s.dependency 'FirebaseAppCheckInterop', '~> 12.1.0' - s.dependency 'FirebaseCore', '~> 12.1.0' + s.dependency 'FirebaseAppCheckInterop', '~> 12.2.0' + s.dependency 'FirebaseCore', '~> 12.2.0' s.dependency 'GoogleUtilities/Environment', '~> 8.1' s.dependency 'GoogleUtilities/UserDefaults', '~> 8.1' diff --git a/FirebaseAppCheck/Interop/Public/FirebaseAppCheckInterop/FIRAppCheckTokenResultInterop.h b/FirebaseAppCheck/Interop/Public/FirebaseAppCheckInterop/FIRAppCheckTokenResultInterop.h index cb86a1bffe8..f87820b9790 100644 --- a/FirebaseAppCheck/Interop/Public/FirebaseAppCheckInterop/FIRAppCheckTokenResultInterop.h +++ b/FirebaseAppCheck/Interop/Public/FirebaseAppCheckInterop/FIRAppCheckTokenResultInterop.h @@ -18,6 +18,7 @@ NS_ASSUME_NONNULL_BEGIN +NS_SWIFT_SENDABLE @protocol FIRAppCheckTokenResultInterop /// App Check token in the case of success or a dummy token in the case of a failure. diff --git a/FirebaseAppCheck/Sources/Core/FIRAppCheckTokenResult.h b/FirebaseAppCheck/Sources/Core/FIRAppCheckTokenResult.h index 37b57b2adef..9cf6ddadec1 100644 --- a/FirebaseAppCheck/Sources/Core/FIRAppCheckTokenResult.h +++ b/FirebaseAppCheck/Sources/Core/FIRAppCheckTokenResult.h @@ -20,6 +20,7 @@ NS_ASSUME_NONNULL_BEGIN +NS_SWIFT_SENDABLE @interface FIRAppCheckTokenResult : NSObject - (instancetype)initWithToken:(NSString *)token error:(nullable NSError *)error; diff --git a/FirebaseAppCheckInterop.podspec b/FirebaseAppCheckInterop.podspec index 0f53cfb45f9..5b2a61f6134 100644 --- a/FirebaseAppCheckInterop.podspec +++ b/FirebaseAppCheckInterop.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAppCheckInterop' - s.version = '12.1.0' + s.version = '12.2.0' s.summary = 'Interfaces that allow other Firebase SDKs to use AppCheck functionality.' s.description = <<-DESC diff --git a/FirebaseAppDistribution.podspec b/FirebaseAppDistribution.podspec index f438f03a7e9..853a66272b6 100644 --- a/FirebaseAppDistribution.podspec +++ b/FirebaseAppDistribution.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAppDistribution' - s.version = '12.1.0-beta' + s.version = '12.2.0-beta' s.summary = 'App Distribution for Firebase iOS SDK.' s.description = <<-DESC @@ -30,10 +30,10 @@ iOS SDK for App Distribution for Firebase. ] s.public_header_files = base_dir + 'Public/FirebaseAppDistribution/*.h' - s.dependency 'FirebaseCore', '~> 12.1.0' + s.dependency 'FirebaseCore', '~> 12.2.0' s.dependency 'GoogleUtilities/AppDelegateSwizzler', '~> 8.1' s.dependency 'GoogleUtilities/UserDefaults', '~> 8.1' - s.dependency 'FirebaseInstallations', '~> 12.1.0' + s.dependency 'FirebaseInstallations', '~> 12.2.0' s.pod_target_xcconfig = { 'HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}"' diff --git a/FirebaseAuth.podspec b/FirebaseAuth.podspec index d89d0e222fb..e192db98da9 100644 --- a/FirebaseAuth.podspec +++ b/FirebaseAuth.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAuth' - s.version = '12.1.0' + s.version = '12.2.0' s.summary = 'Apple platform client for Firebase Authentication' s.description = <<-DESC @@ -55,10 +55,10 @@ supports email and password accounts, as well as several 3rd party authenticatio } s.framework = 'Security' s.ios.framework = 'SafariServices' - s.dependency 'FirebaseAuthInterop', '~> 12.1.0' - s.dependency 'FirebaseAppCheckInterop', '~> 12.1.0' - s.dependency 'FirebaseCore', '~> 12.1.0' - s.dependency 'FirebaseCoreExtension', '~> 12.1.0' + s.dependency 'FirebaseAuthInterop', '~> 12.2.0' + s.dependency 'FirebaseAppCheckInterop', '~> 12.2.0' + s.dependency 'FirebaseCore', '~> 12.2.0' + s.dependency 'FirebaseCoreExtension', '~> 12.2.0' s.dependency 'GoogleUtilities/AppDelegateSwizzler', '~> 8.1' s.dependency 'GoogleUtilities/Environment', '~> 8.1' s.dependency 'GTMSessionFetcher/Core', '>= 3.4', '< 6.0' diff --git a/FirebaseAuth/CHANGELOG.md b/FirebaseAuth/CHANGELOG.md index a0d6d7a2210..2b725886b45 100644 --- a/FirebaseAuth/CHANGELOG.md +++ b/FirebaseAuth/CHANGELOG.md @@ -1,3 +1,6 @@ +# 12.2.0 +- [added] Added TOTP support for macOS. + # 12.1.0 - [fixed] Fix a formatting issue with generated TOTP URLs that prevented them from working with the Google Authenticator app. (#15128) diff --git a/FirebaseAuth/README.md b/FirebaseAuth/README.md index 1dbb6f9cbc3..b055658e4c9 100644 --- a/FirebaseAuth/README.md +++ b/FirebaseAuth/README.md @@ -1,4 +1,4 @@ -# Firebase Auth for iOS +# Firebase Auth for iOS and macOS Firebase Auth enables apps to easily support multiple authentication options for their end users. diff --git a/FirebaseAuth/Sources/ObjC/FIRMultiFactorConstants.m b/FirebaseAuth/Sources/ObjC/FIRMultiFactorConstants.m index 4862a485884..85648f2fc3b 100644 --- a/FirebaseAuth/Sources/ObjC/FIRMultiFactorConstants.m +++ b/FirebaseAuth/Sources/ObjC/FIRMultiFactorConstants.m @@ -15,7 +15,7 @@ */ #import -#if TARGET_OS_IOS +#if TARGET_OS_IOS || TARGET_OS_OSX #import diff --git a/FirebaseAuth/Sources/Public/FirebaseAuth/FIRMultiFactor.h b/FirebaseAuth/Sources/Public/FirebaseAuth/FIRMultiFactor.h index aae0db8ab6f..da05c8309ce 100644 --- a/FirebaseAuth/Sources/Public/FirebaseAuth/FIRMultiFactor.h +++ b/FirebaseAuth/Sources/Public/FirebaseAuth/FIRMultiFactor.h @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -22,27 +22,26 @@ NS_ASSUME_NONNULL_BEGIN /** @typedef FIRMultiFactorSessionCallback @brief The callback that triggered when a developer calls `getSessionWithCompletion`. - This type is available on iOS only. + This type is available on iOS and macOS. @param session The multi factor session returned, if any. @param error The error which occurred, if any. */ typedef void (^FIRMultiFactorSessionCallback)(FIRMultiFactorSession *_Nullable session, NSError *_Nullable error) - NS_SWIFT_UNAVAILABLE("Use Swift's closure syntax instead.") - API_UNAVAILABLE(macos, tvos, watchos); + NS_SWIFT_UNAVAILABLE("Use Swift's closure syntax instead.") API_UNAVAILABLE(tvos, watchos); /** @brief The string identifier for using phone as a second factor. - This constant is available on iOS only. + This constant is available on iOS and macOS. */ extern NSString *const _Nonnull FIRPhoneMultiFactorID NS_SWIFT_NAME(PhoneMultiFactorID) - API_UNAVAILABLE(macos, tvos, watchos); + API_UNAVAILABLE(tvos, watchos); /** @brief The string identifier for using TOTP as a second factor. - This constant is available on iOS only. + This constant is available on iOS and macOS. */ extern NSString *const _Nonnull FIRTOTPMultiFactorID NS_SWIFT_NAME(TOTPMultiFactorID) - API_UNAVAILABLE(macos, tvos, watchos); + API_UNAVAILABLE(tvos, watchos); NS_ASSUME_NONNULL_END diff --git a/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift b/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift index 7a0c39340ae..55780c1ca89 100644 --- a/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift +++ b/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift @@ -99,9 +99,7 @@ final class AuthBackend: AuthBackendProtocol { } private static func generateMFAError(response: AuthRPCResponse, auth: Auth) -> Error? { - #if !os(iOS) - return nil - #else + #if os(iOS) || os(macOS) if let mfaResponse = response as? AuthMFAResponse, mfaResponse.idToken == nil, let enrollments = mfaResponse.mfaInfo { @@ -124,7 +122,9 @@ final class AuthBackend: AuthBackendProtocol { } else { return nil } - #endif // !os(iOS) + #else + return nil + #endif // os(iOS) || os(macOS) } // Check whether or not the successful response is actually the special case phone diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactor.swift b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactor.swift index 17ec6b18731..e227117c981 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactor.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactor.swift @@ -14,7 +14,7 @@ import Foundation -#if os(iOS) +#if os(iOS) || os(macOS) @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) extension MultiFactor: NSSecureCoding {} @@ -22,7 +22,7 @@ import Foundation /// The interface defining the multi factor related properties and operations pertaining to a /// user. /// - /// This class is available on iOS only. + /// This class is available on iOS and macOS. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @objc(FIRMultiFactor) open class MultiFactor: NSObject { @objc open var enrolledFactors: [MultiFactorInfo] diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorAssertion.swift b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorAssertion.swift index ff3edc94dd0..edfad7aaa58 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorAssertion.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorAssertion.swift @@ -14,12 +14,12 @@ import Foundation -#if os(iOS) +#if os(iOS) || os(macOS) /// The base class for asserting ownership of a second factor. This is equivalent to the /// AuthCredential class. /// - /// This class is available on iOS only. + /// This class is available on iOS and macOS. @objc(FIRMultiFactorAssertion) open class MultiFactorAssertion: NSObject { /// The second factor identifier for this opaque object asserting a second factor. @objc open var factorID: String diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorInfo.swift b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorInfo.swift index 839e405fc05..a1b134a7265 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorInfo.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorInfo.swift @@ -16,12 +16,12 @@ import Foundation // TODO(Swift 6 Breaking): Make checked Sendable. -#if os(iOS) +#if os(iOS) || os(macOS) extension MultiFactorInfo: NSSecureCoding {} /// Safe public structure used to represent a second factor entity from a client perspective. /// - /// This class is available on iOS only. + /// This class is available on iOS and macOS. @objc(FIRMultiFactorInfo) open class MultiFactorInfo: NSObject, @unchecked Sendable { /// The multi-factor enrollment ID. @objc(UID) public let uid: String diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorResolver.swift b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorResolver.swift index 223c1f9f5f5..374f86de88c 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorResolver.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorResolver.swift @@ -14,12 +14,12 @@ import Foundation -#if os(iOS) +#if os(iOS) || os(macOS) /// The subclass of base class `MultiFactorAssertion`, used to assert ownership of a phone /// second factor. /// - /// This class is available on iOS only. + /// This class is available on iOS and macOS. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @objc(FIRMultiFactorResolver) open class MultiFactorResolver: NSObject { diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorSession.swift b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorSession.swift index 33f7ef927ce..d22578a291a 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorSession.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorSession.swift @@ -14,7 +14,7 @@ import Foundation -#if os(iOS) +#if os(iOS) || os(macOS) /// Opaque object that identifies the current session to enroll a second factor or to /// complete sign in when previously enrolled. @@ -23,7 +23,7 @@ import Foundation /// or to complete sign in when previously enrolled. It contains additional context on the /// existing user, notably the confirmation that the user passed the first factor challenge. /// - /// This class is available on iOS only. + /// This class is available on iOS and macOS. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @objc(FIRMultiFactorSession) open class MultiFactorSession: NSObject { /// The ID token for an enroll flow. This has to be retrieved after recent authentication. diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/Phone/PhoneMultiFactorAssertion.swift b/FirebaseAuth/Sources/Swift/MultiFactor/Phone/PhoneMultiFactorAssertion.swift index 999809e4bd3..941b0c244f4 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/Phone/PhoneMultiFactorAssertion.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/Phone/PhoneMultiFactorAssertion.swift @@ -14,12 +14,12 @@ import Foundation -#if os(iOS) +#if os(iOS) || os(macOS) /// The subclass of base class FIRMultiFactorAssertion, used to assert ownership of a phone /// second factor. /// - /// This class is available on iOS only. + /// This class is available on iOS and macOS. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @objc(FIRPhoneMultiFactorAssertion) open class PhoneMultiFactorAssertion: MultiFactorAssertion { var authCredential: PhoneAuthCredential? diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/Phone/PhoneMultiFactorGenerator.swift b/FirebaseAuth/Sources/Swift/MultiFactor/Phone/PhoneMultiFactorGenerator.swift index cd213c14196..74c50abfb96 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/Phone/PhoneMultiFactorGenerator.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/Phone/PhoneMultiFactorGenerator.swift @@ -14,14 +14,14 @@ import Foundation -#if os(iOS) +#if os(iOS) || os(macOS) /// The data structure used to help initialize an assertion for a second factor entity to the /// Firebase Auth/CICP server. /// /// Depending on the type of second factor, this will help generate the assertion. /// - /// This class is available on iOS only. + /// This class is available on iOS and macOS. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @objc(FIRPhoneMultiFactorGenerator) open class PhoneMultiFactorGenerator: NSObject { diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/Phone/PhoneMultiFactorInfo.swift b/FirebaseAuth/Sources/Swift/MultiFactor/Phone/PhoneMultiFactorInfo.swift index b847407c48a..4ec36505dac 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/Phone/PhoneMultiFactorInfo.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/Phone/PhoneMultiFactorInfo.swift @@ -16,13 +16,13 @@ import Foundation // TODO(Swift 6 Breaking): Make checked Sendable. -#if os(iOS) +#if os(iOS) || os(macOS) /// Extends the MultiFactorInfo class for phone number second factors. /// /// The identifier of this second factor is "phone". /// - /// This class is available on iOS only. + /// This class is available on iOS and macOS. @objc(FIRPhoneMultiFactorInfo) open class PhoneMultiFactorInfo: MultiFactorInfo, @unchecked Sendable { /// The string identifier for using phone as a second factor. diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultFactorAssertion.swift b/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultFactorAssertion.swift index b5b1c43f3d6..dd64e4319af 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultFactorAssertion.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultFactorAssertion.swift @@ -14,7 +14,7 @@ import Foundation -#if os(iOS) +#if os(iOS) || os(macOS) enum SecretOrID { case secret(TOTPSecret) @@ -24,7 +24,7 @@ import Foundation /// The subclass of base class MultiFactorAssertion, used to assert ownership of a TOTP /// (Time-based One Time Password) second factor. /// - /// This class is available on iOS only. + /// This class is available on iOS and macOS. @objc(FIRTOTPMultiFactorAssertion) open class TOTPMultiFactorAssertion: MultiFactorAssertion { let oneTimePassword: String let secretOrID: SecretOrID diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultiFactorGenerator.swift b/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultiFactorGenerator.swift index bf3b07634ca..c24dedb3b22 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultiFactorGenerator.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultiFactorGenerator.swift @@ -14,13 +14,13 @@ import Foundation -#if os(iOS) +#if os(iOS) || os(macOS) /// The data structure used to help initialize an assertion for a second factor entity to the /// Firebase Auth/CICP server. Depending on the type of second factor, this will help generate /// the assertion. /// - /// This class is available on iOS only. + /// This class is available on iOS and macOS. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @objc(FIRTOTPMultiFactorGenerator) open class TOTPMultiFactorGenerator: NSObject { /// Creates a TOTP secret as part of enrolling a TOTP second factor. Used for generating a diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultiFactorInfo.swift b/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultiFactorInfo.swift index dbb2eeb7042..b252f4fd8c6 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultiFactorInfo.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultiFactorInfo.swift @@ -17,13 +17,13 @@ import Foundation // TODO(Swift 6 Breaking): Make checked Sendable. Also, does this need // to be public? -#if os(iOS) +#if os(iOS) || os(macOS) /// Extends the MultiFactorInfo class for time based one-time password second factors. /// /// The identifier of this second factor is "totp". /// - /// This class is available on iOS only. + /// This class is available on iOS and macOS. class TOTPMultiFactorInfo: MultiFactorInfo, @unchecked Sendable { /// Initialize the AuthProtoMFAEnrollment instance with proto. /// - Parameter proto: AuthProtoMFAEnrollment proto object. diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPSecret.swift b/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPSecret.swift index b9f1d913920..1f4b3f9e125 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPSecret.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPSecret.swift @@ -19,13 +19,17 @@ import Foundation internal import GoogleUtilities_Environment #endif -#if os(iOS) - import UIKit +#if os(iOS) || os(macOS) + #if os(iOS) + import UIKit + #elseif os(macOS) + import AppKit + #endif /// The subclass of base class MultiFactorAssertion, used to assert ownership of a TOTP /// (Time-based One Time Password) second factor. /// - /// This class is available on iOS only. + /// This class is available on iOS and macOS. @objc(FIRTOTPSecret) open class TOTPSecret: NSObject { /// Returns the shared secret key/seed used to generate time-based one-time passwords. @objc open func sharedSecretKey() -> String { @@ -57,24 +61,33 @@ import Foundation @MainActor @objc(openInOTPAppWithQRCodeURL:) open func openInOTPApp(withQRCodeURL qrCodeURL: String) { if GULAppEnvironmentUtil.isAppExtension() { - // iOS App extensions should not call [UIApplication sharedApplication], even if - // UIApplication responds to it. + // App extensions should not call [UIApplication sharedApplication] or [NSWorkspace + // sharedWorkspace], even if they respond to it. return } - // Using reflection here to avoid build errors in extensions. - let sel = NSSelectorFromString("sharedApplication") - guard UIApplication.responds(to: sel), - let rawApplication = UIApplication.perform(sel), - let application = rawApplication.takeUnretainedValue() as? UIApplication else { - return - } - if let url = URL(string: qrCodeURL), application.canOpenURL(url) { - application.open(url, options: [:], completionHandler: nil) - } else { - AuthLog.logError(code: "I-AUT000019", - message: "URL: \(qrCodeURL) cannot be opened") - } + #if os(iOS) + // Using reflection here to avoid build errors in extensions. + let sel = NSSelectorFromString("sharedApplication") + guard UIApplication.responds(to: sel), + let rawApplication = UIApplication.perform(sel), + let application = rawApplication.takeUnretainedValue() as? UIApplication else { + return + } + if let url = URL(string: qrCodeURL), application.canOpenURL(url) { + application.open(url, options: [:], completionHandler: nil) + } else { + AuthLog.logError(code: "I-AUT000019", + message: "URL: \(qrCodeURL) cannot be opened") + } + #elseif os(macOS) + if let url = URL(string: qrCodeURL) { + NSWorkspace.shared.open(url) + } else { + AuthLog.logError(code: "I-AUT000019", + message: "URL: \(qrCodeURL) cannot be opened") + } + #endif } /// Shared secret key/seed used for enrolling in TOTP MFA and generating OTPs. diff --git a/FirebaseAuth/Sources/Swift/User/User.swift b/FirebaseAuth/Sources/Swift/User/User.swift index 4ef324e177c..ea006baa418 100644 --- a/FirebaseAuth/Sources/Swift/User/User.swift +++ b/FirebaseAuth/Sources/Swift/User/User.swift @@ -58,10 +58,10 @@ extension User: NSSecureCoding {} /// The tenant ID of the current user. `nil` if none is available. @objc public private(set) var tenantID: String? - #if os(iOS) + #if os(iOS) || os(macOS) /// Multi factor object associated with the user. /// - /// This property is available on iOS only. + /// This property is available on iOS and macOS. @objc public private(set) var multiFactor: MultiFactor #endif @@ -1066,7 +1066,7 @@ extension User: NSSecureCoding {} isEmailVerified = false metadata = UserMetadata(withCreationDate: nil, lastSignInDate: nil) tenantID = nil - #if os(iOS) + #if os(iOS) || os(macOS) multiFactor = MultiFactor(withMFAEnrollments: []) #endif uid = "" @@ -1297,7 +1297,7 @@ extension User: NSSecureCoding {} } } providerDataRaw = providerData - #if os(iOS) + #if os(iOS) || os(macOS) if let enrollments = user.mfaEnrollments { multiFactor = MultiFactor(withMFAEnrollments: enrollments) } @@ -1718,7 +1718,7 @@ extension User: NSSecureCoding {} coder.encode(auth.requestConfiguration.appID, forKey: kFirebaseAppIDCodingKey) } coder.encode(tokenService, forKey: kTokenServiceCodingKey) - #if os(iOS) + #if os(iOS) || os(macOS) coder.encode(multiFactor, forKey: kMultiFactorCodingKey) #endif } @@ -1747,7 +1747,7 @@ extension User: NSSecureCoding {} as? [String: UserInfoImpl] let metadata = coder.decodeObject(of: UserMetadata.self, forKey: kMetadataCodingKey) let tenantID = coder.decodeObject(of: NSString.self, forKey: kTenantIDCodingKey) as? String - #if os(iOS) + #if os(iOS) || os(macOS) let multiFactor = coder.decodeObject(of: MultiFactor.self, forKey: kMultiFactorCodingKey) #endif self.tokenService = tokenService @@ -1778,7 +1778,7 @@ extension User: NSSecureCoding {} backend = AuthBackend(rpcIssuer: AuthBackendRPCIssuer()) userProfileUpdate = UserProfileUpdate() - #if os(iOS) + #if os(iOS) || os(macOS) self.multiFactor = multiFactor ?? MultiFactor() super.init() multiFactor?.user = self diff --git a/FirebaseAuth/Sources/Swift/Utilities/AuthErrorUtils.swift b/FirebaseAuth/Sources/Swift/Utilities/AuthErrorUtils.swift index 5c78b223ab4..17d76c810a7 100644 --- a/FirebaseAuth/Sources/Swift/Utilities/AuthErrorUtils.swift +++ b/FirebaseAuth/Sources/Swift/Utilities/AuthErrorUtils.swift @@ -568,7 +568,7 @@ class AuthErrorUtils { return error(code: .blockingCloudFunctionError, message: errorMessage) } - #if os(iOS) + #if os(iOS) || os(macOS) static func secondFactorRequiredError(pendingCredential: String?, hints: [MultiFactorInfo], auth: Auth) @@ -581,7 +581,7 @@ class AuthErrorUtils { return error(code: .secondFactorRequired, userInfo: userInfo) } - #endif // os(iOS) + #endif // os(iOS) || os(macOS) static func recaptchaSDKNotLinkedError() -> Error { // TODO(ObjC): point the link to GCIP doc once available. diff --git a/FirebaseAuth/Tests/Unit/ObjCAPITests.m b/FirebaseAuth/Tests/Unit/ObjCAPITests.m index 6784beb8548..d6a528d8930 100644 --- a/FirebaseAuth/Tests/Unit/ObjCAPITests.m +++ b/FirebaseAuth/Tests/Unit/ObjCAPITests.m @@ -372,7 +372,7 @@ - (void)FIRGoogleAuthProvider_h { accessToken:@"token"]; } -#if TARGET_OS_IOS +#if TARGET_OS_IOS || TARGET_OS_OSX - (void)FIRMultiFactor_h:(FIRMultiFactor *)mf mfa:(FIRMultiFactorAssertion *)mfa { [mf getSessionWithCompletion:^(FIRMultiFactorSession *_Nullable credential, NSError *_Nullable error){ @@ -466,7 +466,9 @@ - (void)FIRPhoneAuthProvider_h:(FIRPhoneAuthCredential *)credential { - (void)phoneMultiFactorInfo:(FIRPhoneMultiFactorInfo *)info { __unused NSString *s = [info phoneNumber]; } +#endif +#if TARGET_OS_IOS || TARGET_OS_OSX - (void)FIRTOTPSecret_h:(FIRTOTPSecret *)secret { NSString *s = [secret sharedSecretKey]; s = [secret generateQRCodeURLWithAccountName:@"name" issuer:@"issuer"]; @@ -571,7 +573,7 @@ - (void)userProperties:(FIRUser *)user { b = [user isEmailVerified]; __unused NSArray *> *userInfo = [user providerData]; __unused FIRUserMetadata *meta = [user metadata]; -#if TARGET_OS_IOS +#if TARGET_OS_IOS || TARGET_OS_OSX __unused FIRMultiFactor *mf = [user multiFactor]; #endif NSString *s = [user refreshToken]; diff --git a/FirebaseAuth/Tests/Unit/ObjCGlobalTests.m b/FirebaseAuth/Tests/Unit/ObjCGlobalTests.m index 59fe0718b5e..62ab96666c5 100644 --- a/FirebaseAuth/Tests/Unit/ObjCGlobalTests.m +++ b/FirebaseAuth/Tests/Unit/ObjCGlobalTests.m @@ -41,9 +41,11 @@ - (void)GlobalSymbolBuildTest { s = FIRGoogleAuthSignInMethod; #if TARGET_OS_IOS s = FIRPhoneMultiFactorID; - s = FIRTOTPMultiFactorID; s = FIRPhoneAuthProviderID; s = FIRPhoneAuthSignInMethod; +#endif +#if TARGET_OS_IOS || TARGET_OS_OSX + s = FIRTOTPMultiFactorID; #endif s = FIRTwitterAuthProviderID; s = FIRTwitterAuthSignInMethod; diff --git a/FirebaseAuth/Tests/Unit/SwiftAPI.swift b/FirebaseAuth/Tests/Unit/SwiftAPI.swift index db002ccd599..fc6c2f466da 100644 --- a/FirebaseAuth/Tests/Unit/SwiftAPI.swift +++ b/FirebaseAuth/Tests/Unit/SwiftAPI.swift @@ -548,7 +548,9 @@ class AuthAPI_hOnlyTests: XCTestCase { func phoneMultiFactorInfo(mfi: PhoneMultiFactorInfo) { let _: String = mfi.phoneNumber } + #endif + #if os(iOS) || os(macOS) func FIRTOTPSecret_h(session: MultiFactorSession) async throws { let obj = try await TOTPMultiFactorGenerator.generateSecret(with: session) _ = obj.sharedSecretKey() @@ -638,7 +640,7 @@ class AuthAPI_hOnlyTests: XCTestCase { let _: Bool = user.isEmailVerified let _: [UserInfo] = user.providerData let _: UserMetadata = user.metadata - #if os(iOS) + #if os(iOS) || os(macOS) let _: MultiFactor = user.multiFactor #endif if let _: String = user.refreshToken, diff --git a/FirebaseAuth/Tests/Unit/SwiftGlobalTests.swift b/FirebaseAuth/Tests/Unit/SwiftGlobalTests.swift index f0956d54aa3..8d863cb1a58 100644 --- a/FirebaseAuth/Tests/Unit/SwiftGlobalTests.swift +++ b/FirebaseAuth/Tests/Unit/SwiftGlobalTests.swift @@ -38,9 +38,10 @@ class SwiftGlobalTests: XCTestCase { let _: String = GitHubAuthSignInMethod let _: String = GoogleAuthProviderID let _: String = GoogleAuthSignInMethod - #if os(iOS) - let _: String = PhoneMultiFactorID + #if os(iOS) || os(macOS) let _: String = TOTPMultiFactorID + #endif + #if os(iOS) let _: String = PhoneAuthProviderID let _: String = PhoneAuthSignInMethod #endif diff --git a/FirebaseAuth/Tests/Unit/UserTests.swift b/FirebaseAuth/Tests/Unit/UserTests.swift index c610e04a0bc..24f426d1bd8 100644 --- a/FirebaseAuth/Tests/Unit/UserTests.swift +++ b/FirebaseAuth/Tests/Unit/UserTests.swift @@ -135,6 +135,27 @@ class UserTests: RPCBaseTests { ]) #endif + var mfaInfo: [[AnyHashable: AnyHashable]] = [] + + #if os(iOS) + mfaInfo.append([ + "phoneInfo": kPhoneInfo, + "mfaEnrollmentId": kEnrollmentID, + "displayName": kDisplayName, + "enrolledAt": kEnrolledAt, + ]) + #endif + + #if os(iOS) || os(macOS) + mfaInfo.append([ + // In practice, this will be an empty dictionary. + "totpInfo": [AnyHashable: AnyHashable](), + "mfaEnrollmentId": kEnrollmentID, + "displayName": kDisplayName, + "enrolledAt": kEnrolledAt, + ]) + #endif + rpcIssuer?.fakeGetAccountProviderJSON = [[ kProviderUserInfoKey: providerUserInfos, kLocalIDKey: kLocalID, @@ -146,21 +167,7 @@ class UserTests: RPCBaseTests { "phoneNumber": kPhoneNumber, "createdAt": String(Int(kCreationDateTimeIntervalInSeconds) * 1000), // to nanoseconds "lastLoginAt": String(Int(kLastSignInDateTimeIntervalInSeconds) * 1000), - "mfaInfo": [ - [ - "phoneInfo": kPhoneInfo, - "mfaEnrollmentId": kEnrollmentID, - "displayName": kDisplayName, - "enrolledAt": kEnrolledAt, - ], - [ - // In practice, this will be an empty dictionary. - "totpInfo": [AnyHashable: AnyHashable](), - "mfaEnrollmentId": kEnrollmentID, - "displayName": kDisplayName, - "enrolledAt": kEnrolledAt, - ] as [AnyHashable: AnyHashable], - ], + "mfaInfo": mfaInfo, ]] let expectation = self.expectation(description: #function) @@ -247,9 +254,12 @@ class UserTests: RPCBaseTests { var encodedClasses = [User.self, NSDictionary.self, NSURL.self, SecureTokenService.self, UserInfoImpl.self, NSDate.self, UserMetadata.self, NSString.self, NSArray.self] - #if os(iOS) + #if os(iOS) || os(macOS) encodedClasses.append(MultiFactor.self) - encodedClasses.append(PhoneMultiFactorInfo.self) + encodedClasses.append(TOTPMultiFactorInfo.self) + #if os(iOS) + encodedClasses.append(PhoneMultiFactorInfo.self) + #endif #endif let unarchivedUser = try XCTUnwrap(NSKeyedUnarchiver.unarchivedObject( @@ -370,6 +380,17 @@ class UserTests: RPCBaseTests { XCTAssertEqual("\(date)", kEnrolledAtMatch) } #endif + + #if os(macOS) + // Verify TOTP MultiFactorInfo properties. + let enrolledFactors = try XCTUnwrap(user.multiFactor.enrolledFactors) + XCTAssertEqual(enrolledFactors.count, 1) + XCTAssertEqual(enrolledFactors[0].factorID, PhoneMultiFactorInfo.TOTPMultiFactorID) + XCTAssertEqual(enrolledFactors[0].uid, kEnrollmentID) + XCTAssertEqual(enrolledFactors[0].displayName, self.kDisplayName) + let date = try XCTUnwrap(enrolledFactors[0].enrollmentDate) + XCTAssertEqual("\(date)", kEnrolledAtMatch) + #endif } catch { XCTFail("Caught an error in \(#function): \(error)") } diff --git a/FirebaseAuthInterop.podspec b/FirebaseAuthInterop.podspec index e20e453e484..c5c5877c8d1 100644 --- a/FirebaseAuthInterop.podspec +++ b/FirebaseAuthInterop.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAuthInterop' - s.version = '12.1.0' + s.version = '12.2.0' s.summary = 'Interfaces that allow other Firebase SDKs to use Auth functionality.' s.description = <<-DESC diff --git a/FirebaseCombineSwift.podspec b/FirebaseCombineSwift.podspec index 22fda02d2fe..e25c93a9bdf 100644 --- a/FirebaseCombineSwift.podspec +++ b/FirebaseCombineSwift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseCombineSwift' - s.version = '12.1.0' + s.version = '12.2.0' s.summary = 'Swift extensions with Combine support for Firebase' s.description = <<-DESC @@ -51,11 +51,11 @@ for internal testing only. It should not be published. s.osx.framework = 'AppKit' s.tvos.framework = 'UIKit' - s.dependency 'FirebaseCore', '~> 12.1.0' - s.dependency 'FirebaseAuth', '~> 12.1.0' - s.dependency 'FirebaseFunctions', '~> 12.1.0' - s.dependency 'FirebaseFirestore', '~> 12.1.0' - s.dependency 'FirebaseStorage', '~> 12.1.0' + s.dependency 'FirebaseCore', '~> 12.2.0' + s.dependency 'FirebaseAuth', '~> 12.2.0' + s.dependency 'FirebaseFunctions', '~> 12.2.0' + s.dependency 'FirebaseFirestore', '~> 12.2.0' + s.dependency 'FirebaseStorage', '~> 12.2.0' s.pod_target_xcconfig = { 'HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}"', @@ -104,6 +104,6 @@ for internal testing only. It should not be published. int_tests.resources = 'FirebaseStorage/Tests/Integration/Resources/1mb.dat', 'FirebaseStorage/Tests/Integration/Resources/GoogleService-Info.plist', 'FirebaseStorage/Tests/Integration/Resources/HomeImprovement.numbers' - int_tests.dependency 'FirebaseAuth', '~> 12.1.0' + int_tests.dependency 'FirebaseAuth', '~> 12.2.0' end end diff --git a/FirebaseCombineSwift/Sources/Auth/MultiFactor+Combine.swift b/FirebaseCombineSwift/Sources/Auth/MultiFactor+Combine.swift index 4fd850da1e6..b7f8a790dff 100644 --- a/FirebaseCombineSwift/Sources/Auth/MultiFactor+Combine.swift +++ b/FirebaseCombineSwift/Sources/Auth/MultiFactor+Combine.swift @@ -12,13 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if os(iOS) || targetEnvironment(macCatalyst) +#if os(iOS) || targetEnvironment(macCatalyst) || os(macOS) import Combine import FirebaseAuth - @available(iOS 13.0, macCatalyst 13.0, *) - @available(macOS, unavailable) + @available(iOS 13.0, macCatalyst 13.0, macOS 10.15, *) @available(tvOS, unavailable) @available(watchOS, unavailable) public extension MultiFactor { @@ -111,4 +110,4 @@ } } -#endif // os(iOS) || targetEnvironment(macCatalyst) +#endif // os(iOS) || targetEnvironment(macCatalyst) || os(macOS) diff --git a/FirebaseCombineSwift/Sources/Auth/MultiFactorResolver+Combine.swift b/FirebaseCombineSwift/Sources/Auth/MultiFactorResolver+Combine.swift index b2ada743c66..a94e3b7cd82 100644 --- a/FirebaseCombineSwift/Sources/Auth/MultiFactorResolver+Combine.swift +++ b/FirebaseCombineSwift/Sources/Auth/MultiFactorResolver+Combine.swift @@ -12,13 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -#if os(iOS) || targetEnvironment(macCatalyst) +#if os(iOS) || targetEnvironment(macCatalyst) || os(macOS) import Combine import FirebaseAuth - @available(iOS 13.0, macCatalyst 13.0, *) - @available(macOS, unavailable) + @available(iOS 13.0, macCatalyst 13.0, macOS 10.15, *) @available(tvOS, unavailable) @available(watchOS, unavailable) public extension MultiFactorResolver { @@ -43,4 +42,4 @@ } } -#endif // os(iOS) || targetEnvironment(macCatalyst) +#endif // os(iOS) || targetEnvironment(macCatalyst) || os(macOS) diff --git a/FirebaseCore.podspec b/FirebaseCore.podspec index 9767f1c893e..77c336ba8c4 100644 --- a/FirebaseCore.podspec +++ b/FirebaseCore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseCore' - s.version = '12.1.0' + s.version = '12.2.0' s.summary = 'Firebase Core' s.description = <<-DESC @@ -53,7 +53,7 @@ Firebase Core includes FIRApp and FIROptions which provide central configuration # Remember to also update version in `cmake/external/GoogleUtilities.cmake` s.dependency 'GoogleUtilities/Environment', '~> 8.1' s.dependency 'GoogleUtilities/Logger', '~> 8.1' - s.dependency 'FirebaseCoreInternal', '~> 12.1.0' + s.dependency 'FirebaseCoreInternal', '~> 12.2.0' s.pod_target_xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => 'Firebase_VERSION=' + s.version.to_s, diff --git a/FirebaseCoreExtension.podspec b/FirebaseCoreExtension.podspec index 4458e7e9cd3..b7c893d25c1 100644 --- a/FirebaseCoreExtension.podspec +++ b/FirebaseCoreExtension.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseCoreExtension' - s.version = '12.1.0' + s.version = '12.2.0' s.summary = 'Extended FirebaseCore APIs for Firebase product SDKs' s.description = <<-DESC @@ -34,5 +34,5 @@ Pod::Spec.new do |s| "#{s.module_name}_Privacy" => 'FirebaseCore/Extension/Resources/PrivacyInfo.xcprivacy' } - s.dependency 'FirebaseCore', '~> 12.1.0' + s.dependency 'FirebaseCore', '~> 12.2.0' end diff --git a/FirebaseCoreInternal.podspec b/FirebaseCoreInternal.podspec index e75d03cb018..a5832bb6e8e 100644 --- a/FirebaseCoreInternal.podspec +++ b/FirebaseCoreInternal.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseCoreInternal' - s.version = '12.1.0' + s.version = '12.2.0' s.summary = 'APIs for internal FirebaseCore usage.' s.description = <<-DESC diff --git a/FirebaseCrashlytics.podspec b/FirebaseCrashlytics.podspec index 22b9cc1e163..8675001983c 100644 --- a/FirebaseCrashlytics.podspec +++ b/FirebaseCrashlytics.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseCrashlytics' - s.version = '12.1.0' + s.version = '12.2.0' s.summary = 'Best and lightest-weight crash reporting for mobile, desktop and tvOS.' s.description = 'Firebase Crashlytics helps you track, prioritize, and fix stability issues that erode app quality.' s.homepage = 'https://firebase.google.com/' @@ -59,10 +59,10 @@ Pod::Spec.new do |s| cp -f ./Crashlytics/CrashlyticsInputFiles.xcfilelist ./CrashlyticsInputFiles.xcfilelist PREPARE_COMMAND_END - s.dependency 'FirebaseCore', '~> 12.1.0' - s.dependency 'FirebaseInstallations', '~> 12.1.0' - s.dependency 'FirebaseSessions', '~> 12.1.0' - s.dependency 'FirebaseRemoteConfigInterop', '~> 12.1.0' + s.dependency 'FirebaseCore', '~> 12.2.0' + s.dependency 'FirebaseInstallations', '~> 12.2.0' + s.dependency 'FirebaseSessions', '~> 12.2.0' + s.dependency 'FirebaseRemoteConfigInterop', '~> 12.2.0' s.dependency 'PromisesObjC', '~> 2.4' s.dependency 'GoogleDataTransport', '~> 10.1' s.dependency 'GoogleUtilities/Environment', '~> 8.1' diff --git a/FirebaseDataConnect/README.md b/FirebaseDataConnect/README.md new file mode 100644 index 00000000000..aa3562eb19e --- /dev/null +++ b/FirebaseDataConnect/README.md @@ -0,0 +1,5 @@ +# Firebase Data Connect Swift SDK + +**Connect your Swift & SwiftUI apps directly to a managed Google CloudSQL (PostgreSQL) database.** + +The SDK resides in a separate [git repo](https://github.com/firebase/data-connect-ios-sdk) diff --git a/FirebaseDatabase.podspec b/FirebaseDatabase.podspec index 4ab36e76ed3..86931787c3b 100644 --- a/FirebaseDatabase.podspec +++ b/FirebaseDatabase.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseDatabase' - s.version = '12.1.0' + s.version = '12.2.0' s.summary = 'Firebase Realtime Database' s.description = <<-DESC @@ -48,9 +48,9 @@ Simplify your iOS development, grow your user base, and monetize more effectivel s.macos.frameworks = 'CFNetwork', 'Security', 'SystemConfiguration' s.watchos.frameworks = 'CFNetwork', 'Security', 'WatchKit' s.dependency 'leveldb-library', '~> 1.22' - s.dependency 'FirebaseCore', '~> 12.1.0' - s.dependency 'FirebaseAppCheckInterop', '~> 12.1.0' - s.dependency 'FirebaseSharedSwift', '~> 12.1.0' + s.dependency 'FirebaseCore', '~> 12.2.0' + s.dependency 'FirebaseAppCheckInterop', '~> 12.2.0' + s.dependency 'FirebaseSharedSwift', '~> 12.2.0' s.dependency 'GoogleUtilities/UserDefaults', '~> 8.1' s.pod_target_xcconfig = { 'HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}"' @@ -72,7 +72,7 @@ Simplify your iOS development, grow your user base, and monetize more effectivel 'SharedTestUtilities/FIRComponentTestUtilities.[mh]', 'SharedTestUtilities/FIROptionsMock.[mh]', ] - unit_tests.dependency 'FirebaseAppCheckInterop', '~> 12.1.0' + unit_tests.dependency 'FirebaseAppCheckInterop', '~> 12.2.0' unit_tests.dependency 'OCMock' unit_tests.resources = 'FirebaseDatabase/Tests/Resources/syncPointSpec.json', 'FirebaseDatabase/Tests/Resources/GoogleService-Info.plist' diff --git a/FirebaseFirestore.podspec b/FirebaseFirestore.podspec index 42a937b7681..df500387412 100644 --- a/FirebaseFirestore.podspec +++ b/FirebaseFirestore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseFirestore' - s.version = '12.1.0' + s.version = '12.2.0' s.summary = 'Google Cloud Firestore' s.description = <<-DESC Google Cloud Firestore is a NoSQL document database built for automatic scaling, high performance, and ease of application development. @@ -35,9 +35,9 @@ Google Cloud Firestore is a NoSQL document database built for automatic scaling, "#{s.module_name}_Privacy" => 'Firestore/Swift/Source/Resources/PrivacyInfo.xcprivacy' } - s.dependency 'FirebaseCore', '~> 12.1.0' - s.dependency 'FirebaseCoreExtension', '~> 12.1.0' - s.dependency 'FirebaseFirestoreInternal', '~> 12.1.0' - s.dependency 'FirebaseSharedSwift', '~> 12.1.0' + s.dependency 'FirebaseCore', '~> 12.2.0' + s.dependency 'FirebaseCoreExtension', '~> 12.2.0' + s.dependency 'FirebaseFirestoreInternal', '~> 12.2.0' + s.dependency 'FirebaseSharedSwift', '~> 12.2.0' end diff --git a/FirebaseFirestoreInternal.podspec b/FirebaseFirestoreInternal.podspec index 57e9ce8bfd1..b2881725ecb 100644 --- a/FirebaseFirestoreInternal.podspec +++ b/FirebaseFirestoreInternal.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseFirestoreInternal' - s.version = '12.1.0' + s.version = '12.2.0' s.summary = 'Google Cloud Firestore' s.description = <<-DESC @@ -91,8 +91,8 @@ Google Cloud Firestore is a NoSQL document database built for automatic scaling, "#{s.module_name}_Privacy" => 'Firestore/Source/Resources/PrivacyInfo.xcprivacy' } - s.dependency 'FirebaseAppCheckInterop', '~> 12.1.0' - s.dependency 'FirebaseCore', '~> 12.1.0' + s.dependency 'FirebaseAppCheckInterop', '~> 12.2.0' + s.dependency 'FirebaseCore', '~> 12.2.0' abseil_version = '~> 1.20240722.0' s.dependency 'abseil/algorithm', abseil_version diff --git a/FirebaseFunctions.podspec b/FirebaseFunctions.podspec index b26ef279af2..52bbf0f26c1 100644 --- a/FirebaseFunctions.podspec +++ b/FirebaseFunctions.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseFunctions' - s.version = '12.1.0' + s.version = '12.2.0' s.summary = 'Cloud Functions for Firebase' s.description = <<-DESC @@ -35,12 +35,12 @@ Cloud Functions for Firebase. 'FirebaseFunctions/Sources/**/*.swift', ] - s.dependency 'FirebaseCore', '~> 12.1.0' - s.dependency 'FirebaseCoreExtension', '~> 12.1.0' - s.dependency 'FirebaseAppCheckInterop', '~> 12.1.0' - s.dependency 'FirebaseAuthInterop', '~> 12.1.0' - s.dependency 'FirebaseMessagingInterop', '~> 12.1.0' - s.dependency 'FirebaseSharedSwift', '~> 12.1.0' + s.dependency 'FirebaseCore', '~> 12.2.0' + s.dependency 'FirebaseCoreExtension', '~> 12.2.0' + s.dependency 'FirebaseAppCheckInterop', '~> 12.2.0' + s.dependency 'FirebaseAuthInterop', '~> 12.2.0' + s.dependency 'FirebaseMessagingInterop', '~> 12.2.0' + s.dependency 'FirebaseSharedSwift', '~> 12.2.0' s.dependency 'GTMSessionFetcher/Core', '>= 3.4', '< 6.0' s.test_spec 'objc' do |objc_tests| diff --git a/FirebaseInAppMessaging.podspec b/FirebaseInAppMessaging.podspec index ef773f8c441..ed309155b99 100644 --- a/FirebaseInAppMessaging.podspec +++ b/FirebaseInAppMessaging.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseInAppMessaging' - s.version = '12.1.0-beta' + s.version = '12.2.0-beta' s.summary = 'Firebase In-App Messaging for iOS' s.description = <<-DESC @@ -80,9 +80,9 @@ See more product details at https://firebase.google.com/products/in-app-messagin s.framework = 'UIKit' - s.dependency 'FirebaseCore', '~> 12.1.0' - s.dependency 'FirebaseInstallations', '~> 12.1.0' - s.dependency 'FirebaseABTesting', '~> 12.1.0' + s.dependency 'FirebaseCore', '~> 12.2.0' + s.dependency 'FirebaseInstallations', '~> 12.2.0' + s.dependency 'FirebaseABTesting', '~> 12.2.0' s.dependency 'GoogleUtilities/Environment', '~> 8.1' s.dependency 'GoogleUtilities/UserDefaults', '~> 8.1' s.dependency 'nanopb', '~> 3.30910.0' diff --git a/FirebaseInstallations.podspec b/FirebaseInstallations.podspec index 87b49973f1d..217fe097416 100644 --- a/FirebaseInstallations.podspec +++ b/FirebaseInstallations.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseInstallations' - s.version = '12.1.0' + s.version = '12.2.0' s.summary = 'Firebase Installations' s.description = <<-DESC @@ -45,7 +45,7 @@ Pod::Spec.new do |s| } s.framework = 'Security' - s.dependency 'FirebaseCore', '~> 12.1.0' + s.dependency 'FirebaseCore', '~> 12.2.0' s.dependency 'PromisesObjC', '~> 2.4' s.dependency 'GoogleUtilities/Environment', '~> 8.1' s.dependency 'GoogleUtilities/UserDefaults', '~> 8.1' diff --git a/FirebaseMLModelDownloader.podspec b/FirebaseMLModelDownloader.podspec index 7fa88c88c95..1c19d2e8746 100644 --- a/FirebaseMLModelDownloader.podspec +++ b/FirebaseMLModelDownloader.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseMLModelDownloader' - s.version = '12.1.0-beta' + s.version = '12.2.0-beta' s.summary = 'Firebase ML Model Downloader' s.description = <<-DESC @@ -36,9 +36,9 @@ Pod::Spec.new do |s| ] s.framework = 'Foundation' - s.dependency 'FirebaseCore', '~> 12.1.0' - s.dependency 'FirebaseCoreExtension', '~> 12.1.0' - s.dependency 'FirebaseInstallations', '~> 12.1.0' + s.dependency 'FirebaseCore', '~> 12.2.0' + s.dependency 'FirebaseCoreExtension', '~> 12.2.0' + s.dependency 'FirebaseInstallations', '~> 12.2.0' s.dependency 'GoogleDataTransport', '~> 10.1' s.dependency 'GoogleUtilities/UserDefaults', '~> 8.1' s.dependency 'SwiftProtobuf', '~> 1.19' diff --git a/FirebaseMessaging.podspec b/FirebaseMessaging.podspec index 96b25677379..06f138db888 100644 --- a/FirebaseMessaging.podspec +++ b/FirebaseMessaging.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseMessaging' - s.version = '12.1.0' + s.version = '12.2.0' s.summary = 'Firebase Messaging' s.description = <<-DESC @@ -60,8 +60,8 @@ device, and it is completely free. s.tvos.framework = 'SystemConfiguration' s.osx.framework = 'SystemConfiguration' s.weak_framework = 'UserNotifications' - s.dependency 'FirebaseInstallations', '~> 12.1.0' - s.dependency 'FirebaseCore', '~> 12.1.0' + s.dependency 'FirebaseInstallations', '~> 12.2.0' + s.dependency 'FirebaseCore', '~> 12.2.0' s.dependency 'GoogleUtilities/AppDelegateSwizzler', '~> 8.1' s.dependency 'GoogleUtilities/Reachability', '~> 8.1' s.dependency 'GoogleUtilities/Environment', '~> 8.1' diff --git a/FirebaseMessagingInterop.podspec b/FirebaseMessagingInterop.podspec index 80409d6bf2d..cedd5c82a8b 100644 --- a/FirebaseMessagingInterop.podspec +++ b/FirebaseMessagingInterop.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseMessagingInterop' - s.version = '12.1.0' + s.version = '12.2.0' s.summary = 'Interfaces that allow other Firebase SDKs to use Messaging functionality.' s.description = <<-DESC diff --git a/FirebasePerformance.podspec b/FirebasePerformance.podspec index 0fb8108870d..78cefbdaede 100644 --- a/FirebasePerformance.podspec +++ b/FirebasePerformance.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebasePerformance' - s.version = '12.1.0' + s.version = '12.2.0' s.summary = 'Firebase Performance' s.description = <<-DESC @@ -58,10 +58,10 @@ Firebase Performance library to measure performance of Mobile and Web Apps. s.ios.framework = 'CoreTelephony' s.framework = 'QuartzCore' s.framework = 'SystemConfiguration' - s.dependency 'FirebaseCore', '~> 12.1.0' - s.dependency 'FirebaseInstallations', '~> 12.1.0' - s.dependency 'FirebaseRemoteConfig', '~> 12.1.0' - s.dependency 'FirebaseSessions', '~> 12.1.0' + s.dependency 'FirebaseCore', '~> 12.2.0' + s.dependency 'FirebaseInstallations', '~> 12.2.0' + s.dependency 'FirebaseRemoteConfig', '~> 12.2.0' + s.dependency 'FirebaseSessions', '~> 12.2.0' s.dependency 'GoogleDataTransport', '~> 10.1' s.dependency 'GoogleUtilities/Environment', '~> 8.1' s.dependency 'GoogleUtilities/MethodSwizzler', '~> 8.1' diff --git a/FirebaseRemoteConfig.podspec b/FirebaseRemoteConfig.podspec index f384ed8f29a..06fde8ddcb4 100644 --- a/FirebaseRemoteConfig.podspec +++ b/FirebaseRemoteConfig.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseRemoteConfig' - s.version = '12.1.0' + s.version = '12.2.0' s.summary = 'Firebase Remote Config' s.description = <<-DESC @@ -49,13 +49,13 @@ app update. s.pod_target_xcconfig = { 'HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}"' } - s.dependency 'FirebaseABTesting', '~> 12.1.0' - s.dependency 'FirebaseSharedSwift', '~> 12.1.0' - s.dependency 'FirebaseCore', '~> 12.1.0' - s.dependency 'FirebaseInstallations', '~> 12.1.0' + s.dependency 'FirebaseABTesting', '~> 12.2.0' + s.dependency 'FirebaseSharedSwift', '~> 12.2.0' + s.dependency 'FirebaseCore', '~> 12.2.0' + s.dependency 'FirebaseInstallations', '~> 12.2.0' s.dependency 'GoogleUtilities/Environment', '~> 8.1' s.dependency 'GoogleUtilities/NSData+zlib', '~> 8.1' - s.dependency 'FirebaseRemoteConfigInterop', '~> 12.1.0' + s.dependency 'FirebaseRemoteConfigInterop', '~> 12.2.0' s.test_spec 'unit' do |unit_tests| unit_tests.scheme = { :code_coverage => true } diff --git a/FirebaseRemoteConfig/CHANGELOG.md b/FirebaseRemoteConfig/CHANGELOG.md index f1b25939183..36ced2c9a19 100644 --- a/FirebaseRemoteConfig/CHANGELOG.md +++ b/FirebaseRemoteConfig/CHANGELOG.md @@ -1,3 +1,7 @@ +# 12.2.0 +- [fixed] Fixed a race condition that could lead to a crash during network + session recreation. (#15087) + # 12.0.0 - [added] Improved how the SDK handles real-time requests when a Firebase project has exceeded its available quota for real-time services. diff --git a/FirebaseRemoteConfig/Sources/FIRRemoteConfig.m b/FirebaseRemoteConfig/Sources/FIRRemoteConfig.m index 443265800b2..258eb362fe7 100644 --- a/FirebaseRemoteConfig/Sources/FIRRemoteConfig.m +++ b/FirebaseRemoteConfig/Sources/FIRRemoteConfig.m @@ -700,6 +700,8 @@ - (FIRRemoteConfigSettings *)configSettings { dispatch_sync(_queue, ^{ minimumFetchInterval = self->_settings.minimumFetchInterval; fetchTimeout = self->_settings.fetchTimeout; + // The NSURLSession needs to be recreated whenever the fetch timeout may be updated. + [_configFetch recreateNetworkSession]; }); FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000066", @"Successfully read configSettings. Minimum Fetch Interval:%f, " @@ -708,8 +710,6 @@ - (FIRRemoteConfigSettings *)configSettings { FIRRemoteConfigSettings *settings = [[FIRRemoteConfigSettings alloc] init]; settings.minimumFetchInterval = minimumFetchInterval; settings.fetchTimeout = fetchTimeout; - /// The NSURLSession needs to be recreated whenever the fetch timeout may be updated. - [_configFetch recreateNetworkSession]; return settings; } @@ -721,7 +721,7 @@ - (void)setConfigSettings:(FIRRemoteConfigSettings *)configSettings { self->_settings.minimumFetchInterval = configSettings.minimumFetchInterval; self->_settings.fetchTimeout = configSettings.fetchTimeout; - /// The NSURLSession needs to be recreated whenever the fetch timeout may be updated. + // The NSURLSession needs to be recreated whenever the fetch timeout may be updated. [self->_configFetch recreateNetworkSession]; FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000067", @"Successfully set configSettings. Minimum Fetch Interval:%f, " diff --git a/FirebaseRemoteConfig/Sources/RCNConfigFetch.m b/FirebaseRemoteConfig/Sources/RCNConfigFetch.m index 85e86f85e36..1df80385972 100644 --- a/FirebaseRemoteConfig/Sources/RCNConfigFetch.m +++ b/FirebaseRemoteConfig/Sources/RCNConfigFetch.m @@ -113,6 +113,7 @@ - (instancetype)initWithContent:(RCNConfigContent *)content } /// Force a new NSURLSession creation for updated config. +/// - Warning: This API is **not** thread-safe. - (void)recreateNetworkSession { if (_fetchSession) { [_fetchSession invalidateAndCancel]; diff --git a/FirebaseRemoteConfigInterop.podspec b/FirebaseRemoteConfigInterop.podspec index ddb7c209c3f..3266634c9fa 100644 --- a/FirebaseRemoteConfigInterop.podspec +++ b/FirebaseRemoteConfigInterop.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseRemoteConfigInterop' - s.version = '12.1.0' + s.version = '12.2.0' s.summary = 'Interfaces that allow other Firebase SDKs to use Remote Config functionality.' s.description = <<-DESC diff --git a/FirebaseSessions.podspec b/FirebaseSessions.podspec index 88b2b827e16..88406da2eb6 100644 --- a/FirebaseSessions.podspec +++ b/FirebaseSessions.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseSessions' - s.version = '12.1.0' + s.version = '12.2.0' s.summary = 'Firebase Sessions' s.description = <<-DESC @@ -39,9 +39,9 @@ Pod::Spec.new do |s| base_dir + 'SourcesObjC/**/*.{c,h,m,mm}', ] - s.dependency 'FirebaseCore', '~> 12.1.0' - s.dependency 'FirebaseCoreExtension', '~> 12.1.0' - s.dependency 'FirebaseInstallations', '~> 12.1.0' + s.dependency 'FirebaseCore', '~> 12.2.0' + s.dependency 'FirebaseCoreExtension', '~> 12.2.0' + s.dependency 'FirebaseInstallations', '~> 12.2.0' s.dependency 'GoogleDataTransport', '~> 10.1' s.dependency 'GoogleUtilities/Environment', '~> 8.1' s.dependency 'GoogleUtilities/UserDefaults', '~> 8.1' diff --git a/FirebaseSharedSwift.podspec b/FirebaseSharedSwift.podspec index d14ad49b44d..b52cf86f973 100644 --- a/FirebaseSharedSwift.podspec +++ b/FirebaseSharedSwift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseSharedSwift' - s.version = '12.1.0' + s.version = '12.2.0' s.summary = 'Shared Swift Extensions for Firebase' s.description = <<-DESC diff --git a/FirebaseStorage.podspec b/FirebaseStorage.podspec index d1dc7201bbc..ac4caed6655 100644 --- a/FirebaseStorage.podspec +++ b/FirebaseStorage.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseStorage' - s.version = '12.1.0' + s.version = '12.2.0' s.summary = 'Firebase Storage' s.description = <<-DESC @@ -37,10 +37,10 @@ Firebase Storage provides robust, secure file uploads and downloads from Firebas 'FirebaseStorage/Typedefs/*.h', ] - s.dependency 'FirebaseAppCheckInterop', '~> 12.1.0' - s.dependency 'FirebaseAuthInterop', '~> 12.1.0' - s.dependency 'FirebaseCore', '~> 12.1.0' - s.dependency 'FirebaseCoreExtension', '~> 12.1.0' + s.dependency 'FirebaseAppCheckInterop', '~> 12.2.0' + s.dependency 'FirebaseAuthInterop', '~> 12.2.0' + s.dependency 'FirebaseCore', '~> 12.2.0' + s.dependency 'FirebaseCoreExtension', '~> 12.2.0' s.dependency 'GTMSessionFetcher/Core', '>= 3.4', '< 6.0' s.dependency 'GoogleUtilities/Environment', '~> 8.1' @@ -57,7 +57,7 @@ Firebase Storage provides robust, secure file uploads and downloads from Firebas objc_tests.requires_app_host = true objc_tests.resources = 'FirebaseStorage/Tests/Integration/Resources/1mb.dat', 'FirebaseStorage/Tests/Integration/Resources/GoogleService-Info.plist' - objc_tests.dependency 'FirebaseAuth', '~> 12.1.0' + objc_tests.dependency 'FirebaseAuth', '~> 12.2.0' objc_tests.pod_target_xcconfig = { 'HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}"' } @@ -86,6 +86,6 @@ Firebase Storage provides robust, secure file uploads and downloads from Firebas int_tests.resources = 'FirebaseStorage/Tests/Integration/Resources/1mb.dat', 'FirebaseStorage/Tests/Integration/Resources/GoogleService-Info.plist', 'FirebaseStorage/Tests/Integration/Resources/HomeImprovement.numbers' - int_tests.dependency 'FirebaseAuth', '~> 12.1.0' + int_tests.dependency 'FirebaseAuth', '~> 12.2.0' end end diff --git a/GoogleAppMeasurement.podspec b/GoogleAppMeasurement.podspec index f9fc10c2772..244898bbd48 100644 --- a/GoogleAppMeasurement.podspec +++ b/GoogleAppMeasurement.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GoogleAppMeasurement' - s.version = '12.1.0' + s.version = '12.2.0' s.summary = 'Shared measurement methods for Google libraries. Not intended for direct use.' s.description = <<-DESC @@ -16,7 +16,7 @@ Pod::Spec.new do |s| s.authors = 'Google, Inc.' s.source = { - :http => 'https://dl.google.com/firebase/ios/analytics/40ed69179ccc9278/GoogleAppMeasurement-12.1.0.tar.gz' + :http => 'https://dl.google.com/firebase/ios/analytics/47d80ee1ff340179/GoogleAppMeasurement-12.2.0.tar.gz' } s.cocoapods_version = '>= 1.12.0' @@ -37,9 +37,9 @@ Pod::Spec.new do |s| s.default_subspecs = 'Default' s.subspec 'Default' do |ss| - ss.dependency 'GoogleAppMeasurement/Core', '12.1.0' - ss.dependency 'GoogleAppMeasurement/IdentitySupport', '12.1.0' - ss.ios.dependency 'GoogleAdsOnDeviceConversion', '2.2.0' + ss.dependency 'GoogleAppMeasurement/Core', '12.2.0' + ss.dependency 'GoogleAppMeasurement/IdentitySupport', '12.2.0' + ss.ios.dependency 'GoogleAdsOnDeviceConversion', '2.3.0' end s.subspec 'Core' do |ss| @@ -47,7 +47,7 @@ Pod::Spec.new do |s| end s.subspec 'IdentitySupport' do |ss| - ss.dependency 'GoogleAppMeasurement/Core', '12.1.0' + ss.dependency 'GoogleAppMeasurement/Core', '12.2.0' ss.vendored_frameworks = 'Frameworks/GoogleAppMeasurementIdentitySupport.xcframework' end end diff --git a/Package.swift b/Package.swift index 2a5240e3bf7..f1f118874fb 100644 --- a/Package.swift +++ b/Package.swift @@ -18,7 +18,7 @@ import PackageDescription -let firebaseVersion = "12.1.0" +let firebaseVersion = "12.2.0" let package = Package( name: "Firebase", @@ -329,8 +329,8 @@ let package = Package( ), .binaryTarget( name: "FirebaseAnalytics", - url: "https://dl.google.com/firebase/ios/swiftpm/12.1.0/FirebaseAnalytics.zip", - checksum: "57ab43b31bc0b804bb09db48d77d713fa7834085bc5aa7e2cd1b5369e63a697d" + url: "https://dl.google.com/firebase/ios/swiftpm/12.2.0/FirebaseAnalytics.zip", + checksum: "f1b07dabcdf3f2b6c495af72baa55e40672a625b8a1b6c631fb43ec74a2ec1ca" ), .testTarget( name: "AnalyticsSwiftUnit", @@ -1384,7 +1384,7 @@ func googleAppMeasurementDependency() -> Package.Dependency { return .package(url: appMeasurementURL, branch: "main") } - return .package(url: appMeasurementURL, exact: "12.1.0") + return .package(url: appMeasurementURL, exact: "12.2.0") } func abseilDependency() -> Package.Dependency { diff --git a/ReleaseTooling/CarthageJSON/FirebaseABTestingBinary.json b/ReleaseTooling/CarthageJSON/FirebaseABTestingBinary.json index cf57a499bd7..46bcfece798 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseABTestingBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseABTestingBinary.json @@ -45,6 +45,7 @@ "11.8.0": "https://dl.google.com/dl/firebase/ios/carthage/11.8.0/FirebaseABTesting-14bbd5283f79341d.zip", "11.9.0": "https://dl.google.com/dl/firebase/ios/carthage/11.9.0/FirebaseABTesting-5436773ba2b9326e.zip", "12.0.0": "https://dl.google.com/dl/firebase/ios/carthage/12.0.0/FirebaseABTesting-bd721c84362383a6.zip", + "12.1.0": "https://dl.google.com/dl/firebase/ios/carthage/12.1.0/FirebaseABTesting-bb0e44f97fd81c31.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/ABTesting-d0fdf10c43e985b1.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/ABTesting-d0fdf10c43e985b1.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/ABTesting-a71d17cadc209af9.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAIBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAIBinary.json index f7024c4b7bd..ba7d7cb3685 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAIBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAIBinary.json @@ -2,5 +2,6 @@ "11.13.0": "https://dl.google.com/dl/firebase/ios/carthage/11.13.0/FirebaseAI-b1e75ff6284775b1.zip", "11.14.0": "https://dl.google.com/dl/firebase/ios/carthage/11.14.0/FirebaseAI-0991ef5c3a83833a.zip", "11.15.0": "https://dl.google.com/dl/firebase/ios/carthage/11.15.0/FirebaseAI-ba1237ee5b7a5baa.zip", - "12.0.0": "https://dl.google.com/dl/firebase/ios/carthage/12.0.0/FirebaseAI-05a4568076093001.zip" + "12.0.0": "https://dl.google.com/dl/firebase/ios/carthage/12.0.0/FirebaseAI-05a4568076093001.zip", + "12.1.0": "https://dl.google.com/dl/firebase/ios/carthage/12.1.0/FirebaseAI-1fa7d016c66b2331.zip" } diff --git a/ReleaseTooling/CarthageJSON/FirebaseAnalyticsBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAnalyticsBinary.json index 57b27800483..6628fc3fdda 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAnalyticsBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAnalyticsBinary.json @@ -45,6 +45,7 @@ "11.8.0": "https://dl.google.com/dl/firebase/ios/carthage/11.8.0/FirebaseAnalytics-0929c5c36f6a3dd2.zip", "11.9.0": "https://dl.google.com/dl/firebase/ios/carthage/11.9.0/FirebaseAnalytics-fe649e5740ef72e9.zip", "12.0.0": "https://dl.google.com/dl/firebase/ios/carthage/12.0.0/FirebaseAnalytics-70ead21957efa870.zip", + "12.1.0": "https://dl.google.com/dl/firebase/ios/carthage/12.1.0/FirebaseAnalytics-88dad74aa8ab040a.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/Analytics-2468c231ebeb7922.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/Analytics-bc8101d420b896c5.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/Analytics-d2b6a6b0242db786.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAppCheckBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAppCheckBinary.json index 65a947bb3a0..209eee8f4d4 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAppCheckBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAppCheckBinary.json @@ -45,6 +45,7 @@ "11.8.0": "https://dl.google.com/dl/firebase/ios/carthage/11.8.0/FirebaseAppCheck-68439fef0d9ee01c.zip", "11.9.0": "https://dl.google.com/dl/firebase/ios/carthage/11.9.0/FirebaseAppCheck-366c926c105319b0.zip", "12.0.0": "https://dl.google.com/dl/firebase/ios/carthage/12.0.0/FirebaseAppCheck-b9f47f169bb6249c.zip", + "12.1.0": "https://dl.google.com/dl/firebase/ios/carthage/12.1.0/FirebaseAppCheck-072a1be1f8eb1177.zip", "8.0.0": "https://dl.google.com/dl/firebase/ios/carthage/8.0.0/FirebaseAppCheck-9ef1d217cf057203.zip", "8.1.0": "https://dl.google.com/dl/firebase/ios/carthage/8.1.0/FirebaseAppCheck-fc03215d9fe45d3a.zip", "8.10.0": "https://dl.google.com/dl/firebase/ios/carthage/8.10.0/FirebaseAppCheck-6ebe9e9539f06003.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAppDistributionBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAppDistributionBinary.json index a0ad51cc2fd..252385766d7 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAppDistributionBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAppDistributionBinary.json @@ -45,6 +45,7 @@ "11.8.0": "https://dl.google.com/dl/firebase/ios/carthage/11.8.0/FirebaseAppDistribution-e558ade73b5891d6.zip", "11.9.0": "https://dl.google.com/dl/firebase/ios/carthage/11.9.0/FirebaseAppDistribution-32e12df219d91736.zip", "12.0.0": "https://dl.google.com/dl/firebase/ios/carthage/12.0.0/FirebaseAppDistribution-076c2c6efb7eb8dc.zip", + "12.1.0": "https://dl.google.com/dl/firebase/ios/carthage/12.1.0/FirebaseAppDistribution-370884f5f825f098.zip", "6.31.0": "https://dl.google.com/dl/firebase/ios/carthage/6.31.0/FirebaseAppDistribution-07f6a2cf7f576a8a.zip", "6.32.0": "https://dl.google.com/dl/firebase/ios/carthage/6.32.0/FirebaseAppDistribution-a9c4f5db794508ca.zip", "6.33.0": "https://dl.google.com/dl/firebase/ios/carthage/6.33.0/FirebaseAppDistribution-448a96d2ade54581.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAuthBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAuthBinary.json index 7c63aa2557c..65ac8b39fec 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAuthBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAuthBinary.json @@ -45,6 +45,7 @@ "11.8.0": "https://dl.google.com/dl/firebase/ios/carthage/11.8.0/FirebaseAuth-f41dc3e6a1a923d7.zip", "11.9.0": "https://dl.google.com/dl/firebase/ios/carthage/11.9.0/FirebaseAuth-ab131b2e07abc902.zip", "12.0.0": "https://dl.google.com/dl/firebase/ios/carthage/12.0.0/FirebaseAuth-e58b2b5f430bfbfe.zip", + "12.1.0": "https://dl.google.com/dl/firebase/ios/carthage/12.1.0/FirebaseAuth-2c17100b302eb080.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/Auth-0fa76ba0f7956220.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/Auth-5ddd2b4351012c7a.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/Auth-5e248984d78d7284.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseCrashlyticsBinary.json b/ReleaseTooling/CarthageJSON/FirebaseCrashlyticsBinary.json index 1ff9582109d..cc05921dcfd 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseCrashlyticsBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseCrashlyticsBinary.json @@ -45,6 +45,7 @@ "11.8.0": "https://dl.google.com/dl/firebase/ios/carthage/11.8.0/FirebaseCrashlytics-36f932dcd3db6874.zip", "11.9.0": "https://dl.google.com/dl/firebase/ios/carthage/11.9.0/FirebaseCrashlytics-81aa29d9a106acb0.zip", "12.0.0": "https://dl.google.com/dl/firebase/ios/carthage/12.0.0/FirebaseCrashlytics-d9a9be2a4e220017.zip", + "12.1.0": "https://dl.google.com/dl/firebase/ios/carthage/12.1.0/FirebaseCrashlytics-fbf241b0c59f3821.zip", "6.15.0": "https://dl.google.com/dl/firebase/ios/carthage/6.15.0/FirebaseCrashlytics-1c6d22d5b73c84fd.zip", "6.16.0": "https://dl.google.com/dl/firebase/ios/carthage/6.16.0/FirebaseCrashlytics-938e5fd0e2eab3b3.zip", "6.17.0": "https://dl.google.com/dl/firebase/ios/carthage/6.17.0/FirebaseCrashlytics-fa09f0c8f31ed5d9.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseDatabaseBinary.json b/ReleaseTooling/CarthageJSON/FirebaseDatabaseBinary.json index fc72965de98..0959ea0af13 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseDatabaseBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseDatabaseBinary.json @@ -45,6 +45,7 @@ "11.8.0": "https://dl.google.com/dl/firebase/ios/carthage/11.8.0/FirebaseDatabase-8e544ced90fb6eb2.zip", "11.9.0": "https://dl.google.com/dl/firebase/ios/carthage/11.9.0/FirebaseDatabase-8b970d6e0f67a415.zip", "12.0.0": "https://dl.google.com/dl/firebase/ios/carthage/12.0.0/FirebaseDatabase-18b95ef28b89f3db.zip", + "12.1.0": "https://dl.google.com/dl/firebase/ios/carthage/12.1.0/FirebaseDatabase-d6c24e13e4b05437.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/Database-1f7a820452722c7d.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/Database-1f7a820452722c7d.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/Database-59a12d87456b3e1c.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseFirestoreBinary.json b/ReleaseTooling/CarthageJSON/FirebaseFirestoreBinary.json index 66d2d9882c0..b724c13a0de 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseFirestoreBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseFirestoreBinary.json @@ -45,6 +45,7 @@ "11.8.0": "https://dl.google.com/dl/firebase/ios/carthage/11.8.0/FirebaseFirestore-792558f0eddb9934.zip", "11.9.0": "https://dl.google.com/dl/firebase/ios/carthage/11.9.0/FirebaseFirestore-30a2451150d46015.zip", "12.0.0": "https://dl.google.com/dl/firebase/ios/carthage/12.0.0/FirebaseFirestore-635c7c2864cdd1a9.zip", + "12.1.0": "https://dl.google.com/dl/firebase/ios/carthage/12.1.0/FirebaseFirestore-6098779ef7b7b151.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/Firestore-68fc02c229d0cc69.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/Firestore-87a804ab561d91db.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/Firestore-ecb3eea7bde7e8e8.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseFunctionsBinary.json b/ReleaseTooling/CarthageJSON/FirebaseFunctionsBinary.json index 87255957bf9..cce3563815c 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseFunctionsBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseFunctionsBinary.json @@ -45,6 +45,7 @@ "11.8.0": "https://dl.google.com/dl/firebase/ios/carthage/11.8.0/FirebaseFunctions-e85bcbe133482bbc.zip", "11.9.0": "https://dl.google.com/dl/firebase/ios/carthage/11.9.0/FirebaseFunctions-1681244d37d89040.zip", "12.0.0": "https://dl.google.com/dl/firebase/ios/carthage/12.0.0/FirebaseFunctions-2ac7bbbaf94c52e1.zip", + "12.1.0": "https://dl.google.com/dl/firebase/ios/carthage/12.1.0/FirebaseFunctions-f4a1c660d9a2ea75.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/Functions-f4c426016dd41e38.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/Functions-c6c44427c3034736.zip", "5.0.0": "https://dl.google.com/dl/firebase/ios/carthage/5.0.0/Functions-146f34c401bd459b.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseGoogleSignInBinary.json b/ReleaseTooling/CarthageJSON/FirebaseGoogleSignInBinary.json index 42ea689c712..87f96dd518f 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseGoogleSignInBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseGoogleSignInBinary.json @@ -45,6 +45,7 @@ "11.8.0": "https://dl.google.com/dl/firebase/ios/carthage/11.8.0/GoogleSignIn-45d907510d5c840b.zip", "11.9.0": "https://dl.google.com/dl/firebase/ios/carthage/11.9.0/GoogleSignIn-d4359fb699843869.zip", "12.0.0": "https://dl.google.com/dl/firebase/ios/carthage/12.0.0/GoogleSignIn-b924d38d37bc920a.zip", + "12.1.0": "https://dl.google.com/dl/firebase/ios/carthage/12.1.0/GoogleSignIn-01f98c11db934294.zip", "6.0.0": "https://dl.google.com/dl/firebase/ios/carthage/6.0.0/GoogleSignIn-de9c5d5e8eb6d6ea.zip", "6.1.0": "https://dl.google.com/dl/firebase/ios/carthage/6.1.0/GoogleSignIn-8c82f2870573a793.zip", "6.10.0": "https://dl.google.com/dl/firebase/ios/carthage/6.10.0/GoogleSignIn-ff3aef61c4a55b05.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseInAppMessagingBinary.json b/ReleaseTooling/CarthageJSON/FirebaseInAppMessagingBinary.json index 53f4da612a0..d83b9ffff48 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseInAppMessagingBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseInAppMessagingBinary.json @@ -45,6 +45,7 @@ "11.8.0": "https://dl.google.com/dl/firebase/ios/carthage/11.8.0/FirebaseInAppMessaging-af2b93ac9f853087.zip", "11.9.0": "https://dl.google.com/dl/firebase/ios/carthage/11.9.0/FirebaseInAppMessaging-67bccdf31b1dc458.zip", "12.0.0": "https://dl.google.com/dl/firebase/ios/carthage/12.0.0/FirebaseInAppMessaging-59f9fc54fc4eecb5.zip", + "12.1.0": "https://dl.google.com/dl/firebase/ios/carthage/12.1.0/FirebaseInAppMessaging-78a0d591fb574512.zip", "5.10.0": "https://dl.google.com/dl/firebase/ios/carthage/5.10.0/InAppMessaging-a7a3f933362f6e95.zip", "5.11.0": "https://dl.google.com/dl/firebase/ios/carthage/5.11.0/InAppMessaging-fa28ce1b88fbca93.zip", "5.12.0": "https://dl.google.com/dl/firebase/ios/carthage/5.12.0/InAppMessaging-fa28ce1b88fbca93.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseMLModelDownloaderBinary.json b/ReleaseTooling/CarthageJSON/FirebaseMLModelDownloaderBinary.json index 646fbacad35..1f8860d484f 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseMLModelDownloaderBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseMLModelDownloaderBinary.json @@ -45,6 +45,7 @@ "11.8.0": "https://dl.google.com/dl/firebase/ios/carthage/11.8.0/FirebaseMLModelDownloader-373db3aced970d88.zip", "11.9.0": "https://dl.google.com/dl/firebase/ios/carthage/11.9.0/FirebaseMLModelDownloader-2d08410294abf160.zip", "12.0.0": "https://dl.google.com/dl/firebase/ios/carthage/12.0.0/FirebaseMLModelDownloader-1f183c6e3be7cab3.zip", + "12.1.0": "https://dl.google.com/dl/firebase/ios/carthage/12.1.0/FirebaseMLModelDownloader-3864d35f4429bc08.zip", "8.0.0": "https://dl.google.com/dl/firebase/ios/carthage/8.0.0/FirebaseMLModelDownloader-8f972757fb181320.zip", "8.1.0": "https://dl.google.com/dl/firebase/ios/carthage/8.1.0/FirebaseMLModelDownloader-058ad59fa6dc0111.zip", "8.10.0": "https://dl.google.com/dl/firebase/ios/carthage/8.10.0/FirebaseMLModelDownloader-286479a966d2fb37.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseMessagingBinary.json b/ReleaseTooling/CarthageJSON/FirebaseMessagingBinary.json index c33b12a6473..4fc0c68b1ca 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseMessagingBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseMessagingBinary.json @@ -45,6 +45,7 @@ "11.8.0": "https://dl.google.com/dl/firebase/ios/carthage/11.8.0/FirebaseMessaging-ffd97136b9f3cde5.zip", "11.9.0": "https://dl.google.com/dl/firebase/ios/carthage/11.9.0/FirebaseMessaging-379bf3738f94ef44.zip", "12.0.0": "https://dl.google.com/dl/firebase/ios/carthage/12.0.0/FirebaseMessaging-0f018ab3d7701839.zip", + "12.1.0": "https://dl.google.com/dl/firebase/ios/carthage/12.1.0/FirebaseMessaging-252cac88c87e9c55.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/Messaging-a22ef2b5f2f30f82.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/Messaging-94fa4e090c7e9185.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/Messaging-2a00a1c64a19d176.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebasePerformanceBinary.json b/ReleaseTooling/CarthageJSON/FirebasePerformanceBinary.json index 939a7832128..0237b8ed2c3 100644 --- a/ReleaseTooling/CarthageJSON/FirebasePerformanceBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebasePerformanceBinary.json @@ -45,6 +45,7 @@ "11.8.0": "https://dl.google.com/dl/firebase/ios/carthage/11.8.0/FirebasePerformance-e7063e87d9b3d1b7.zip", "11.9.0": "https://dl.google.com/dl/firebase/ios/carthage/11.9.0/FirebasePerformance-9a2f8d3983650cea.zip", "12.0.0": "https://dl.google.com/dl/firebase/ios/carthage/12.0.0/FirebasePerformance-0df8c67ab3cb665c.zip", + "12.1.0": "https://dl.google.com/dl/firebase/ios/carthage/12.1.0/FirebasePerformance-dec4dc5c3edadd9a.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/Performance-d8693eb892bfa05b.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/Performance-0a400f9460f7a71d.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/Performance-f5b4002ab96523e4.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseRemoteConfigBinary.json b/ReleaseTooling/CarthageJSON/FirebaseRemoteConfigBinary.json index fb0b84442a7..9027616f5d0 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseRemoteConfigBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseRemoteConfigBinary.json @@ -45,6 +45,7 @@ "11.8.0": "https://dl.google.com/dl/firebase/ios/carthage/11.8.0/FirebaseRemoteConfig-6e66634da4590f07.zip", "11.9.0": "https://dl.google.com/dl/firebase/ios/carthage/11.9.0/FirebaseRemoteConfig-7455afe6f2231467.zip", "12.0.0": "https://dl.google.com/dl/firebase/ios/carthage/12.0.0/FirebaseRemoteConfig-5894aa4820a265ae.zip", + "12.1.0": "https://dl.google.com/dl/firebase/ios/carthage/12.1.0/FirebaseRemoteConfig-bb5ba29a5f73cd24.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/RemoteConfig-7e9635365ccd4a17.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/RemoteConfig-e7928fcb6311c439.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/RemoteConfig-9ab1ca5f360a1780.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseStorageBinary.json b/ReleaseTooling/CarthageJSON/FirebaseStorageBinary.json index 69838986e91..b753e8be256 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseStorageBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseStorageBinary.json @@ -45,6 +45,7 @@ "11.8.0": "https://dl.google.com/dl/firebase/ios/carthage/11.8.0/FirebaseStorage-f55cadd62f44b14f.zip", "11.9.0": "https://dl.google.com/dl/firebase/ios/carthage/11.9.0/FirebaseStorage-5a28ee1b2244be55.zip", "12.0.0": "https://dl.google.com/dl/firebase/ios/carthage/12.0.0/FirebaseStorage-21ed034a4fa51f2a.zip", + "12.1.0": "https://dl.google.com/dl/firebase/ios/carthage/12.1.0/FirebaseStorage-faeffdccd0d44a7c.zip", "4.11.0": "https://dl.google.com/dl/firebase/ios/carthage/4.11.0/Storage-6b3e77e1a7fdbc61.zip", "4.12.0": "https://dl.google.com/dl/firebase/ios/carthage/4.12.0/Storage-4721c35d2b90a569.zip", "4.9.0": "https://dl.google.com/dl/firebase/ios/carthage/4.9.0/Storage-821299369b9d0fb2.zip", diff --git a/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift b/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift index 27faaa0b188..9e5a6aebb35 100755 --- a/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift +++ b/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift @@ -21,7 +21,7 @@ import Foundation /// The version and releasing fields of the non-Firebase pods should be reviewed every release. /// The array should be ordered so that any pod's dependencies precede it in the list. public let shared = Manifest( - version: "12.1.0", + version: "12.2.0", pods: [ Pod("FirebaseSharedSwift"), Pod("FirebaseCoreInternal"), diff --git a/ReleaseTooling/Sources/ZipBuilder/FrameworkBuilder.swift b/ReleaseTooling/Sources/ZipBuilder/FrameworkBuilder.swift index cd87fd3272e..c29eaeb3305 100755 --- a/ReleaseTooling/Sources/ZipBuilder/FrameworkBuilder.swift +++ b/ReleaseTooling/Sources/ZipBuilder/FrameworkBuilder.swift @@ -272,7 +272,7 @@ struct FrameworkBuilder { /// /// - Parameter framework: The name of the pod to be built. /// - Returns: The corresponding framework/module name. - private static func frameworkBuildName(_ framework: String) -> String { + static func frameworkBuildName(_ framework: String) -> String { switch framework { case "abseil": return "absl" diff --git a/ReleaseTooling/Sources/ZipBuilder/ModuleMapBuilder.swift b/ReleaseTooling/Sources/ZipBuilder/ModuleMapBuilder.swift index dc781d6a0d4..a1ccd32243e 100755 --- a/ReleaseTooling/Sources/ZipBuilder/ModuleMapBuilder.swift +++ b/ReleaseTooling/Sources/ZipBuilder/ModuleMapBuilder.swift @@ -248,6 +248,11 @@ struct ModuleMapBuilder { installedPods[name]?.transitiveFrameworks = transitiveFrameworkDeps installedPods[name]?.transitiveLibraries = transitiveLibraryDeps - return ModuleMapContents(module: name, frameworks: myFrameworkDeps, libraries: myLibraryDeps) + let moduleName = FrameworkBuilder.frameworkBuildName(name) + return ModuleMapContents( + module: moduleName, + frameworks: myFrameworkDeps, + libraries: myLibraryDeps + ) } } diff --git a/ReleaseTooling/Sources/ZipBuilder/main.swift b/ReleaseTooling/Sources/ZipBuilder/main.swift index 4a56842b425..cc19e3fafe6 100644 --- a/ReleaseTooling/Sources/ZipBuilder/main.swift +++ b/ReleaseTooling/Sources/ZipBuilder/main.swift @@ -100,7 +100,7 @@ struct ZipBuilderTool: ParsableCommand { // MARK: - Platform Arguments /// The minimum iOS Version to build for. - @Option(default: "12.0", help: ArgumentHelp("The minimum supported iOS version.")) + @Option(default: "15.0", help: ArgumentHelp("The minimum supported iOS version.")) var minimumIOSVersion: String /// The minimum macOS Version to build for. @@ -108,7 +108,7 @@ struct ZipBuilderTool: ParsableCommand { var minimumMacOSVersion: String /// The minimum tvOS Version to build for. - @Option(default: "13.0", help: ArgumentHelp("The minimum supported tvOS version.")) + @Option(default: "15.0", help: ArgumentHelp("The minimum supported tvOS version.")) var minimumTVOSVersion: String /// The minimum watchOS Version to build for. diff --git a/ReleaseTooling/Template/README.md b/ReleaseTooling/Template/README.md index 2b02fa7177a..495301380e2 100644 --- a/ReleaseTooling/Template/README.md +++ b/ReleaseTooling/Template/README.md @@ -67,13 +67,26 @@ To integrate a Firebase SDK with your app: c. Double-click the setting, click the '+' button, and add `-lc++` -10. Drag the `Firebase.h` header in this directory into your project. This will +10. If you're using Firebase Analytics, disable + GoogleAdsOnDeviceConversion.xcframework for Mac Catalyst: + + a. In your project settings, open the **Settings** panel for your target. + + b. Go to the Build Phases tab and find the + **GoogleAdsOnDeviceConversion.xcframework** setting in the **Link Binary + With Libraries** section. + + c. Click on the filter icon button in the + **GoogleAdsOnDeviceConversion.xcframework** row and deselect the Mac Catalyst + checkbox. + +11. Drag the `Firebase.h` header in this directory into your project. This will allow you to `#import "Firebase.h"` and start using any Firebase SDK that you have. -11. Drag `module.modulemap` into your project and update the +12. Drag `module.modulemap` into your project and update the "User Header Search Paths" in your project's Build Settings to include the directory that contains the added module map. -12. If your app does not include any Swift implementation, you may need to add +13. If your app does not include any Swift implementation, you may need to add a dummy Swift file to the app to prevent Swift system library missing symbol linker errors. See https://forums.swift.org/t/using-binary-swift-sdks-from-non-swift-apps/55989. @@ -81,7 +94,7 @@ To integrate a Firebase SDK with your app: > ⚠ If prompted with the option to create a corresponding bridging header > for the new Swift file, select **Don't create**. -13. You're done! Build your target and start using Firebase. +14. You're done! Build your target and start using Firebase. If you want to add another SDK, repeat the steps above with the xcframeworks for the new SDK. You only need to add each framework once, so if you've already diff --git a/scripts/build.sh b/scripts/build.sh index 80cc79f0bb5..2be500a8bbc 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -187,8 +187,11 @@ tvos_flags=( -destination 'platform=tvOS Simulator,name=Apple TV' ) visionos_flags=( + # As of Aug 15, 2025, the default OS "latest" was failing as it matched both + # the visionOS 26 beta and visionOS 2.5 (from Xcode 16.4) simulators; + # explicitly specifying OS=2.5 in destination as a workaround. -sdk 'xrsimulator' - -destination 'platform=visionOS Simulator,name=Apple Vision Pro' + -destination 'platform=visionOS Simulator,OS=2.5,name=Apple Vision Pro' ) catalyst_flags=( ARCHS=x86_64 VALID_ARCHS=x86_64 SUPPORTS_MACCATALYST=YES -sdk macosx @@ -489,12 +492,10 @@ case "$product-$platform-$method" in ../../../FirebaseRemoteConfig/Tests/Swift/AccessToken.json # Integration tests are only run on iOS to minimize flake failures. - # TODO(ncooke3): Remove -sdk and -destination flags and replace with "${xcb_flags[@]}" RunXcodebuild \ -workspace 'gen/FirebaseRemoteConfig/FirebaseRemoteConfig.xcworkspace' \ -scheme "FirebaseRemoteConfig-Unit-swift-api-tests" \ - -sdk 'iphonesimulator' \ - -destination 'platform=iOS Simulator,name=iPhone 16,OS=18.3.1' \ + "${xcb_flags[@]}" \ build \ test ;; @@ -521,6 +522,8 @@ case "$product-$platform-$method" in -scheme "FirebaseAITestApp-SPM" \ "${xcb_flags[@]}" \ -parallel-testing-enabled NO \ + -retry-tests-on-failure \ + -test-iterations 3 \ test ;; @@ -570,12 +573,10 @@ case "$product-$platform-$method" in if check_secrets; then # Integration tests are only run on iOS to minimize flake failures. - # TODO(ncooke3): Add back "${ios_flags[@]}". See #14657. RunXcodebuild \ -workspace 'gen/FirebaseStorage/FirebaseStorage.xcworkspace' \ -scheme "FirebaseStorage-Unit-integration" \ - -sdk 'iphonesimulator' \ - -destination 'platform=iOS Simulator,name=iPhone 16,OS=18.3.1' \ + "${ios_flags[@]}" \ "${xcb_flags[@]}" \ test fi @@ -591,12 +592,10 @@ case "$product-$platform-$method" in if check_secrets; then # Integration tests are only run on iOS to minimize flake failures. - # TODO(ncooke3): Add back "${ios_flags[@]}". See #14657. RunXcodebuild \ -workspace 'gen/FirebaseStorage/FirebaseStorage.xcworkspace' \ -scheme "FirebaseStorage-Unit-ObjCIntegration" \ - -sdk 'iphonesimulator' \ - -destination 'platform=iOS Simulator,name=iPhone 16,OS=18.3.1' \ + "${ios_flags[@]}" \ "${xcb_flags[@]}" \ test fi @@ -615,8 +614,8 @@ case "$product-$platform-$method" in RunXcodebuild \ -workspace 'gen/FirebaseCombineSwift/FirebaseCombineSwift.xcworkspace' \ -scheme "FirebaseCombineSwift-Unit-integration" \ - -sdk 'iphonesimulator' \ - -destination 'platform=iOS Simulator,name=iPhone 16,OS=18.3.1' \ + "${ios_flags[@]}" \ + "${xcb_flags[@]}" \ test fi ;;