diff --git a/.github/workflows/abtesting.yml b/.github/workflows/abtesting.yml index 8aaf1c20297..84e2719bde0 100644 --- a/.github/workflows/abtesting.yml +++ b/.github/workflows/abtesting.yml @@ -29,7 +29,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -38,10 +38,13 @@ jobs: run: scripts/setup_bundler.sh - name: Xcode run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - - name: Build and test - run: | - scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb FirebaseABTesting.podspec \ - --platforms=${{ matrix.target }} + - uses: nick-fields/retry@v3 + with: + timeout_minutes: 120 + max_attempts: 3 + retry_on: error + retry_wait_seconds: 120 + command: scripts/pod_lib_lint.rb FirebaseABTesting.podspec --platforms=${{ matrix.target }} spm: # Don't run on private repo unless it is a PR. @@ -54,7 +57,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -65,8 +68,13 @@ jobs: run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - name: Initialize xcodebuild run: scripts/setup_spm_tests.sh - - name: Unit Tests - run: scripts/third_party/travis/retry.sh ./scripts/build.sh ABTestingUnit ${{ matrix.target }} spm + - uses: nick-fields/retry@v3 + with: + timeout_minutes: 120 + max_attempts: 3 + retry_on: error + retry_wait_seconds: 120 + command: scripts/build.sh ABTestingUnit ${{ matrix.target }} spm catalyst: # Don't run on private repo unless it is a PR. @@ -81,8 +89,13 @@ jobs: - uses: ruby/setup-ruby@v1 - name: Setup Bundler run: scripts/setup_bundler.sh - - name: Setup project and Build for Catalyst - run: scripts/test_catalyst.sh FirebaseABTesting test FirebaseABTesting-Unit-unit + - uses: nick-fields/retry@v3 + with: + timeout_minutes: 120 + max_attempts: 3 + retry_on: error + retry_wait_seconds: 120 + command: scripts/test_catalyst.sh FirebaseABTesting test FirebaseABTesting-Unit-unit quickstart: # Don't run on private repo unless it is a PR. diff --git a/.github/workflows/analytics.yml b/.github/workflows/analytics.yml index 67872715210..503d4faa70f 100644 --- a/.github/workflows/analytics.yml +++ b/.github/workflows/analytics.yml @@ -28,7 +28,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: diff --git a/.github/workflows/api_diff_report.yml b/.github/workflows/api_diff_report.yml index 872d36d1372..650f94f4727 100644 --- a/.github/workflows/api_diff_report.yml +++ b/.github/workflows/api_diff_report.yml @@ -82,7 +82,7 @@ jobs: --commit $GITHUB_SHA \ --run_id ${{github.run_id}} - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} with: name: api_info_and_report diff --git a/.github/workflows/appdistribution.yml b/.github/workflows/appdistribution.yml index eb5d76880af..882d00b134c 100644 --- a/.github/workflows/appdistribution.yml +++ b/.github/workflows/appdistribution.yml @@ -27,7 +27,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -52,7 +52,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/auth.yml b/.github/workflows/auth.yml index a4627ff26cc..9c536268dbd 100644 --- a/.github/workflows/auth.yml +++ b/.github/workflows/auth.yml @@ -33,7 +33,7 @@ jobs: xcode: Xcode_14.2 tests: --skip-tests - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 tests: runs-on: ${{ matrix.os }} steps: @@ -45,12 +45,14 @@ jobs: run: scripts/configure_test_keychain.sh - name: Xcode run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - - name: Build and test -# TODO: Restore warnings check after resolution of #11693 -# run: scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb ${{ matrix.podspec }} --platforms=${{ matrix.target }} - run: | - scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb ${{ matrix.podspec }} --platforms=${{ matrix.target }} \ - ${{ matrix.tests }} --allow-warnings +#TODO: Restore warnings check after resolution of #11693 + - uses: nick-fields/retry@v3 + with: + timeout_minutes: 120 + max_attempts: 3 + retry_on: error + retry_wait_seconds: 120 + command: scripts/pod_lib_lint.rb ${{ matrix.podspec }} --platforms=${{ matrix.target }} ${{ matrix.tests }} --allow-warnings integration-tests: # Don't run on private repo unless it is a PR. @@ -58,7 +60,7 @@ jobs: env: plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} - runs-on: macos-12 + runs-on: macos-13 steps: - uses: actions/checkout@v4 - uses: mikehardy/buildcache-action@c87cea0ccd718971d6cc39e672c4f26815b6c126 @@ -85,9 +87,15 @@ jobs: FirebaseAuth/Tests/SampleSwift/Sample.entitlements "$plist_secret" scripts/decrypt_gha_secret.sh scripts/gha-encrypted/AuthSample/Credentials.swift.gpg \ FirebaseAuth/Tests/SampleSwift/SwiftApiTests/Credentials.swift "$plist_secret" - - - name: BuildAndTest # can be replaced with pod lib lint with CocoaPods 1.10 - run: ([ -z $plist_secret ] || scripts/third_party/travis/retry.sh scripts/build.sh Auth iOS) + - name: Xcode + run: sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer + - uses: nick-fields/retry@v3 + with: + timeout_minutes: 120 + max_attempts: 3 + retry_on: error + retry_wait_seconds: 120 + command: ([ -z $plist_secret ] || scripts/build.sh Auth iOS) spm: # Don't run on private repo unless it is a PR. @@ -101,7 +109,7 @@ jobs: xcode: Xcode_14.2 test: spm - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 test: spmbuildonly runs-on: ${{ matrix.os }} steps: @@ -113,8 +121,13 @@ jobs: run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - name: Initialize xcodebuild run: scripts/setup_spm_tests.sh - - name: Unit Tests - run: scripts/third_party/travis/retry.sh ./scripts/build.sh AuthUnit ${{ matrix.target }} ${{ matrix.test }} + - uses: nick-fields/retry@v3 + with: + timeout_minutes: 120 + max_attempts: 3 + retry_on: error + retry_wait_seconds: 120 + command: scripts/third_party/travis/retry.sh ./scripts/build.sh AuthUnit ${{ matrix.target }} ${{ matrix.test }} catalyst: # Don't run on private repo unless it is a PR. @@ -128,9 +141,13 @@ jobs: - uses: ruby/setup-ruby@v1 - name: Setup Bundler run: scripts/setup_bundler.sh - - name: Setup project and Build for Catalyst - # Only build the unit tests on Catalyst. Their keychain reliance causes several failures. - run: scripts/test_catalyst.sh FirebaseAuth build FirebaseAuth-Unit-unit + - uses: nick-fields/retry@v3 + with: + timeout_minutes: 120 + max_attempts: 3 + retry_on: error + retry_wait_seconds: 120 + command: scripts/test_catalyst.sh FirebaseAuth build FirebaseAuth-Unit-unit quickstart: # Don't run on private repo unless it is a PR. @@ -197,5 +214,10 @@ jobs: run: scripts/setup_bundler.sh - name: Configure test keychain run: scripts/configure_test_keychain.sh - - name: PodLibLint Auth Cron - run: scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb FirebaseAuth.podspec --platforms=${{ matrix.target }} ${{ matrix.flags }} + - uses: nick-fields/retry@v3 + with: + timeout_minutes: 120 + max_attempts: 3 + retry_on: error + retry_wait_seconds: 120 + command: scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb FirebaseAuth.podspec --platforms=${{ matrix.target }} ${{ matrix.flags }} diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index 0b251d24426..cd74aa4e9ae 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -27,7 +27,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -50,7 +50,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/core_extension.yml b/.github/workflows/core_extension.yml index 0706c035fc0..fdfc4692616 100644 --- a/.github/workflows/core_extension.yml +++ b/.github/workflows/core_extension.yml @@ -25,7 +25,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/core_internal.yml b/.github/workflows/core_internal.yml index 3fa93f047a1..fc0edb777f6 100644 --- a/.github/workflows/core_internal.yml +++ b/.github/workflows/core_internal.yml @@ -23,7 +23,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -46,7 +46,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/crashlytics.yml b/.github/workflows/crashlytics.yml index 26cc1ceb3a7..ad4f618f40e 100644 --- a/.github/workflows/crashlytics.yml +++ b/.github/workflows/crashlytics.yml @@ -31,7 +31,7 @@ jobs: xcode: Xcode_14.2 tests: --skip-tests - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 tests: runs-on: ${{ matrix.os }} steps: @@ -41,10 +41,13 @@ jobs: run: scripts/setup_bundler.sh - name: Xcode run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - - name: Build and test - run: | - scripts/third_party/travis/retry.sh scripts/pod_lib_lint.rb FirebaseCrashlytics.podspec --platforms=${{ matrix.target }} \ - ${{ matrix.tests }} + - uses: nick-fields/retry@v3 + with: + timeout_minutes: 120 + max_attempts: 3 + retry_on: error + retry_wait_seconds: 120 + command: scripts/pod_lib_lint.rb FirebaseCrashlytics.podspec --platforms=${{ matrix.target }} ${{ matrix.tests }} spm: # Don't run on private repo unless it is a PR. @@ -57,7 +60,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -68,9 +71,13 @@ jobs: run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - name: Initialize xcodebuild run: scripts/setup_spm_tests.sh - - name: Unit Tests - run: scripts/third_party/travis/retry.sh ./scripts/build.sh FirebaseCrashlyticsUnit ${{ matrix.target }} spm - + - uses: nick-fields/retry@v3 + with: + timeout_minutes: 120 + max_attempts: 3 + retry_on: error + retry_wait_seconds: 120 + command: scripts/third_party/travis/retry.sh ./scripts/build.sh FirebaseCrashlyticsUnit ${{ matrix.target }} spm catalyst: # Don't run on private repo unless it is a PR. @@ -85,8 +92,13 @@ jobs: - uses: ruby/setup-ruby@v1 - name: Setup Bundler run: scripts/setup_bundler.sh - - name: Setup project and Build for Catalyst - run: scripts/test_catalyst.sh FirebaseCrashlytics test FirebaseCrashlytics-Unit-unit + - uses: nick-fields/retry@v3 + with: + timeout_minutes: 120 + max_attempts: 3 + retry_on: error + retry_wait_seconds: 120 + command: scripts/test_catalyst.sh FirebaseCrashlytics test FirebaseCrashlytics-Unit-unit quickstart: # Don't run on private repo unless it is a PR. diff --git a/.github/workflows/database.yml b/.github/workflows/database.yml index 50fee76ee61..857e559b023 100644 --- a/.github/workflows/database.yml +++ b/.github/workflows/database.yml @@ -33,7 +33,7 @@ jobs: xcode: Xcode_14.2 tests: --skip-tests - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 tests: --test-specs=unit runs-on: ${{ matrix.os }} steps: @@ -75,7 +75,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/dynamiclinks.yml b/.github/workflows/dynamiclinks.yml index 2cc95b49443..310b7a88de6 100644 --- a/.github/workflows/dynamiclinks.yml +++ b/.github/workflows/dynamiclinks.yml @@ -27,7 +27,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -49,7 +49,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/firebase_app_check.yml b/.github/workflows/firebase_app_check.yml index 3bfe934f3c2..4893fe26042 100644 --- a/.github/workflows/firebase_app_check.yml +++ b/.github/workflows/firebase_app_check.yml @@ -28,7 +28,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -72,7 +72,7 @@ jobs: run: scripts/third_party/travis/retry.sh ./scripts/build.sh FirebaseAppCheckUnit iOS spm ${{ matrix.diagnostic }} - name: Upload raw logs if failed if: ${{ failure() }} - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: failure-xcodebuild-raw-logs path: xcodebuild.log @@ -107,7 +107,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/firestore.yml b/.github/workflows/firestore.yml index 49464754d05..cc257911e3f 100644 --- a/.github/workflows/firestore.yml +++ b/.github/workflows/firestore.yml @@ -369,7 +369,7 @@ jobs: - name: Setup Bundler run: ./scripts/setup_bundler.sh - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_15.1.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer - name: Pod lib lint # TODO(#9565, b/227461966): Remove --no-analyze when absl is fixed. @@ -411,7 +411,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: @@ -445,7 +445,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} needs: check env: diff --git a/.github/workflows/functions.yml b/.github/workflows/functions.yml index 1c19f64a0ae..423b44bb372 100644 --- a/.github/workflows/functions.yml +++ b/.github/workflows/functions.yml @@ -35,7 +35,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -93,7 +93,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/health-metrics-presubmit.yml b/.github/workflows/health-metrics-presubmit.yml index 5218ff651cf..c9fe876604c 100644 --- a/.github/workflows/health-metrics-presubmit.yml +++ b/.github/workflows/health-metrics-presubmit.yml @@ -75,7 +75,7 @@ jobs: run: scripts/setup_bundler.sh - name: Build and test run: ./scripts/health_metrics/pod_test_code_coverage_report.sh --sdk=FirebaseABTesting --platform=${{ matrix.target }} - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: codecoverage path: /Users/runner/*.xcresult @@ -95,7 +95,7 @@ jobs: run: scripts/setup_bundler.sh - name: Build and test run: ./scripts/health_metrics/pod_test_code_coverage_report.sh --sdk=FirebaseAuth --platform=${{ matrix.target }} - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: codecoverage path: /Users/runner/*.xcresult @@ -118,7 +118,9 @@ jobs: run: scripts/setup_bundler.sh - name: Build and test run: ./scripts/health_metrics/pod_test_code_coverage_report.sh --sdk=FirebaseDatabase --platform=${{ matrix.target }} - - uses: actions/upload-artifact@v2 + # TODO: Make sure that https://github.com/actions/upload-artifact/issues/478 is resolved + # before going to actions/upload-artifact@v4. + - uses: actions/upload-artifact@v3 with: name: codecoverage path: /Users/runner/*.xcresult @@ -141,7 +143,7 @@ jobs: run: scripts/setup_bundler.sh - name: Build and test run: ./scripts/health_metrics/pod_test_code_coverage_report.sh --sdk=FirebaseDynamicLinks --platform=${{ matrix.target }} - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: codecoverage path: /Users/runner/*.xcresult @@ -167,7 +169,7 @@ jobs: run: | export EXPERIMENTAL_MODE=true ./scripts/health_metrics/pod_test_code_coverage_report.sh --sdk=FirebaseFirestore --platform=${{ matrix.target }} - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: codecoverage path: /Users/runner/*.xcresult @@ -190,7 +192,7 @@ jobs: run: scripts/setup_bundler.sh - name: Build and test run: ./scripts/health_metrics/pod_test_code_coverage_report.sh --sdk=FirebaseFunctions --platform=${{ matrix.target }} - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: codecoverage path: /Users/runner/*.xcresult @@ -213,7 +215,7 @@ jobs: run: scripts/setup_bundler.sh - name: Build and test run: ./scripts/health_metrics/pod_test_code_coverage_report.sh --sdk=FirebaseInAppMessaging --platform=${{ matrix.target }} - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: codecoverage path: /Users/runner/*.xcresult @@ -236,7 +238,7 @@ jobs: run: scripts/setup_bundler.sh - name: Build and test run: ./scripts/health_metrics/pod_test_code_coverage_report.sh --sdk=FirebaseMessaging --platform=${{ matrix.target }} - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: codecoverage path: /Users/runner/*.xcresult @@ -261,7 +263,7 @@ jobs: run: gem install xcpretty - name: Build and test run: ./scripts/health_metrics/pod_test_code_coverage_report.sh --sdk=FirebasePerformance --platform=${{ matrix.target }} - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: codecoverage path: /Users/runner/*.xcresult @@ -284,7 +286,7 @@ jobs: run: scripts/setup_bundler.sh - name: Build and test run: ./scripts/health_metrics/pod_test_code_coverage_report.sh --sdk=FirebaseRemoteConfig --platform=${{ matrix.target }} - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: codecoverage path: /Users/runner/*.xcresult @@ -307,7 +309,7 @@ jobs: run: scripts/setup_bundler.sh - name: Build and test run: ./scripts/health_metrics/pod_test_code_coverage_report.sh --sdk=FirebaseStorage --platform=${{ matrix.target }} - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: codecoverage path: /Users/runner/*.xcresult @@ -334,7 +336,7 @@ jobs: scripts/decrypt_gha_secret.sh scripts/gha-encrypted/metrics_service_access.json.gpg \ metrics-access.json "${{ env.METRICS_SERVICE_SECRET }}" gcloud auth activate-service-account --key-file metrics-access.json - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v3 id: download with: path: /Users/runner/test diff --git a/.github/workflows/health-metrics-release.yml b/.github/workflows/health-metrics-release.yml deleted file mode 100644 index 86934534175..00000000000 --- a/.github/workflows/health-metrics-release.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: health-metrics-release - -on: - release: - types: [published] - -env: - gpg_passphrase: ${{ secrets.GHASecretsGPGPassphrase1 }} - - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} - cancel-in-progress: true - -jobs: - release-diffing: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - name: Set up Google Cloud SDK - uses: google-github-actions/setup-gcloud@main - - name: Authenticate Google Cloud SDK - run: | - scripts/decrypt_gha_secret.sh \ - scripts/gha-encrypted/metrics_service_access.json.gpg \ - service_account.json \ - "${{ env.gpg_passphrase }}" - gcloud auth activate-service-account --key-file service_account.json - - name: Produce health metric diff reports - uses: FirebaseExtended/github-actions/health-metrics/release-diffing@master - with: - repo: ${{ github.repository }} - ref: ${{ github.ref }} - commit: ${{ github.sha }} - releaseId: ${{ github.event.release.id }} diff --git a/.github/workflows/inappmessaging.yml b/.github/workflows/inappmessaging.yml index b29a51e0ae5..6c4ced921da 100644 --- a/.github/workflows/inappmessaging.yml +++ b/.github/workflows/inappmessaging.yml @@ -29,7 +29,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -75,7 +75,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/installations.yml b/.github/workflows/installations.yml index c89c8c695f0..72ba9d5bd23 100644 --- a/.github/workflows/installations.yml +++ b/.github/workflows/installations.yml @@ -31,7 +31,7 @@ jobs: xcode: Xcode_14.2 test-specs: unit,integration - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 test-specs: unit runs-on: ${{ matrix.os }} steps: @@ -69,7 +69,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/messaging.yml b/.github/workflows/messaging.yml index 618e629668b..0524dbb8f76 100644 --- a/.github/workflows/messaging.yml +++ b/.github/workflows/messaging.yml @@ -48,7 +48,7 @@ jobs: scripts/decrypt_gha_secret.sh scripts/gha-encrypted/messaging-sample-plist.gpg \ FirebaseMessaging/Tests/IntegrationTests/Resources/GoogleService-Info.plist "$plist_secret" - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_15.1.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer - name: BuildAndTest run: ([ -z $plist_secret ] || scripts/third_party/travis/retry.sh scripts/build.sh Messaging all) @@ -65,7 +65,7 @@ jobs: xcode: Xcode_14.2 tests: --test-specs=unit - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 tests: --skip-tests runs-on: ${{ matrix.os }} steps: @@ -89,7 +89,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -130,7 +130,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -192,7 +192,7 @@ jobs: xcode: Xcode_14.2 tests: --test-specs=unit - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 tests: --skip-tests runs-on: ${{ matrix.os }} steps: @@ -226,7 +226,7 @@ jobs: - name: Prereqs run: scripts/install_prereqs.sh MessagingSample iOS - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_15.1.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer - name: Build run: ([ -z $plist_secret ] || scripts/build.sh MessagingSample iOS) @@ -251,7 +251,7 @@ jobs: - name: Prereqs run: scripts/install_prereqs.sh SwiftUISample iOS - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_15.1.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer - name: Build run: ([ -z $plist_secret ] || scripts/build.sh SwiftUISample iOS) @@ -276,7 +276,7 @@ jobs: - name: Prereqs run: scripts/install_prereqs.sh MessagingSampleStandaloneWatchApp watchOS - name: Xcode - run: sudo xcode-select -s /Applications/Xcode_15.1.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_15.2.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 5210baa686f..4cf1332519b 100644 --- a/.github/workflows/mlmodeldownloader.yml +++ b/.github/workflows/mlmodeldownloader.yml @@ -27,7 +27,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -80,7 +80,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/performance.yml b/.github/workflows/performance.yml index 7f2cedfb6fb..1452e205385 100644 --- a/.github/workflows/performance.yml +++ b/.github/workflows/performance.yml @@ -60,7 +60,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -132,7 +132,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index b4252adb6ba..87164ab1c1d 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -62,7 +62,7 @@ jobs: if: ${{ always() }} run: | rm -rf oss-bot-access.txt - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: firebase-ios-sdk path: | @@ -80,7 +80,7 @@ jobs: targeted_pod: FirebaseCore steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: firebase-ios-sdk path: ${{ env.local_sdk_repo_dir }} @@ -122,7 +122,7 @@ jobs: targeted_pod: ${{ matrix.podspec }} steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: firebase-ios-sdk path: ${{ env.local_sdk_repo_dir }} @@ -234,7 +234,7 @@ jobs: env: LEGACY: true run: scripts/remove_data.sh config release_testing - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: name: quickstart_artifacts_abtesting @@ -266,7 +266,7 @@ jobs: run: ([ -z $plist_secret ] || scripts/third_party/travis/retry.sh scripts/test_quickstart.sh Authentication false) - name: Remove data before upload run: scripts/remove_data.sh authentication release_testing - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: name: quickstart_artifacts_auth @@ -315,7 +315,7 @@ jobs: env: LEGACY: true run: scripts/remove_data.sh crashlytics release_testing - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: name: quickstart_artifacts_crashlytics @@ -351,7 +351,7 @@ jobs: run: ([ -z $plist_secret ] || scripts/third_party/travis/retry.sh scripts/test_quickstart.sh Database false swift) - name: Remove data before upload run: scripts/remove_data.sh database release_testing - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: name: quickstart_artifacts_database @@ -393,7 +393,7 @@ jobs: run: ([ -z $plist_secret ] || scripts/third_party/travis/retry.sh scripts/test_quickstart.sh DynamicLinks true swift) - name: Remove data before upload run: scripts/remove_data.sh dynamiclinks release_testing - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: name: quickstart_artifacts_dynamiclinks @@ -428,7 +428,7 @@ jobs: scripts/third_party/travis/retry.sh scripts/test_quickstart.sh Firestore false) - name: Remove data before upload run: scripts/remove_data.sh firestore release_testing - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: name: quickstart_artifacts_firestore @@ -469,7 +469,7 @@ jobs: scripts/third_party/travis/retry.sh scripts/test_quickstart.sh Functions true swift) - name: Remove data before upload run: scripts/remove_data.sh functions release_testing - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: name: quickstart_artifacts_functions @@ -507,7 +507,7 @@ jobs: scripts/third_party/travis/retry.sh scripts/test_quickstart.sh InAppMessaging true swift) - name: Remove data before upload run: scripts/remove_data.sh inappmessaging release_testing - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: name: quickstart_artifacts_inappmessaging @@ -545,7 +545,7 @@ jobs: scripts/third_party/travis/retry.sh scripts/test_quickstart.sh Messaging false swift) - name: Remove data before upload run: scripts/remove_data.sh messaging release_testing - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: name: quickstart_artifacts_messaging @@ -577,7 +577,7 @@ jobs: run: ([ -z $plist_secret ] || scripts/third_party/travis/retry.sh scripts/test_quickstart.sh Config true) - name: Remove data before upload run: scripts/remove_data.sh config release_testing - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: name: quickstart_artifacts_config @@ -614,7 +614,7 @@ jobs: run: ([ -z $plist_secret ] || scripts/third_party/travis/retry.sh scripts/test_quickstart.sh Storage true swift) - name: Remove data before upload run: scripts/remove_data.sh storage release_testing - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: name: quickstart_artifacts_storage @@ -651,7 +651,7 @@ jobs: run: ([ -z $plist_secret ] || scripts/third_party/travis/retry.sh scripts/test_quickstart.sh Performance true swift) - name: Remove data before upload run: scripts/remove_data.sh performance release_testing - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: name: quickstart_artifacts_performance diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1434c97c119..dcf67d29dba 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -66,7 +66,7 @@ jobs: if: ${{ always() }} run: | rm -rf bot-access.txt - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: firebase-ios-sdk path: | @@ -84,7 +84,7 @@ jobs: targeted_pod: FirebaseCore steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: firebase-ios-sdk path: ${{ env.local_sdk_repo_dir }} @@ -124,7 +124,7 @@ jobs: targeted_pod: ${{ matrix.podspec }} steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: firebase-ios-sdk path: ${{ env.local_sdk_repo_dir }} @@ -185,7 +185,7 @@ jobs: env: LEGACY: true run: scripts/remove_data.sh config release_testing - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: name: quickstart_artifacts_abtesting @@ -217,7 +217,7 @@ jobs: run: ([ -z $plist_secret ] || scripts/third_party/travis/retry.sh scripts/test_quickstart.sh Authentication false) - name: Remove data before upload run: scripts/remove_data.sh authentication release_testing - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: name: quickstart_artifacts_auth @@ -266,7 +266,7 @@ jobs: env: LEGACY: true run: scripts/remove_data.sh crashlytics release_testing - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: name: quickstart_artifacts_crashlytics @@ -302,7 +302,7 @@ jobs: run: ([ -z $plist_secret ] || scripts/third_party/travis/retry.sh scripts/test_quickstart.sh Database false swift) - name: Remove data before upload run: scripts/remove_data.sh database release_testing - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: name: quickstart_artifacts_database @@ -344,7 +344,7 @@ jobs: run: ([ -z $plist_secret ] || scripts/third_party/travis/retry.sh scripts/test_quickstart.sh DynamicLinks true swift) - name: Remove data before upload run: scripts/remove_data.sh dynamiclinks release_testing - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: name: quickstart_artifacts_dynamiclinks @@ -379,7 +379,7 @@ jobs: scripts/third_party/travis/retry.sh scripts/test_quickstart.sh Firestore false) - name: Remove data before upload run: scripts/remove_data.sh firestore release_testing - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: name: quickstart_artifacts_firestore @@ -420,7 +420,7 @@ jobs: scripts/third_party/travis/retry.sh scripts/test_quickstart.sh Functions true swift) - name: Remove data before upload run: scripts/remove_data.sh functions release_testing - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: name: quickstart_artifacts_functions @@ -458,7 +458,7 @@ jobs: scripts/third_party/travis/retry.sh scripts/test_quickstart.sh InAppMessaging true swift) - name: Remove data before upload run: scripts/remove_data.sh inappmessaging release_testing - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: name: quickstart_artifacts_inappmessaging @@ -496,7 +496,7 @@ jobs: scripts/third_party/travis/retry.sh scripts/test_quickstart.sh Messaging false swift) - name: Remove data before upload run: scripts/remove_data.sh messaging release_testing - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: name: quickstart_artifacts_messaging @@ -528,7 +528,7 @@ jobs: run: ([ -z $plist_secret ] || scripts/third_party/travis/retry.sh scripts/test_quickstart.sh Config true) - name: Remove data before upload run: scripts/remove_data.sh config release_testing - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: name: quickstart_artifacts_config @@ -565,7 +565,7 @@ jobs: run: ([ -z $plist_secret ] || scripts/third_party/travis/retry.sh scripts/test_quickstart.sh Storage true swift) - name: Remove data before upload run: scripts/remove_data.sh storage release_testing - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: name: quickstart_artifacts_storage @@ -602,7 +602,7 @@ jobs: run: ([ -z $plist_secret ] || scripts/third_party/travis/retry.sh scripts/test_quickstart.sh Performance true swift) - name: Remove data before upload run: scripts/remove_data.sh performance release_testing - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: name: quickstart_artifacts_performance diff --git a/.github/workflows/remoteconfig.yml b/.github/workflows/remoteconfig.yml index f7704ff3b2e..60963dfc4cb 100644 --- a/.github/workflows/remoteconfig.yml +++ b/.github/workflows/remoteconfig.yml @@ -68,7 +68,7 @@ jobs: tests: # Flaky tests on CI - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 tests: --skip-tests runs-on: ${{ matrix.os }} steps: @@ -95,7 +95,7 @@ jobs: xcode: Xcode_14.2 test: spm - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 test: spmbuildonly runs-on: ${{ matrix.os }} steps: diff --git a/.github/workflows/sessions.yml b/.github/workflows/sessions.yml index ccf8fe00e09..312da50f72f 100644 --- a/.github/workflows/sessions.yml +++ b/.github/workflows/sessions.yml @@ -31,7 +31,7 @@ jobs: tests: # Flaky tests on CI - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 tests: --skip-tests runs-on: ${{ matrix.os }} steps: @@ -57,7 +57,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/shared-swift.yml b/.github/workflows/shared-swift.yml index 5fe07b43f56..34edbe69a2c 100644 --- a/.github/workflows/shared-swift.yml +++ b/.github/workflows/shared-swift.yml @@ -29,7 +29,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -52,7 +52,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/spectesting.yml b/.github/workflows/spectesting.yml index a8592310013..11a58f5c8ec 100644 --- a/.github/workflows/spectesting.yml +++ b/.github/workflows/spectesting.yml @@ -66,7 +66,7 @@ jobs: swift run podspecs-tester --git-root "${GITHUB_WORKSPACE}" --podspec ${PODSPEC} --skip-tests --temp-log-dir "${GITHUB_WORKSPACE}/specTestingLogs" - name: Upload Failed Testing Logs if: failure() - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: specTestingLogs path: specTestingLogs/*.txt diff --git a/.github/workflows/spm.yml b/.github/workflows/spm.yml index 53fddd8d0f7..a2563af2cc4 100644 --- a/.github/workflows/spm.yml +++ b/.github/workflows/spm.yml @@ -34,7 +34,7 @@ jobs: test: spm # The integration tests are slow and flaky on Xcode 15, so just build. - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 test: spmbuildonly runs-on: ${{ matrix.os }} steps: @@ -62,7 +62,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -89,7 +89,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/storage.yml b/.github/workflows/storage.yml index dcedb25ff8a..d5ae8073ddf 100644 --- a/.github/workflows/storage.yml +++ b/.github/workflows/storage.yml @@ -25,7 +25,7 @@ jobs: language: [Swift, ObjC] include: - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 env: plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} runs-on: ${{ matrix.os }} @@ -51,8 +51,13 @@ jobs: FirebaseStorage/Tests/Integration/Credentials.swift "$plist_secret" - name: Xcode run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - - name: BuildAndTest # can be replaced with pod lib lint with CocoaPods 1.10 - run: ([ -z $plist_secret ] || scripts/third_party/travis/retry.sh scripts/build.sh Storage${{ matrix.language }} all) + - uses: nick-fields/retry@v3 + with: + timeout_minutes: 120 + max_attempts: 3 + retry_on: error + retry_wait_seconds: 120 + command: ([ -z $plist_secret ] || scripts/build.sh Storage${{ matrix.language }} all) spm: # Don't run on private repo unless it is a PR. @@ -63,7 +68,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -88,7 +93,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -107,13 +112,18 @@ jobs: quickstart: # 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' + # TODO: See #12399 and restore Objective-C testing for Xcode 15 if GHA is fixed. strategy: matrix: include: - os: macos-12 xcode: Xcode_14.2 - - os: macos-13 - xcode: Xcode_15.1 + - swift: swift + os: macos-13 + xcode: Xcode_15.2 + - swift: swift + os: macos-13 + xcode: Xcode_15.2 env: plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} signin_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} @@ -129,10 +139,8 @@ jobs: quickstart-ios/storage/GoogleService-Info.plist "$plist_secret" - name: Xcode run: sudo xcode-select -s /Applications/${{ matrix.xcode }}.app/Contents/Developer - - name: Test objc quickstart - run: ([ -z $plist_secret ] || scripts/third_party/travis/retry.sh scripts/test_quickstart.sh Storage true) - - name: Test swift quickstart - run: ([ -z $plist_secret ] || scripts/third_party/travis/retry.sh scripts/test_quickstart.sh Storage true swift) + - name: Test quickstart + run: ([ -z $plist_secret ] || scripts/third_party/travis/retry.sh scripts/test_quickstart.sh Storage true ${{ matrix.swift }}) quickstart-ftl-cron-only: # Don't run on private repo. @@ -176,7 +184,7 @@ jobs: xcode: Xcode_14.2 tests: --skip-tests - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 tests: --test-specs=unit runs-on: ${{ matrix.os }} steps: @@ -204,7 +212,7 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} needs: pod-lib-lint steps: diff --git a/.github/workflows/zip.yml b/.github/workflows/zip.yml index 569b12b472f..45578d33761 100644 --- a/.github/workflows/zip.yml +++ b/.github/workflows/zip.yml @@ -44,7 +44,7 @@ jobs: mkdir -p release_zip_dir sh -x scripts/build_zip.sh release_zip_dir \ "${{ github.event.inputs.custom_spec_repos || 'https://github.com/firebase/SpecsStaging.git' }}" - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 with: name: Firebase-release-zip-zip # Zip the entire output directory since the builder adds subdirectories we don't know the @@ -85,7 +85,7 @@ jobs: sh -x scripts/build_zip.sh \ zip_output_dir "${{ github.event.inputs.custom_spec_repos || 'https://github.com/firebase/SpecsStaging.git,https://github.com/firebase/SpecsDev.git' }}" \ build-head - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 with: name: Firebase-actions-dir # Zip the entire output directory since the builder adds subdirectories we don't know the @@ -99,7 +99,6 @@ jobs: env: plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} signin_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} - FRAMEWORK_DIR: "Firebase-actions-dir" SDK: "ABTesting" strategy: matrix: @@ -108,12 +107,12 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - name: Get framework dir - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v4 with: name: Firebase-actions-dir - uses: ruby/setup-ruby@v1 @@ -122,7 +121,7 @@ jobs: - name: Move frameworks run: | mkdir -p "${HOME}"/ios_frameworks/ - find "${GITHUB_WORKSPACE}/${FRAMEWORK_DIR}" -name "Firebase*latest.zip" -exec unzip -d "${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 @@ -148,7 +147,7 @@ jobs: LEGACY: true if: ${{ failure() }} run: scripts/remove_data.sh abtesting - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: name: quickstart_artifacts_abtesting @@ -161,7 +160,6 @@ jobs: env: plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} signin_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} - FRAMEWORK_DIR: "Firebase-actions-dir" SDK: "Authentication" strategy: matrix: @@ -170,12 +168,12 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - name: Get framework dir - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v4 with: name: Firebase-actions-dir - uses: ruby/setup-ruby@v1 @@ -184,7 +182,7 @@ jobs: - name: Move frameworks run: | mkdir -p "${HOME}"/ios_frameworks/ - find "${GITHUB_WORKSPACE}/${FRAMEWORK_DIR}" -name "Firebase*latest.zip" -exec unzip -d "${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 @@ -202,7 +200,7 @@ jobs: - name: Remove data before upload if: ${{ failure() }} run: scripts/remove_data.sh authentiation - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: name: quickstart_artifacts_auth @@ -215,7 +213,6 @@ jobs: env: plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} signin_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} - FRAMEWORK_DIR: "Firebase-actions-dir" SDK: "Config" strategy: matrix: @@ -224,12 +221,12 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - name: Get framework dir - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v4 with: name: Firebase-actions-dir - uses: ruby/setup-ruby@v1 @@ -238,7 +235,7 @@ jobs: - name: Move frameworks run: | mkdir -p "${HOME}"/ios_frameworks/ - find "${GITHUB_WORKSPACE}/${FRAMEWORK_DIR}" -name "Firebase*latest.zip" -exec unzip -d "${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 @@ -254,7 +251,7 @@ jobs: - name: Remove data before upload if: ${{ failure() }} run: scripts/remove_data.sh config - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: name: quickstart_artifacts_config @@ -267,7 +264,6 @@ jobs: env: plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} signin_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} - FRAMEWORK_DIR: "Firebase-actions-dir" SDK: "Crashlytics" strategy: matrix: @@ -276,12 +272,12 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - name: Get framework dir - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v4 with: name: Firebase-actions-dir - uses: ruby/setup-ruby@v1 @@ -290,7 +286,7 @@ jobs: - name: Move frameworks run: | mkdir -p "${HOME}"/ios_frameworks/ - find "${GITHUB_WORKSPACE}/${FRAMEWORK_DIR}" -name "Firebase*latest.zip" -exec unzip -d "${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 @@ -329,7 +325,7 @@ jobs: LEGACY: true if: ${{ failure() }} run: scripts/remove_data.sh crashlytics - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: name: quickstart_artifacts_crashlytics @@ -342,7 +338,6 @@ jobs: env: plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} signin_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} - FRAMEWORK_DIR: "Firebase-actions-dir" SDK: "Database" strategy: matrix: @@ -352,12 +347,12 @@ jobs: xcode: Xcode_14.2 # TODO: Building FirebaseUI fails on Xcode 15 because it needs to sign the resources. # - os: macos-13 - # xcode: Xcode_15.1 + # xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - name: Get framework dir - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v4 with: name: Firebase-actions-dir - uses: ruby/setup-ruby@v1 @@ -366,7 +361,7 @@ jobs: - name: Move frameworks run: | mkdir -p "${HOME}"/ios_frameworks/ - find "${GITHUB_WORKSPACE}/${FRAMEWORK_DIR}" -name "Firebase*latest.zip" -exec unzip -d "${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 @@ -386,7 +381,7 @@ jobs: - name: Remove data before upload if: ${{ failure() }} run: scripts/remove_data.sh database - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: name: quickstart_artifacts database @@ -399,7 +394,6 @@ jobs: env: plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} signin_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} - FRAMEWORK_DIR: "Firebase-actions-dir" SDK: "DynamicLinks" strategy: matrix: @@ -408,12 +402,12 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - name: Get framework dir - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v4 with: name: Firebase-actions-dir - uses: ruby/setup-ruby@v1 @@ -422,7 +416,7 @@ jobs: - name: Move frameworks run: | mkdir -p "${HOME}"/ios_frameworks/ - find "${GITHUB_WORKSPACE}/${FRAMEWORK_DIR}" -name "Firebase*latest.zip" -exec unzip -d "${HOME}"/ios_frameworks/ {} + + find "${GITHUB_WORKSPACE}" -name "Firebase*latest.zip" -exec unzip -d "${HOME}"/ios_frameworks/ {} + - name: Setup Objc Quickstart run: SAMPLE="$SDK" TARGET="${SDK}Example" scripts/setup_quickstart_framework.sh \ "${HOME}"/ios_frameworks/Firebase/FirebaseDynamicLinks/* \ @@ -446,7 +440,7 @@ jobs: - name: Remove data before upload if: ${{ failure() }} run: scripts/remove_data.sh dynamiclinks - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: name: quickstart_artifacts_dynamiclinks @@ -459,7 +453,6 @@ jobs: env: plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} signin_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} - FRAMEWORK_DIR: "Firebase-actions-dir" SDK: "Firestore" strategy: matrix: @@ -469,12 +462,12 @@ jobs: xcode: Xcode_14.2 # TODO: Building FirebaseUI fails on Xcode 15 because it needs to sign the resources. # - os: macos-13 - # xcode: Xcode_15.1 + # xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - name: Get framework dir - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v4 with: name: Firebase-actions-dir - uses: ruby/setup-ruby@v1 @@ -483,7 +476,7 @@ jobs: - name: Move frameworks run: | mkdir -p "${HOME}"/ios_frameworks/ - find "${GITHUB_WORKSPACE}/${FRAMEWORK_DIR}" -name "Firebase*latest.zip" -exec unzip -d "${HOME}"/ios_frameworks/ {} + + find "${GITHUB_WORKSPACE}" -name "Firebase*latest.zip" -exec unzip -d "${HOME}"/ios_frameworks/ {} + - uses: actions/checkout@v4 - name: Setup quickstart run: SAMPLE="$SDK" TARGET="${SDK}Example" NON_FIREBASE_SDKS="SDWebImage FirebaseAuthUI FirebaseEmailAuthUI" scripts/setup_quickstart_framework.sh \ @@ -501,7 +494,7 @@ jobs: - name: Remove data before upload if: ${{ failure() }} run: scripts/remove_data.sh firestore - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: name: quickstart_artifacts_firestore @@ -512,7 +505,6 @@ jobs: if: (github.repository == 'Firebase/firebase-ios-sdk' && github.event_name == 'schedule') || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' needs: package-head env: - FRAMEWORK_DIR: "Firebase-actions-dir" FIREBASECI_USE_LATEST_GOOGLEAPPMEASUREMENT: 1 runs-on: macos-13 steps: @@ -520,7 +512,7 @@ jobs: run: sudo xcode-select -s /Applications/Xcode_14.1.app/Contents/Developer - uses: actions/checkout@v4 - name: Get framework dir - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v4 with: name: Firebase-actions-dir - uses: ruby/setup-ruby@v1 @@ -531,7 +523,7 @@ jobs: - name: Move frameworks run: | mkdir -p "${HOME}"/ios_frameworks/ - find "${GITHUB_WORKSPACE}/${FRAMEWORK_DIR}" -name "Firebase*latest.zip" -exec unzip -d "${HOME}"/ios_frameworks/ {} + + find "${GITHUB_WORKSPACE}" -name "Firebase*latest.zip" -exec unzip -d "${HOME}"/ios_frameworks/ {} + - uses: actions/checkout@v4 - name: Check linked Firestore.xcframework for unlinked symbols. run: | @@ -546,7 +538,6 @@ jobs: env: plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} signin_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} - FRAMEWORK_DIR: "Firebase-actions-dir" SDK: "InAppMessaging" strategy: matrix: @@ -555,12 +546,12 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - name: Get framework dir - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v4 with: name: Firebase-actions-dir - uses: ruby/setup-ruby@v1 @@ -569,7 +560,7 @@ jobs: - name: Move frameworks run: | mkdir -p "${HOME}"/ios_frameworks/ - find "${GITHUB_WORKSPACE}/${FRAMEWORK_DIR}" -name "Firebase*latest.zip" -exec unzip -d "${HOME}"/ios_frameworks/ {} + + find "${GITHUB_WORKSPACE}" -name "Firebase*latest.zip" -exec unzip -d "${HOME}"/ios_frameworks/ {} + - uses: actions/checkout@v4 - name: Setup quickstart run: SAMPLE="$SDK" TARGET="${SDK}Example" scripts/setup_quickstart_framework.sh \ @@ -590,10 +581,10 @@ jobs: - name: Remove data before upload if: ${{ failure() }} run: scripts/remove_data.sh inappmessaging - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: - name: quickstart_artifacts_ihappmessaging + name: quickstart_artifacts_inappmessaging path: quickstart-ios/ quickstart_framework_messaging: @@ -603,7 +594,6 @@ jobs: env: plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} signin_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} - FRAMEWORK_DIR: "Firebase-actions-dir" SDK: "Messaging" strategy: matrix: @@ -612,12 +602,12 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - name: Get framework dir - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v4 with: name: Firebase-actions-dir - uses: ruby/setup-ruby@v1 @@ -626,7 +616,7 @@ jobs: - name: Move frameworks run: | mkdir -p "${HOME}"/ios_frameworks/ - find "${GITHUB_WORKSPACE}/${FRAMEWORK_DIR}" -name "Firebase*latest.zip" -exec unzip -d "${HOME}"/ios_frameworks/ {} + + find "${GITHUB_WORKSPACE}" -name "Firebase*latest.zip" -exec unzip -d "${HOME}"/ios_frameworks/ {} + - uses: actions/checkout@v4 - name: Setup quickstart run: SAMPLE="$SDK" TARGET="${SDK}Example" scripts/setup_quickstart_framework.sh \ @@ -646,7 +636,7 @@ jobs: - name: Remove data before upload if: ${{ failure() }} run: scripts/remove_data.sh messaging - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: name: quickstart_artifacts_messaging @@ -659,7 +649,6 @@ jobs: env: plist_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} signin_secret: ${{ secrets.GHASecretsGPGPassphrase1 }} - FRAMEWORK_DIR: "Firebase-actions-dir" SDK: "Storage" strategy: matrix: @@ -668,12 +657,12 @@ jobs: - os: macos-12 xcode: Xcode_14.2 - os: macos-13 - xcode: Xcode_15.1 + xcode: Xcode_15.2 runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - name: Get framework dir - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v4 with: name: Firebase-actions-dir - uses: ruby/setup-ruby@v1 @@ -682,7 +671,7 @@ jobs: - name: Move frameworks run: | mkdir -p "${HOME}"/ios_frameworks/ - find "${GITHUB_WORKSPACE}/${FRAMEWORK_DIR}" -name "Firebase*latest.zip" -exec unzip -d "${HOME}"/ios_frameworks/ {} + + find "${GITHUB_WORKSPACE}" -name "Firebase*latest.zip" -exec unzip -d "${HOME}"/ios_frameworks/ {} + - uses: actions/checkout@v4 - name: Setup quickstart env: @@ -713,7 +702,7 @@ jobs: LEGACY: true if: ${{ failure() }} run: scripts/remove_data.sh storage - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 if: ${{ failure() }} with: name: quickstart_artifacts_storage diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c1c20896cb1..b62328f4dde 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -132,7 +132,7 @@ To develop Firebase software, **install**: To install [clang-format] and [mint] using [Homebrew]: ```console - brew install clang-format@17 + brew install clang-format@18 brew install mint ``` @@ -187,7 +187,7 @@ To learn more about running tests with Swift Package Manager, visit the #### **[CocoaPods]** [CocoaPods] is another popular dependency manager used in Apple development. -Firebase supports development with CocoaPods 1.10.0 (or later). If you choose to +Firebase supports development with CocoaPods 1.12.0 (or later). If you choose to develop using CocoaPods, it's recommend to use [`cocoapods-generate`][cocoapods-generate], a plugin that generates a [workspace] from a [podspec]. This plugin allows you to quickly generate a diff --git a/CoreOnly/NOTICES b/CoreOnly/NOTICES index 659f6159e33..802eaf57d93 100644 --- a/CoreOnly/NOTICES +++ b/CoreOnly/NOTICES @@ -18,6 +18,7 @@ FirebaseMessaging FirebaseMessagingInterop FirebasePerformance FirebaseRemoteConfig +FirebaseRemoteConfigInterop FirebaseSessions FirebaseStorage GTMSessionFetcher @@ -253,6 +254,7 @@ record keeping.) 27287199 27287880 27287883 + 263291445 OpenSSL License --------------- diff --git a/CoreOnly/Sources/Firebase.h b/CoreOnly/Sources/Firebase.h index 8a7420d42c3..37d5f9e495c 100755 --- a/CoreOnly/Sources/Firebase.h +++ b/CoreOnly/Sources/Firebase.h @@ -32,6 +32,13 @@ #if __has_include() #import + #if __has_include("FirebaseAuth-umbrella.h") + #if __has_include() + #import + #endif + #import + #import + #endif #endif #if __has_include() diff --git a/CoreOnly/Tests/FirebasePodTest/Podfile b/CoreOnly/Tests/FirebasePodTest/Podfile index dfe7a0fa557..1e7dacbdb17 100644 --- a/CoreOnly/Tests/FirebasePodTest/Podfile +++ b/CoreOnly/Tests/FirebasePodTest/Podfile @@ -33,6 +33,7 @@ target 'FirebasePodTest' do pod 'FirebaseAppCheckInterop', :path => '../../../' pod 'FirebaseAuthInterop', :path => '../../../' pod 'FirebaseMessagingInterop', :path => '../../../' + pod 'FirebaseRemoteConfigInterop', :path => '../../../' pod 'FirebaseCoreInternal', :path => '../../../' pod 'FirebaseCoreExtension', :path => '../../../' pod 'FirebaseSessions', :path => '../../../' diff --git a/Crashlytics/CHANGELOG.md b/Crashlytics/CHANGELOG.md index 997798c64db..ce0e9c89eea 100644 --- a/Crashlytics/CHANGELOG.md +++ b/Crashlytics/CHANGELOG.md @@ -1,3 +1,12 @@ +# 10.23.0 +- [added] Updated upload-symbols to 13.7 with VisionPro build phase support. (#12306) +- [changed] Added support for Crashlytics to report metadata about Remote Config keys and values. + +# 10.22.0 +- [fixed] Force validation or rotation of FIDs for FirebaseSessions. +- [changed] Removed calls to statfs in the Crashlytics SDK to comply with Apple Privacy Manifests. This change removes support for collecting Disk Space Free in Crashlytics reports. +- [fixed] Fixed FirebaseSessions crash on startup that occurs in release mode in Xcode 15.3 and other build configurations. (#11403) + # 10.16.0 - [fixed] Fixed a memory leak regression when generating session events (#11725). diff --git a/Crashlytics/Crashlytics/Components/FIRCLSHost.m b/Crashlytics/Crashlytics/Components/FIRCLSHost.m index f4b117c4eac..c1f41a0182b 100644 --- a/Crashlytics/Crashlytics/Components/FIRCLSHost.m +++ b/Crashlytics/Crashlytics/Components/FIRCLSHost.m @@ -15,7 +15,6 @@ #include "Crashlytics/Crashlytics/Components/FIRCLSHost.h" #include -#include #include #import "Crashlytics/Crashlytics/Components/FIRCLSApplication.h" @@ -180,16 +179,15 @@ bool FIRCLSHostRecord(FIRCLSFile* file) { } void FIRCLSHostWriteDiskUsage(FIRCLSFile* file) { - struct statfs tStats; - FIRCLSFileWriteSectionStart(file, "storage"); FIRCLSFileWriteHashStart(file); - if (statfs(_firclsContext.readonly->host.documentDirectoryPath, &tStats) == 0) { - FIRCLSFileWriteHashEntryUint64(file, "free", tStats.f_bavail * tStats.f_bsize); - FIRCLSFileWriteHashEntryUint64(file, "total", tStats.f_blocks * tStats.f_bsize); - } + // Due to Apple Privacy Manifests, Crashlytics is not collecting + // disk space using statfs. When we find a solution, we can update + // this to actually track disk space correctly. + FIRCLSFileWriteHashEntryUint64(file, "free", 0); + FIRCLSFileWriteHashEntryUint64(file, "total", 0); FIRCLSFileWriteHashEnd(file); diff --git a/Crashlytics/Crashlytics/Components/FIRCLSUserLogging.h b/Crashlytics/Crashlytics/Components/FIRCLSUserLogging.h index e0cadd483fb..0b2aa3922f8 100644 --- a/Crashlytics/Crashlytics/Components/FIRCLSUserLogging.h +++ b/Crashlytics/Crashlytics/Components/FIRCLSUserLogging.h @@ -81,7 +81,9 @@ void FIRCLSUserLoggingRecordUserKeysAndValues(NSDictionary* keysAndValues); void FIRCLSUserLoggingRecordInternalKeyValue(NSString* key, id value); void FIRCLSUserLoggingWriteInternalKeyValue(NSString* key, NSString* value); -void FIRCLSUserLoggingRecordError(NSError* error, NSDictionary* additionalUserInfo); +void FIRCLSUserLoggingRecordError(NSError* error, + NSDictionary* additionalUserInfo, + NSString* rolloutsInfoJSON); NSDictionary* FIRCLSUserLoggingGetCompactedKVEntries(FIRCLSUserLoggingKVStorage* storage, bool decodeHex); diff --git a/Crashlytics/Crashlytics/Components/FIRCLSUserLogging.m b/Crashlytics/Crashlytics/Components/FIRCLSUserLogging.m index 31b4deef1e9..4da93b43450 100644 --- a/Crashlytics/Crashlytics/Components/FIRCLSUserLogging.m +++ b/Crashlytics/Crashlytics/Components/FIRCLSUserLogging.m @@ -355,7 +355,8 @@ static void FIRCLSUserLoggingWriteError(FIRCLSFile *file, NSError *error, NSDictionary *additionalUserInfo, NSArray *addresses, - uint64_t timestamp) { + uint64_t timestamp, + NSString *rolloutsInfoJSON) { FIRCLSFileWriteSectionStart(file, "error"); FIRCLSFileWriteHashStart(file); FIRCLSFileWriteHashEntryHexEncodedString(file, "domain", [[error domain] UTF8String]); @@ -374,12 +375,20 @@ static void FIRCLSUserLoggingWriteError(FIRCLSFile *file, FIRCLSUserLoggingRecordErrorUserInfo(file, "info", [error userInfo]); FIRCLSUserLoggingRecordErrorUserInfo(file, "extra_info", additionalUserInfo); + // rollouts + if (rolloutsInfoJSON) { + FIRCLSFileWriteHashKey(file, "rollouts"); + FIRCLSFileWriteStringUnquoted(file, [rolloutsInfoJSON UTF8String]); + FIRCLSFileWriteHashEnd(file); + } + FIRCLSFileWriteHashEnd(file); FIRCLSFileWriteSectionEnd(file); } void FIRCLSUserLoggingRecordError(NSError *error, - NSDictionary *additionalUserInfo) { + NSDictionary *additionalUserInfo, + NSString *rolloutsInfoJSON) { if (!error) { return; } @@ -396,7 +405,8 @@ void FIRCLSUserLoggingRecordError(NSError *error, FIRCLSUserLoggingWriteAndCheckABFiles( &_firclsContext.readonly->logging.errorStorage, &_firclsContext.writable->logging.activeErrorLogPath, ^(FIRCLSFile *file) { - FIRCLSUserLoggingWriteError(file, error, additionalUserInfo, addresses, timestamp); + FIRCLSUserLoggingWriteError(file, error, additionalUserInfo, addresses, timestamp, + rolloutsInfoJSON); }); } diff --git a/Crashlytics/Crashlytics/Controllers/FIRCLSReportUploader.h b/Crashlytics/Crashlytics/Controllers/FIRCLSReportUploader.h index 6f134b55296..3723a50e464 100644 --- a/Crashlytics/Crashlytics/Controllers/FIRCLSReportUploader.h +++ b/Crashlytics/Crashlytics/Controllers/FIRCLSReportUploader.h @@ -28,6 +28,7 @@ @property(nonatomic, readonly) NSOperationQueue *operationQueue; @property(nonatomic, readonly) FIRCLSFileManager *fileManager; @property(nonatomic, copy) NSString *fiid; +@property(nonatomic, copy) NSString *authToken; - (void)prepareAndSubmitReport:(FIRCLSInternalReport *)report dataCollectionToken:(FIRCLSDataCollectionToken *)dataCollectionToken diff --git a/Crashlytics/Crashlytics/Controllers/FIRCLSReportUploader.m b/Crashlytics/Crashlytics/Controllers/FIRCLSReportUploader.m index caf1bae252d..0de639c6e86 100644 --- a/Crashlytics/Crashlytics/Controllers/FIRCLSReportUploader.m +++ b/Crashlytics/Crashlytics/Controllers/FIRCLSReportUploader.m @@ -95,8 +95,10 @@ - (void)prepareAndSubmitReport:(FIRCLSInternalReport *)report // urgent mode. Since urgent mode happens when the app is in a crash loop, // we can safely assume users aren't rotating their FIID, so this can be skipped. if (!urgent) { - [self.installIDModel regenerateInstallIDIfNeededWithBlock:^(NSString *_Nonnull newFIID) { + [self.installIDModel regenerateInstallIDIfNeededWithBlock:^( + NSString *_Nonnull newFIID, NSString *_Nonnull authToken) { self.fiid = [newFIID copy]; + self.authToken = [authToken copy]; }]; } else { FIRCLSWarningLog( @@ -186,7 +188,8 @@ - (void)uploadPackagedReportAtPath:(NSString *)path FIRCLSReportAdapter *adapter = [[FIRCLSReportAdapter alloc] initWithPath:path googleAppId:self.googleAppID installIDModel:self.installIDModel - fiid:self.fiid]; + fiid:self.fiid + authToken:self.authToken]; GDTCOREvent *event = [self.googleTransport eventForTransport]; event.dataObject = adapter; diff --git a/Crashlytics/Crashlytics/Controllers/FIRCLSRolloutsPersistenceManager.h b/Crashlytics/Crashlytics/Controllers/FIRCLSRolloutsPersistenceManager.h new file mode 100644 index 00000000000..bda6eabbf5e --- /dev/null +++ b/Crashlytics/Crashlytics/Controllers/FIRCLSRolloutsPersistenceManager.h @@ -0,0 +1,30 @@ +// 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. + +#if SWIFT_PACKAGE +@import FirebaseCrashlyticsSwift; +#else // Swift Package Manager +#import +#endif // CocoaPods + +@interface FIRCLSRolloutsPersistenceManager : NSObject + +- (instancetype _Nullable)initWithFileManager:(FIRCLSFileManager *_Nonnull)fileManager; +- (instancetype _Nonnull)init NS_UNAVAILABLE; ++ (instancetype _Nonnull)new NS_UNAVAILABLE; + +- (void)updateRolloutsStateToPersistenceWithRollouts:(NSData *_Nonnull)rollouts + reportID:(NSString *_Nonnull)reportID; +- (void)debugLogWithMessage:(NSString *_Nonnull)message; +@end diff --git a/Crashlytics/Crashlytics/Controllers/FIRCLSRolloutsPersistenceManager.m b/Crashlytics/Crashlytics/Controllers/FIRCLSRolloutsPersistenceManager.m new file mode 100644 index 00000000000..3e7867dab76 --- /dev/null +++ b/Crashlytics/Crashlytics/Controllers/FIRCLSRolloutsPersistenceManager.m @@ -0,0 +1,67 @@ +// 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. + +#import +#include "Crashlytics/Crashlytics/Components/FIRCLSGlobals.h" +#include "Crashlytics/Crashlytics/Components/FIRCLSUserLogging.h" +#import "Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h" +#import "Crashlytics/Crashlytics/Models/FIRCLSFileManager.h" +#import "Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h" + +#if SWIFT_PACKAGE +@import FirebaseCrashlyticsSwift; +#else // Swift Package Manager +#import +#endif // CocoaPods + +@interface FIRCLSRolloutsPersistenceManager : NSObject +@property(nonatomic, readonly) FIRCLSFileManager *fileManager; +@end + +@implementation FIRCLSRolloutsPersistenceManager +- (instancetype)initWithFileManager:(FIRCLSFileManager *)fileManager { + self = [super init]; + if (!self) { + return nil; + } + _fileManager = fileManager; + return self; +} + +- (void)updateRolloutsStateToPersistenceWithRollouts:(NSData *_Nonnull)rollouts + reportID:(NSString *_Nonnull)reportID { + NSString *rolloutsPath = [[[_fileManager activePath] stringByAppendingPathComponent:reportID] + stringByAppendingPathComponent:FIRCLSReportRolloutsFile]; + if (![_fileManager fileExistsAtPath:rolloutsPath]) { + if (![_fileManager createFileAtPath:rolloutsPath contents:nil attributes:nil]) { + FIRCLSDebugLog(@"Could not create rollouts.clsrecord file. Error was code: %d - message: %s", + errno, strerror(errno)); + } + } + + NSFileHandle *rolloutsFile = [NSFileHandle fileHandleForUpdatingAtPath:rolloutsPath]; + + dispatch_sync(FIRCLSGetLoggingQueue(), ^{ + [rolloutsFile seekToEndOfFile]; + [rolloutsFile writeData:rollouts]; + NSData *newLineData = [@"\n" dataUsingEncoding:NSUTF8StringEncoding]; + [rolloutsFile writeData:newLineData]; + }); +} + +- (void)debugLogWithMessage:(NSString *_Nonnull)message { + FIRCLSDebugLog(message); +} + +@end diff --git a/Crashlytics/Crashlytics/FIRCrashlytics.m b/Crashlytics/Crashlytics/FIRCrashlytics.m index 4d112cddad4..85502b2a9d9 100644 --- a/Crashlytics/Crashlytics/FIRCrashlytics.m +++ b/Crashlytics/Crashlytics/FIRCrashlytics.m @@ -31,6 +31,7 @@ #import "Crashlytics/Crashlytics/Helpers/FIRCLSDefines.h" #include "Crashlytics/Crashlytics/Helpers/FIRCLSProfiling.h" #include "Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h" +#import "Crashlytics/Crashlytics/Models/FIRCLSExecutionIdentifierModel.h" #import "Crashlytics/Crashlytics/Models/FIRCLSFileManager.h" #import "Crashlytics/Crashlytics/Models/FIRCLSSettings.h" #import "Crashlytics/Crashlytics/Settings/Models/FIRCLSApplicationIdentifierModel.h" @@ -47,6 +48,7 @@ #import "Crashlytics/Crashlytics/Controllers/FIRCLSNotificationManager.h" #import "Crashlytics/Crashlytics/Controllers/FIRCLSReportManager.h" #import "Crashlytics/Crashlytics/Controllers/FIRCLSReportUploader.h" +#import "Crashlytics/Crashlytics/Controllers/FIRCLSRolloutsPersistenceManager.h" #import "Crashlytics/Crashlytics/Private/FIRCLSExistingReportManager_Private.h" #import "Crashlytics/Crashlytics/Private/FIRCLSOnDemandModel_Private.h" #import "Crashlytics/Crashlytics/Private/FIRExceptionModel_Private.h" @@ -58,6 +60,12 @@ #import @import FirebaseSessions; +@import FirebaseRemoteConfigInterop; +#if SWIFT_PACKAGE +@import FirebaseCrashlyticsSwift; +#else // Swift Package Manager +#import +#endif // CocoaPods #if TARGET_OS_IPHONE #import @@ -76,7 +84,10 @@ @protocol FIRCrashlyticsInstanceProvider @end -@interface FIRCrashlytics () +@interface FIRCrashlytics () @property(nonatomic) BOOL didPreviouslyCrash; @property(nonatomic, copy) NSString *googleAppID; @@ -91,6 +102,8 @@ @interface FIRCrashlytics () )analytics - sessions:(id)sessions { + sessions:(id)sessions + remoteConfig:(id)remoteConfig { self = [super init]; if (self) { @@ -189,8 +203,19 @@ - (instancetype)initWithApp:(FIRApp *)app }] catch:^void(NSError *error) { FIRCLSErrorLog(@"Crash reporting failed to initialize with error: %@", error); }]; - } + // RemoteConfig subscription should be made after session report directory created. + if (remoteConfig) { + FIRCLSDebugLog(@"Registering RemoteConfig SDK subscription for rollouts data"); + + FIRCLSRolloutsPersistenceManager *persistenceManager = + [[FIRCLSRolloutsPersistenceManager alloc] initWithFileManager:_fileManager]; + _remoteConfigManager = + [[FIRCLSRemoteConfigManager alloc] initWithRemoteConfig:remoteConfig + persistenceDelegate:persistenceManager]; + [remoteConfig registerRolloutsStateSubscriber:self for:FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform]; + } + } return self; } @@ -215,6 +240,7 @@ + (void)load { id analytics = FIR_COMPONENT(FIRAnalyticsInterop, container); id sessions = FIR_COMPONENT(FIRSessionsProvider, container); + id remoteConfig = FIR_COMPONENT(FIRRemoteConfigInterop, container); FIRInstallations *installations = [FIRInstallations installationsWithApp:container.app]; @@ -224,7 +250,8 @@ + (void)load { appInfo:NSBundle.mainBundle.infoDictionary installations:installations analytics:analytics - sessions:sessions]; + sessions:sessions + remoteConfig:remoteConfig]; }; FIRComponent *component = @@ -377,11 +404,13 @@ - (void)recordError:(NSError *)error { } - (void)recordError:(NSError *)error userInfo:(NSDictionary *)userInfo { - FIRCLSUserLoggingRecordError(error, userInfo); + NSString *rolloutsInfoJSON = [_remoteConfigManager getRolloutAssignmentsEncodedJsonString]; + FIRCLSUserLoggingRecordError(error, userInfo, rolloutsInfoJSON); } - (void)recordExceptionModel:(FIRExceptionModel *)exceptionModel { - FIRCLSExceptionRecordModel(exceptionModel); + NSString *rolloutsInfoJSON = [_remoteConfigManager getRolloutAssignmentsEncodedJsonString]; + FIRCLSExceptionRecordModel(exceptionModel, rolloutsInfoJSON); } - (void)recordOnDemandExceptionModel:(FIRExceptionModel *)exceptionModel { @@ -407,4 +436,14 @@ - (FIRSessionsSubscriberName)sessionsSubscriberName { return FIRSessionsSubscriberNameCrashlytics; } +#pragma mark - FIRRolloutsStateSubscriber +- (void)rolloutsStateDidChange:(FIRRolloutsState *_Nonnull)rolloutsState { + if (!_remoteConfigManager) { + FIRCLSDebugLog(@"rolloutsStateDidChange gets called without init the rc manager."); + return; + } + NSString *currentReportID = _managerData.executionIDModel.executionID; + [_remoteConfigManager updateRolloutsStateWithRolloutsState:rolloutsState + reportID:currentReportID]; +} @end diff --git a/Crashlytics/Crashlytics/Handlers/FIRCLSException.h b/Crashlytics/Crashlytics/Handlers/FIRCLSException.h index ae53b916f8b..65aae9bfd32 100644 --- a/Crashlytics/Crashlytics/Handlers/FIRCLSException.h +++ b/Crashlytics/Crashlytics/Handlers/FIRCLSException.h @@ -60,7 +60,7 @@ void FIRCLSExceptionRaiseTestObjCException(void) __attribute((noreturn)); void FIRCLSExceptionRaiseTestCppException(void) __attribute((noreturn)); #ifdef __OBJC__ -void FIRCLSExceptionRecordModel(FIRExceptionModel* exceptionModel); +void FIRCLSExceptionRecordModel(FIRExceptionModel* exceptionModel, NSString* rolloutsInfoJSON); NSString* FIRCLSExceptionRecordOnDemandModel(FIRExceptionModel* exceptionModel, int previousRecordedOnDemandExceptions, int previousDroppedOnDemandExceptions); @@ -68,7 +68,8 @@ void FIRCLSExceptionRecordNSException(NSException* exception); void FIRCLSExceptionRecord(FIRCLSExceptionType type, const char* name, const char* reason, - NSArray* frames); + NSArray* frames, + NSString* rolloutsInfoJSON); NSString* FIRCLSExceptionRecordOnDemand(FIRCLSExceptionType type, const char* name, const char* reason, diff --git a/Crashlytics/Crashlytics/Handlers/FIRCLSException.mm b/Crashlytics/Crashlytics/Handlers/FIRCLSException.mm index 798a4548ded..b92cd9848dd 100644 --- a/Crashlytics/Crashlytics/Handlers/FIRCLSException.mm +++ b/Crashlytics/Crashlytics/Handlers/FIRCLSException.mm @@ -82,11 +82,11 @@ void FIRCLSExceptionInitialize(FIRCLSExceptionReadOnlyContext *roContext, rwContext->customExceptionCount = 0; } -void FIRCLSExceptionRecordModel(FIRExceptionModel *exceptionModel) { +void FIRCLSExceptionRecordModel(FIRExceptionModel *exceptionModel, NSString *rolloutsInfoJSON) { const char *name = [[exceptionModel.name copy] UTF8String]; const char *reason = [[exceptionModel.reason copy] UTF8String] ?: ""; - - FIRCLSExceptionRecord(FIRCLSExceptionTypeCustom, name, reason, [exceptionModel.stackTrace copy]); + FIRCLSExceptionRecord(FIRCLSExceptionTypeCustom, name, reason, [exceptionModel.stackTrace copy], + rolloutsInfoJSON); } NSString *FIRCLSExceptionRecordOnDemandModel(FIRExceptionModel *exceptionModel, @@ -122,7 +122,7 @@ void FIRCLSExceptionRecordNSException(NSException *exception) { } FIRCLSExceptionRecord(FIRCLSExceptionTypeObjectiveC, [name UTF8String], [reason UTF8String], - frames); + frames, nil); } static void FIRCLSExceptionRecordFrame(FIRCLSFile *file, FIRStackFrame *frame) { @@ -175,7 +175,8 @@ void FIRCLSExceptionWrite(FIRCLSFile *file, FIRCLSExceptionType type, const char *name, const char *reason, - NSArray *frames) { + NSArray *frames, + NSString *rolloutsInfoJSON) { FIRCLSFileWriteSectionStart(file, "exception"); FIRCLSFileWriteHashStart(file); @@ -196,6 +197,12 @@ void FIRCLSExceptionWrite(FIRCLSFile *file, FIRCLSFileWriteArrayEnd(file); } + if (rolloutsInfoJSON) { + FIRCLSFileWriteHashKey(file, "rollouts"); + FIRCLSFileWriteStringUnquoted(file, [rolloutsInfoJSON UTF8String]); + FIRCLSFileWriteHashEnd(file); + } + FIRCLSFileWriteHashEnd(file); FIRCLSFileWriteSectionEnd(file); @@ -204,7 +211,8 @@ void FIRCLSExceptionWrite(FIRCLSFile *file, void FIRCLSExceptionRecord(FIRCLSExceptionType type, const char *name, const char *reason, - NSArray *frames) { + NSArray *frames, + NSString *rolloutsInfoJSON) { if (!FIRCLSContextIsInitialized()) { return; } @@ -224,7 +232,7 @@ void FIRCLSExceptionRecord(FIRCLSExceptionType type, return; } - FIRCLSExceptionWrite(&file, type, name, reason, frames); + FIRCLSExceptionWrite(&file, type, name, reason, frames, nil); // We only want to do this work if we have the expectation that we'll actually crash FIRCLSHandler(&file, mach_thread_self(), NULL); @@ -235,7 +243,7 @@ void FIRCLSExceptionRecord(FIRCLSExceptionType type, FIRCLSUserLoggingWriteAndCheckABFiles( &_firclsContext.readonly->logging.customExceptionStorage, &_firclsContext.writable->logging.activeCustomExceptionPath, ^(FIRCLSFile *file) { - FIRCLSExceptionWrite(file, type, name, reason, frames); + FIRCLSExceptionWrite(file, type, name, reason, frames, rolloutsInfoJSON); }); } @@ -271,6 +279,7 @@ void FIRCLSExceptionRecord(FIRCLSExceptionType type, // Create new report and copy into it the current state of custom keys and log and the sdk.log, // binary_images.clsrecord, and metadata.clsrecord files. + // Also copy rollouts.clsrecord if applicable. NSError *error = nil; BOOL copied = [fileManager.underlyingFileManager copyItemAtPath:currentReportPath toPath:newReportPath @@ -343,7 +352,7 @@ void FIRCLSExceptionRecord(FIRCLSExceptionType type, FIRCLSSDKLog("Unable to open log file for on demand custom exception\n"); return nil; } - FIRCLSExceptionWrite(&file, type, name, reason, frames); + FIRCLSExceptionWrite(&file, type, name, reason, frames, nil); FIRCLSHandler(&file, mach_thread_self(), NULL); FIRCLSFileClose(&file); @@ -397,19 +406,21 @@ static void FIRCLSCatchAndRecordActiveException(std::type_info *typeInfo) { #endif } } catch (const char *exc) { - FIRCLSExceptionRecord(FIRCLSExceptionTypeCpp, "const char *", exc, nil); + FIRCLSExceptionRecord(FIRCLSExceptionTypeCpp, "const char *", exc, nil, nil); } catch (const std::string &exc) { - FIRCLSExceptionRecord(FIRCLSExceptionTypeCpp, "std::string", exc.c_str(), nil); + FIRCLSExceptionRecord(FIRCLSExceptionTypeCpp, "std::string", exc.c_str(), nil, nil); } catch (const std::exception &exc) { - FIRCLSExceptionRecord(FIRCLSExceptionTypeCpp, FIRCLSExceptionDemangle(name), exc.what(), nil); + FIRCLSExceptionRecord(FIRCLSExceptionTypeCpp, FIRCLSExceptionDemangle(name), exc.what(), nil, + nil); } catch (const std::exception *exc) { - FIRCLSExceptionRecord(FIRCLSExceptionTypeCpp, FIRCLSExceptionDemangle(name), exc->what(), nil); + FIRCLSExceptionRecord(FIRCLSExceptionTypeCpp, FIRCLSExceptionDemangle(name), exc->what(), nil, + nil); } catch (const std::bad_alloc &exc) { // it is especially important to avoid demangling in this case, because the expetation at this // point is that all allocations could fail - FIRCLSExceptionRecord(FIRCLSExceptionTypeCpp, "std::bad_alloc", exc.what(), nil); + FIRCLSExceptionRecord(FIRCLSExceptionTypeCpp, "std::bad_alloc", exc.what(), nil, nil); } catch (...) { - FIRCLSExceptionRecord(FIRCLSExceptionTypeCpp, FIRCLSExceptionDemangle(name), "", nil); + FIRCLSExceptionRecord(FIRCLSExceptionTypeCpp, FIRCLSExceptionDemangle(name), "", nil, nil); } } diff --git a/Crashlytics/Crashlytics/Helpers/FIRCLSFile.m b/Crashlytics/Crashlytics/Helpers/FIRCLSFile.m index 05e7561c9a8..27caa6dcdf7 100644 --- a/Crashlytics/Crashlytics/Helpers/FIRCLSFile.m +++ b/Crashlytics/Crashlytics/Helpers/FIRCLSFile.m @@ -33,7 +33,8 @@ static const size_t FIRCLSStringBufferLength = 16; const size_t FIRCLSWriteBufferLength = 1000; -static bool FIRCLSFileInit(FIRCLSFile* file, int fdm, bool appendMode, bool bufferWrites); +static bool FIRCLSFileInit( + FIRCLSFile* file, const char* path, int fdm, bool appendMode, bool bufferWrites); static void FIRCLSFileWriteToFileDescriptorOrBuffer(FIRCLSFile* file, const char* string, @@ -55,7 +56,8 @@ static void FIRCLSFileWriteToFileDescriptorOrBuffer(FIRCLSFile* file, #define CLS_FILE_DEBUG_LOGGING 0 #pragma mark - File Structure -static bool FIRCLSFileInit(FIRCLSFile* file, int fd, bool appendMode, bool bufferWrites) { +static bool FIRCLSFileInit( + FIRCLSFile* file, const char* path, int fd, bool appendMode, bool bufferWrites) { if (!file) { FIRCLSSDKLog("Error: file is null\n"); return false; @@ -83,9 +85,16 @@ static bool FIRCLSFileInit(FIRCLSFile* file, int fd, bool appendMode, bool buffe file->writtenLength = 0; if (appendMode) { - struct stat fileStats; - fstat(fd, &fileStats); - off_t currentFileSize = fileStats.st_size; + NSError* attributesError; + NSString* objCPath = [NSString stringWithCString:path encoding:NSUTF8StringEncoding]; + NSDictionary* fileAttributes = + [[NSFileManager defaultManager] attributesOfItemAtPath:objCPath error:&attributesError]; + if (attributesError != nil) { + FIRCLSErrorLog(@"Failed to read filesize from %@ with error %@", objCPath, attributesError); + return false; + } + NSNumber* fileSizeNumber = [fileAttributes objectForKey:NSFileSize]; + long long currentFileSize = [fileSizeNumber longLongValue]; if (currentFileSize > 0) { file->writtenLength += currentFileSize; } @@ -133,7 +142,7 @@ bool FIRCLSFileInitWithPathMode(FIRCLSFile* file, } } - return FIRCLSFileInit(file, fd, appendMode, bufferWrites); + return FIRCLSFileInit(file, path, fd, appendMode, bufferWrites); } bool FIRCLSFileClose(FIRCLSFile* file) { diff --git a/Crashlytics/Crashlytics/Models/FIRCLSInstallIdentifierModel.h b/Crashlytics/Crashlytics/Models/FIRCLSInstallIdentifierModel.h index fd33a4b6959..4e0f4832162 100644 --- a/Crashlytics/Crashlytics/Models/FIRCLSInstallIdentifierModel.h +++ b/Crashlytics/Crashlytics/Models/FIRCLSInstallIdentifierModel.h @@ -44,7 +44,7 @@ NS_ASSUME_NONNULL_BEGIN * - Concern 2: Whatever the FIID is, we should send it with the Crash report so we're in sync with * Sessions and other Firebase SDKs */ -- (BOOL)regenerateInstallIDIfNeededWithBlock:(void (^)(NSString *fiid))block; +- (BOOL)regenerateInstallIDIfNeededWithBlock:(void (^)(NSString *fiid, NSString *authToken))block; @end diff --git a/Crashlytics/Crashlytics/Models/FIRCLSInstallIdentifierModel.m b/Crashlytics/Crashlytics/Models/FIRCLSInstallIdentifierModel.m index 9cc936bb50e..fbdcc21f80b 100644 --- a/Crashlytics/Crashlytics/Models/FIRCLSInstallIdentifierModel.m +++ b/Crashlytics/Crashlytics/Models/FIRCLSInstallIdentifierModel.m @@ -98,33 +98,45 @@ - (NSString *)generateInstallationUUID { #pragma mark Privacy Shield -- (BOOL)regenerateInstallIDIfNeededWithBlock:(void (^)(NSString *fiid))block { +- (BOOL)regenerateInstallIDIfNeededWithBlock:(void (^)(NSString *fiid, NSString *authToken))block { BOOL __block didRotate = false; + NSString __block *authTokenComplete = @""; + NSString __block *currentIIDComplete = @""; - dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + // Installations Completions run async, so wait a reasonable amount of time for it to finish. + dispatch_group_t workingGroup = dispatch_group_create(); - // This runs Completion async, so wait a reasonable amount of time for it to finish. + dispatch_group_enter(workingGroup); [self.installations - installationIDWithCompletion:^(NSString *_Nullable currentIID, NSError *_Nullable error) { - // Provide the IID to the callback. For this case we don't care - // if the FIID is null because it's the best we can do - we just want - // to send up the same FIID that is sent by other SDKs (eg. the Sessions SDK). - block(currentIID); + authTokenWithCompletion:^(FIRInstallationsAuthTokenResult *_Nullable tokenResult, + NSError *_Nullable error) { + authTokenComplete = tokenResult.authToken; + dispatch_group_leave(workingGroup); + }]; + dispatch_group_enter(workingGroup); + [self.installations + installationIDWithCompletion:^(NSString *_Nullable currentIID, NSError *_Nullable error) { + currentIIDComplete = currentIID; didRotate = [self rotateCrashlyticsInstallUUIDWithIID:currentIID error:error]; if (didRotate) { FIRCLSInfoLog(@"Rotated Crashlytics Install UUID because Firebase Install ID changed"); } - dispatch_semaphore_signal(semaphore); + dispatch_group_leave(workingGroup); }]; - intptr_t result = dispatch_semaphore_wait( - semaphore, dispatch_time(DISPATCH_TIME_NOW, FIRCLSInstallationsWaitTime)); + intptr_t result = dispatch_group_wait( + workingGroup, dispatch_time(DISPATCH_TIME_NOW, FIRCLSInstallationsWaitTime)); + if (result != 0) { FIRCLSErrorLog(@"Crashlytics timed out while checking for Firebase Installation ID"); } + // Provide the IID to the callback. For this case we don't care + // if the FIID is null because it's the best we can do - we just want + // to send up the same FIID that is sent by other SDKs (eg. the Sessions SDK). + block(currentIIDComplete, authTokenComplete); return didRotate; } diff --git a/Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h b/Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h index 6303962c667..624c1990ae7 100644 --- a/Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h +++ b/Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h @@ -36,6 +36,7 @@ extern NSString *const FIRCLSReportInternalIncrementalKVFile; extern NSString *const FIRCLSReportInternalCompactedKVFile; extern NSString *const FIRCLSReportUserIncrementalKVFile; extern NSString *const FIRCLSReportUserCompactedKVFile; +extern NSString *const FIRCLSReportRolloutsFile; @class FIRCLSFileManager; diff --git a/Crashlytics/Crashlytics/Models/FIRCLSInternalReport.m b/Crashlytics/Crashlytics/Models/FIRCLSInternalReport.m index 61daf92f3e8..35160d1cbc1 100644 --- a/Crashlytics/Crashlytics/Models/FIRCLSInternalReport.m +++ b/Crashlytics/Crashlytics/Models/FIRCLSInternalReport.m @@ -41,6 +41,7 @@ NSString *const FIRCLSReportInternalCompactedKVFile = @"internal_compacted_kv.clsrecord"; NSString *const FIRCLSReportUserIncrementalKVFile = @"user_incremental_kv.clsrecord"; NSString *const FIRCLSReportUserCompactedKVFile = @"user_compacted_kv.clsrecord"; +NSString *const FIRCLSReportRolloutsFile = @"rollouts.clsrecord"; @interface FIRCLSInternalReport () { NSString *_identifier; diff --git a/Crashlytics/Crashlytics/Models/Record/FIRCLSReportAdapter.h b/Crashlytics/Crashlytics/Models/Record/FIRCLSReportAdapter.h index 24afcdeff91..4db047cf58f 100644 --- a/Crashlytics/Crashlytics/Models/Record/FIRCLSReportAdapter.h +++ b/Crashlytics/Crashlytics/Models/Record/FIRCLSReportAdapter.h @@ -35,5 +35,6 @@ - (instancetype)initWithPath:(NSString *)folderPath googleAppId:(NSString *)googleAppID installIDModel:(FIRCLSInstallIdentifierModel *)installIDModel - fiid:(NSString *)fiid; + fiid:(NSString *)fiid + authToken:(NSString *)authToken; @end diff --git a/Crashlytics/Crashlytics/Models/Record/FIRCLSReportAdapter.m b/Crashlytics/Crashlytics/Models/Record/FIRCLSReportAdapter.m index 3d52bbeb844..2cbcdb9ef23 100644 --- a/Crashlytics/Crashlytics/Models/Record/FIRCLSReportAdapter.m +++ b/Crashlytics/Crashlytics/Models/Record/FIRCLSReportAdapter.m @@ -30,6 +30,7 @@ @interface FIRCLSReportAdapter () @property(nonatomic, strong) FIRCLSInstallIdentifierModel *installIDModel; @property(nonatomic, copy) NSString *fiid; +@property(nonatomic, copy) NSString *authToken; @end @@ -38,13 +39,15 @@ @implementation FIRCLSReportAdapter - (instancetype)initWithPath:(NSString *)folderPath googleAppId:(NSString *)googleAppID installIDModel:(FIRCLSInstallIdentifierModel *)installIDModel - fiid:(NSString *)fiid { + fiid:(NSString *)fiid + authToken:(NSString *)authToken { self = [super init]; if (self) { _folderPath = folderPath; _googleAppID = googleAppID; _installIDModel = installIDModel; _fiid = [fiid copy]; + _authToken = [authToken copy]; [self loadMetaDataFile]; @@ -156,6 +159,7 @@ - (google_crashlytics_Report)protoReport { report.installation_uuid = FIRCLSEncodeString(self.installIDModel.installID); report.firebase_installation_id = FIRCLSEncodeString(self.fiid); report.app_quality_session_id = FIRCLSEncodeString(self.identity.app_quality_session_id); + report.firebase_authentication_token = FIRCLSEncodeString(self.authToken); report.build_version = FIRCLSEncodeString(self.application.build_version); report.display_version = FIRCLSEncodeString(self.application.display_version); report.apple_payload = [self protoFilesPayload]; diff --git a/Crashlytics/Crashlytics/Rollouts/CrashlyticsRemoteConfigManager.swift b/Crashlytics/Crashlytics/Rollouts/CrashlyticsRemoteConfigManager.swift new file mode 100644 index 00000000000..d6d5cb16b82 --- /dev/null +++ b/Crashlytics/Crashlytics/Rollouts/CrashlyticsRemoteConfigManager.swift @@ -0,0 +1,141 @@ +// Copyright 2023 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 FirebaseRemoteConfigInterop +import Foundation + +@objc(FIRCLSPersistenceLog) +public protocol CrashlyticsPersistenceLog { + func updateRolloutsStateToPersistence(rollouts: Data, reportID: String) + func debugLog(message: String) +} + +@objc(FIRCLSRemoteConfigManager) +public class CrashlyticsRemoteConfigManager: NSObject { + public static let maxRolloutAssignments = 128 + public static let maxParameterValueLength = 256 + + private let lock = NSLock() + private var _rolloutAssignment: [RolloutAssignment] = [] + + var remoteConfig: RemoteConfigInterop + var persistenceDelegate: CrashlyticsPersistenceLog + + @objc public var rolloutAssignment: [RolloutAssignment] { + lock.lock() + defer { lock.unlock() } + let copy = _rolloutAssignment + return copy + } + + @objc public init(remoteConfig: RemoteConfigInterop, + persistenceDelegate: CrashlyticsPersistenceLog) { + self.remoteConfig = remoteConfig + self.persistenceDelegate = persistenceDelegate + } + + @objc public func updateRolloutsState(rolloutsState: RolloutsState, reportID: String) { + lock.lock() + _rolloutAssignment = normalizeRolloutAssignment(assignments: Array(rolloutsState.assignments)) + lock.unlock() + + // Writring to persistence + if let rolloutsData = + getRolloutsStateEncodedJsonData() { + persistenceDelegate.updateRolloutsStateToPersistence( + rollouts: rolloutsData, + reportID: reportID + ) + } + } + + /// Return string format: [{RolloutAssignment1}, {RolloutAssignment2}, {RolloutAssignment3}...] + /// This will get inserted into each clsrcord for non-fatal events. + /// Return a string type because later `FIRCLSFileWriteStringUnquoted` takes string as input + @objc public func getRolloutAssignmentsEncodedJsonString() -> String? { + let encodeData = getRolloutAssignmentsEncodedJsonData() + if let data = encodeData { + return String(data: data, encoding: .utf8) + } + + let debugInfo = encodeData?.debugDescription ?? "nil" + persistenceDelegate.debugLog(message: String( + format: "Failed to serialize rollouts: %@", + arguments: [debugInfo] + )) + + return nil + } +} + +private extension CrashlyticsRemoteConfigManager { + func normalizeRolloutAssignment(assignments: [RolloutAssignment]) -> [RolloutAssignment] { + var validatedAssignments = assignments + if assignments.count > CrashlyticsRemoteConfigManager.maxRolloutAssignments { + persistenceDelegate + .debugLog( + message: "Rollouts excess the maximum number of assignments can pass to Crashlytics" + ) + validatedAssignments = + Array(assignments[.. CrashlyticsRemoteConfigManager.maxParameterValueLength { + debugPrint( + "Rollouts excess the maximum length of parameter value can pass to Crashlytics", + assignment.parameterValue + ) + let upperBound = String.Index( + utf16Offset: CrashlyticsRemoteConfigManager.maxParameterValueLength, + in: assignment.parameterValue + ) + let slicedParameterValue = assignment.parameterValue[.. Data? { + let contentEncodedRolloutAssignments = rolloutAssignment.map { assignment in + EncodedRolloutAssignment(assignment: assignment) + } + + let encoder = JSONEncoder() + encoder.keyEncodingStrategy = .convertToSnakeCase + encoder.outputFormatting = .sortedKeys + let encodeData = try? encoder.encode(contentEncodedRolloutAssignments) + return encodeData + } + + /// Return string format: {"rollouts": [{RolloutAssignment1}, {RolloutAssignment2}, + /// {RolloutAssignment3}...]} + /// This will get stored in the separate rollouts.clsrecord + /// Return a data type because later `[NSFileHandler writeData:]` takes data as input + func getRolloutsStateEncodedJsonData() -> Data? { + let contentEncodedRolloutAssignments = rolloutAssignment.map { assignment in + EncodedRolloutAssignment(assignment: assignment) + } + + let state = EncodedRolloutsState(assignments: contentEncodedRolloutAssignments) + let encoder = JSONEncoder() + encoder.keyEncodingStrategy = .convertToSnakeCase + let encodeData = try? encoder.encode(state) + return encodeData + } +} diff --git a/Crashlytics/Crashlytics/Rollouts/EncodedRolloutAssignment.swift b/Crashlytics/Crashlytics/Rollouts/EncodedRolloutAssignment.swift new file mode 100644 index 00000000000..725b63050ec --- /dev/null +++ b/Crashlytics/Crashlytics/Rollouts/EncodedRolloutAssignment.swift @@ -0,0 +1,44 @@ +// 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. + +import FirebaseRemoteConfigInterop +import Foundation + +@objc(FIRCLSEncodedRolloutsState) +class EncodedRolloutsState: NSObject, Codable { + @objc public private(set) var rollouts: [EncodedRolloutAssignment] + + @objc public init(assignments: [EncodedRolloutAssignment]) { + rollouts = assignments + super.init() + } +} + +@objc(FIRCLSEncodedRolloutAssignment) +class EncodedRolloutAssignment: NSObject, Codable { + @objc public private(set) var rolloutId: String + @objc public private(set) var variantId: String + @objc public private(set) var templateVersion: Int64 + @objc public private(set) var parameterKey: String + @objc public private(set) var parameterValue: String + + public init(assignment: RolloutAssignment) { + rolloutId = FileUtility.stringToHexConverter(for: assignment.rolloutId) + variantId = FileUtility.stringToHexConverter(for: assignment.variantId) + templateVersion = assignment.templateVersion + parameterKey = FileUtility.stringToHexConverter(for: assignment.parameterKey) + parameterValue = FileUtility.stringToHexConverter(for: assignment.parameterValue) + super.init() + } +} diff --git a/Crashlytics/Crashlytics/Rollouts/StringToHexConverter.swift b/Crashlytics/Crashlytics/Rollouts/StringToHexConverter.swift new file mode 100644 index 00000000000..9d4365db927 --- /dev/null +++ b/Crashlytics/Crashlytics/Rollouts/StringToHexConverter.swift @@ -0,0 +1,38 @@ +// 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. + +import Foundation + +// This is a swift rewrite for the logic in FIRCLSFile for the function FIRCLSFileHexEncodeString() +@objc(FIRCLSwiftFileUtility) +public class FileUtility: NSObject { + @objc public static func stringToHexConverter(for string: String) -> String { + let hexMap = "0123456789abcdef" + + var processedString = "" + let utf8Array = string.utf8.map { UInt8($0) } + for c in utf8Array { + let index1 = String.Index( + utf16Offset: Int(c >> 4), + in: hexMap + ) + let index2 = String.Index( + utf16Offset: Int(c & 0x0F), + in: hexMap + ) + processedString = processedString + String(hexMap[index1]) + String(hexMap[index2]) + } + return processedString + } +} diff --git a/Crashlytics/Protogen/nanopb/crashlytics.nanopb.c b/Crashlytics/Protogen/nanopb/crashlytics.nanopb.c index cd9c87d50bd..cb88acc139f 100644 --- a/Crashlytics/Protogen/nanopb/crashlytics.nanopb.c +++ b/Crashlytics/Protogen/nanopb/crashlytics.nanopb.c @@ -26,7 +26,7 @@ -const pb_field_t google_crashlytics_Report_fields[10] = { +const pb_field_t google_crashlytics_Report_fields[11] = { PB_FIELD( 1, BYTES , SINGULAR, POINTER , FIRST, google_crashlytics_Report, sdk_version, sdk_version, 0), PB_FIELD( 3, BYTES , SINGULAR, POINTER , OTHER, google_crashlytics_Report, gmp_app_id, sdk_version, 0), PB_FIELD( 4, UENUM , SINGULAR, STATIC , OTHER, google_crashlytics_Report, platform, gmp_app_id, 0), @@ -36,6 +36,7 @@ const pb_field_t google_crashlytics_Report_fields[10] = { PB_FIELD( 10, MESSAGE , SINGULAR, STATIC , OTHER, google_crashlytics_Report, apple_payload, display_version, &google_crashlytics_FilesPayload_fields), PB_FIELD( 16, BYTES , SINGULAR, POINTER , OTHER, google_crashlytics_Report, firebase_installation_id, apple_payload, 0), PB_FIELD( 17, BYTES , SINGULAR, POINTER , OTHER, google_crashlytics_Report, app_quality_session_id, firebase_installation_id, 0), + PB_FIELD( 19, BYTES , SINGULAR, POINTER , OTHER, google_crashlytics_Report, firebase_authentication_token, app_quality_session_id, 0), PB_LAST_FIELD }; diff --git a/Crashlytics/Protogen/nanopb/crashlytics.nanopb.h b/Crashlytics/Protogen/nanopb/crashlytics.nanopb.h index ed9838ad47d..4ad4f89a120 100644 --- a/Crashlytics/Protogen/nanopb/crashlytics.nanopb.h +++ b/Crashlytics/Protogen/nanopb/crashlytics.nanopb.h @@ -61,6 +61,7 @@ typedef struct _google_crashlytics_Report { google_crashlytics_FilesPayload apple_payload; pb_bytes_array_t *firebase_installation_id; pb_bytes_array_t *app_quality_session_id; + pb_bytes_array_t *firebase_authentication_token; /* @@protoc_insertion_point(struct:google_crashlytics_Report) */ } google_crashlytics_Report; @@ -84,12 +85,13 @@ typedef struct _google_crashlytics_Report { #define google_crashlytics_Report_installation_uuid_tag 5 #define google_crashlytics_Report_firebase_installation_id_tag 16 #define google_crashlytics_Report_app_quality_session_id_tag 17 +#define google_crashlytics_Report_firebase_authentication_token 19 #define google_crashlytics_Report_build_version_tag 6 #define google_crashlytics_Report_display_version_tag 7 #define google_crashlytics_Report_apple_payload_tag 10 /* Struct field encoding specification for nanopb */ -extern const pb_field_t google_crashlytics_Report_fields[10]; +extern const pb_field_t google_crashlytics_Report_fields[11]; extern const pb_field_t google_crashlytics_FilesPayload_fields[2]; extern const pb_field_t google_crashlytics_FilesPayload_File_fields[3]; diff --git a/Crashlytics/Resources/PrivacyInfo.xcprivacy b/Crashlytics/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 00000000000..730bb05c863 --- /dev/null +++ b/Crashlytics/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,66 @@ + + + + + NSPrivacyTracking + + NSPrivacyTrackingDomains + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeCrashData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeOtherDiagnosticData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategorySystemBootTime + NSPrivacyAccessedAPITypeReasons + + 35F9.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + + + diff --git a/Crashlytics/Shared/FIRCLSMachO/FIRCLSMachO.m b/Crashlytics/Shared/FIRCLSMachO/FIRCLSMachO.m index e2f17eb5547..477541c784d 100644 --- a/Crashlytics/Shared/FIRCLSMachO/FIRCLSMachO.m +++ b/Crashlytics/Shared/FIRCLSMachO/FIRCLSMachO.m @@ -42,8 +42,6 @@ static void FIRCLSMachOHeaderValues(FIRCLSMachOSliceRef slice, static bool FIRCLSMachOSliceIsValid(FIRCLSMachOSliceRef slice); bool FIRCLSMachOFileInitWithPath(FIRCLSMachOFileRef file, const char* path) { - struct stat statBuffer; - if (!file || !path) { return false; } @@ -58,16 +56,23 @@ bool FIRCLSMachOFileInitWithPath(FIRCLSMachOFileRef file, const char* path) { return false; } - if (fstat(file->fd, &statBuffer) == -1) { + NSError* attributesError; + NSString* objCPath = [NSString stringWithCString:path encoding:NSUTF8StringEncoding]; + NSDictionary* fileAttributes = + [[NSFileManager defaultManager] attributesOfItemAtPath:objCPath error:&attributesError]; + if (attributesError != nil) { close(file->fd); return false; } + NSNumber* fileSizeNumber = [fileAttributes objectForKey:NSFileSize]; + long long currentFileSize = [fileSizeNumber longLongValue]; + NSFileAttributeType fileType = [fileAttributes objectForKey:NSFileType]; // We need some minimum size for this to even be a possible mach-o file. I believe // its probably quite a bit bigger than this, but this at least covers something. // We also need it to be a regular file. - file->mappedSize = (size_t)statBuffer.st_size; - if (statBuffer.st_size < 16 || !(statBuffer.st_mode & S_IFREG)) { + file->mappedSize = (size_t)currentFileSize; + if (currentFileSize < 16 || ![fileType isEqualToString:NSFileTypeRegular]) { close(file->fd); return false; } diff --git a/Crashlytics/UnitTests/FIRCLSContextManagerTests.m b/Crashlytics/UnitTests/FIRCLSContextManagerTests.m index 249b85f4991..887eb442bf0 100644 --- a/Crashlytics/UnitTests/FIRCLSContextManagerTests.m +++ b/Crashlytics/UnitTests/FIRCLSContextManagerTests.m @@ -75,7 +75,8 @@ - (void)test_notSettingSessionID_protoHasNilSessionID { FIRCLSReportAdapter *adapter = [[FIRCLSReportAdapter alloc] initWithPath:self.report.path googleAppId:@"TestGoogleAppID" installIDModel:self.installIDModel - fiid:@"TestFIID"]; + fiid:@"TestFIID" + authToken:@"TestAuthToken"]; XCTAssertEqualObjects(adapter.identity.app_quality_session_id, @""); } @@ -92,7 +93,8 @@ - (void)test_settingSessionIDMultipleTimes_protoHasLastSessionID { FIRCLSReportAdapter *adapter = [[FIRCLSReportAdapter alloc] initWithPath:self.report.path googleAppId:@"TestGoogleAppID" installIDModel:self.installIDModel - fiid:@"TestFIID"]; + fiid:@"TestFIID" + authToken:@"TestAuthToken"]; NSLog(@"reportPath: %@", self.report.path); XCTAssertEqualObjects(adapter.identity.app_quality_session_id, TestContextSessionID2); @@ -110,7 +112,8 @@ - (void)test_settingSessionIDOutOfOrder_protoHasLastSessionID { FIRCLSReportAdapter *adapter = [[FIRCLSReportAdapter alloc] initWithPath:self.report.path googleAppId:@"TestGoogleAppID" installIDModel:self.installIDModel - fiid:@"TestFIID"]; + fiid:@"TestFIID" + authToken:@"TestAuthToken"]; NSLog(@"reportPath: %@", self.report.path); XCTAssertEqualObjects(adapter.identity.app_quality_session_id, TestContextSessionID2); diff --git a/Crashlytics/UnitTests/FIRCLSFileTests.m b/Crashlytics/UnitTests/FIRCLSFileTests.m index 85ff6c36a57..b8895f68a67 100644 --- a/Crashlytics/UnitTests/FIRCLSFileTests.m +++ b/Crashlytics/UnitTests/FIRCLSFileTests.m @@ -14,6 +14,12 @@ #include "Crashlytics/Crashlytics/Helpers/FIRCLSFile.h" +#if SWIFT_PACKAGE +@import FirebaseCrashlyticsSwift; +#else // Swift Package Manager +#import +#endif // CocoaPods + #import @interface FIRCLSFileTests : XCTestCase @@ -169,6 +175,31 @@ - (void)hexEncodingStringWithFile:(FIRCLSFile *)file buffered ? @"" : @"un"); } +// This is the test to compare FIRCLSwiftFileUtility.stringToHexConverter(for:) and +// FIRCLSFileWriteHexEncodedString return the same hex encoding value +- (void)testHexEncodingStringObjcAndSwiftResultsSame { + NSString *testedValueString = @"是themis的测试数据,输入中文"; + + FIRCLSFile *unbufferedFile = &_unbufferedFile; + FIRCLSFileWriteHashStart(unbufferedFile); + FIRCLSFileWriteHashEntryHexEncodedString(unbufferedFile, "hex", [testedValueString UTF8String]); + FIRCLSFileWriteHashEnd(unbufferedFile); + NSString *contentsFromObjcHexEncoding = [self contentsOfFileAtPath:self.unbufferedPath]; + + FIRCLSFile *bufferedFile = &_bufferedFile; + NSString *encodedValue = [FIRCLSwiftFileUtility stringToHexConverterFor:testedValueString]; + FIRCLSFileWriteHashStart(bufferedFile); + FIRCLSFileWriteHashKey(bufferedFile, "hex"); + FIRCLSFileWriteStringUnquoted(bufferedFile, "\""); + FIRCLSFileWriteStringUnquoted(bufferedFile, [encodedValue UTF8String]); + FIRCLSFileWriteStringUnquoted(bufferedFile, "\""); + FIRCLSFileWriteHashEnd(bufferedFile); + FIRCLSFileFlushWriteBuffer(bufferedFile); + NSString *contentsFromSwiftHexEncoding = [self contentsOfFileAtPath:self.bufferedPath]; + + XCTAssertTrue([contentsFromObjcHexEncoding isEqualToString:contentsFromSwiftHexEncoding]); +} + #pragma mark - - (void)testHexEncodingLongString { diff --git a/Crashlytics/UnitTests/FIRCLSInstallIdentifierModelTests.m b/Crashlytics/UnitTests/FIRCLSInstallIdentifierModelTests.m index b95b65b3ff6..6ccbc00b932 100644 --- a/Crashlytics/UnitTests/FIRCLSInstallIdentifierModelTests.m +++ b/Crashlytics/UnitTests/FIRCLSInstallIdentifierModelTests.m @@ -66,10 +66,13 @@ - (void)testCreateUUIDAndRotate { [[FIRCLSInstallIdentifierModel alloc] initWithInstallations:iid]; XCTAssertNotNil(model.installID); - BOOL didRotate = [model regenerateInstallIDIfNeededWithBlock:^(NSString *_Nonnull fiid){ - }]; + BOOL didRotate = [model + regenerateInstallIDIfNeededWithBlock:^(NSString *_Nonnull fiid, NSString *_Nonnull authToken){ + }]; sleep(1); + XCTAssertTrue(iid.authTokenFinished); + XCTAssertTrue(iid.installationIDFinished); XCTAssertFalse(didRotate); XCTAssertEqualObjects([_defaults objectForKey:FABInstallationUUIDKey], model.installID); XCTAssertNil([_defaults objectForKey:FABInstallationADIDKey]); @@ -85,10 +88,13 @@ - (void)testCreateUUIDAndErrorGettingInstanceID { [[FIRCLSInstallIdentifierModel alloc] initWithInstallations:iid]; XCTAssertNotNil(model.installID); - BOOL didRotate = [model regenerateInstallIDIfNeededWithBlock:^(NSString *_Nonnull fiid){ - }]; + BOOL didRotate = [model + regenerateInstallIDIfNeededWithBlock:^(NSString *_Nonnull fiid, NSString *_Nonnull authToken){ + }]; XCTAssertFalse(didRotate); + XCTAssertTrue(iid.authTokenFinished); + XCTAssertTrue(iid.installationIDFinished); XCTAssertEqualObjects([_defaults objectForKey:FABInstallationUUIDKey], model.installID); XCTAssertNil([_defaults objectForKey:FABInstallationADIDKey]); XCTAssertEqualObjects(nil, [_defaults objectForKey:FIRCLSInstallationIIDHashKey]); @@ -135,10 +141,13 @@ - (void)testIIDChanges { [[FIRCLSInstallIdentifierModel alloc] initWithInstallations:iid]; XCTAssertNotNil(model.installID); - BOOL didRotate = [model regenerateInstallIDIfNeededWithBlock:^(NSString *_Nonnull fiid){ - }]; + BOOL didRotate = [model + regenerateInstallIDIfNeededWithBlock:^(NSString *_Nonnull fiid, NSString *_Nonnull authToken){ + }]; XCTAssertTrue(didRotate); + XCTAssertTrue(iid.authTokenFinished); + XCTAssertTrue(iid.installationIDFinished); // Test that the UUID changed. XCTAssertNotEqualObjects(model.installID, @"old_uuid"); XCTAssertEqualObjects([_defaults objectForKey:FABInstallationUUIDKey], model.installID); @@ -158,10 +167,13 @@ - (void)testIIDDoesntChange { [[FIRCLSInstallIdentifierModel alloc] initWithInstallations:iid]; XCTAssertNotNil(model.installID); - BOOL didRotate = [model regenerateInstallIDIfNeededWithBlock:^(NSString *_Nonnull fiid){ - }]; + BOOL didRotate = [model + regenerateInstallIDIfNeededWithBlock:^(NSString *_Nonnull fiid, NSString *_Nonnull authToken){ + }]; XCTAssertFalse(didRotate); + XCTAssertTrue(iid.authTokenFinished); + XCTAssertTrue(iid.installationIDFinished); // Test that the UUID changed. XCTAssertEqualObjects(model.installID, @"test_uuid"); XCTAssertEqualObjects([_defaults objectForKey:FABInstallationUUIDKey], model.installID); @@ -180,10 +192,13 @@ - (void)testUUIDSetButNeverIIDNilIID { [[FIRCLSInstallIdentifierModel alloc] initWithInstallations:iid]; XCTAssertNotNil(model.installID); - BOOL didRotate = [model regenerateInstallIDIfNeededWithBlock:^(NSString *_Nonnull fiid){ - }]; + BOOL didRotate = [model + regenerateInstallIDIfNeededWithBlock:^(NSString *_Nonnull fiid, NSString *_Nonnull authToken){ + }]; XCTAssertFalse(didRotate); + XCTAssertTrue(iid.authTokenFinished); + XCTAssertTrue(iid.installationIDFinished); // Test that the UUID did not change. The FIID can be nil if // there's no FIID cached, so we can't say whether to regenerate XCTAssertEqualObjects(model.installID, @"old_uuid"); @@ -202,10 +217,13 @@ - (void)testUUIDSetButNeverIIDWithIID { [[FIRCLSInstallIdentifierModel alloc] initWithInstallations:iid]; XCTAssertNotNil(model.installID); - BOOL didRotate = [model regenerateInstallIDIfNeededWithBlock:^(NSString *_Nonnull fiid){ - }]; + BOOL didRotate = [model + regenerateInstallIDIfNeededWithBlock:^(NSString *_Nonnull fiid, NSString *_Nonnull authToken){ + }]; XCTAssertFalse(didRotate); + XCTAssertTrue(iid.authTokenFinished); + XCTAssertTrue(iid.installationIDFinished); // Test that the UUID did not change. The FIID can be nil if // there's no FIID cached, so we can't say whether to regenerate XCTAssertEqualObjects(model.installID, @"old_uuid"); @@ -226,10 +244,14 @@ - (void)testADIDWasSetButNeverIID { [[FIRCLSInstallIdentifierModel alloc] initWithInstallations:iid]; XCTAssertNotNil(model.installID); - BOOL didRotate = [model regenerateInstallIDIfNeededWithBlock:^(NSString *_Nonnull fiid){ - }]; + BOOL didRotate = [model + regenerateInstallIDIfNeededWithBlock:^(NSString *_Nonnull fiid, NSString *_Nonnull authToken){ + }]; XCTAssertFalse(didRotate); + XCTAssertTrue(iid.authTokenFinished); + XCTAssertTrue(iid.installationIDFinished); + // Test that the UUID didn't change. XCTAssertEqualObjects(model.installID, @"test_uuid"); XCTAssertEqualObjects([_defaults objectForKey:FABInstallationUUIDKey], model.installID); @@ -248,10 +270,13 @@ - (void)testADIDWasSetAndIIDBecomesSet { [[FIRCLSInstallIdentifierModel alloc] initWithInstallations:iid]; XCTAssertNotNil(model.installID); - BOOL didRotate = [model regenerateInstallIDIfNeededWithBlock:^(NSString *_Nonnull fiid){ - }]; + BOOL didRotate = [model + regenerateInstallIDIfNeededWithBlock:^(NSString *_Nonnull fiid, NSString *_Nonnull authToken){ + }]; XCTAssertFalse(didRotate); + XCTAssertTrue(iid.authTokenFinished); + XCTAssertTrue(iid.installationIDFinished); // Test that the UUID didn't change. XCTAssertEqualObjects(model.installID, @"test_uuid"); XCTAssertEqualObjects([_defaults objectForKey:FABInstallationUUIDKey], model.installID); @@ -272,8 +297,9 @@ - (void)testADIDAndIIDWereSet { [[FIRCLSInstallIdentifierModel alloc] initWithInstallations:iid]; XCTAssertNotNil(model.installID); - BOOL didRotate = [model regenerateInstallIDIfNeededWithBlock:^(NSString *_Nonnull fiid){ - }]; + BOOL didRotate = [model + regenerateInstallIDIfNeededWithBlock:^(NSString *_Nonnull fiid, NSString *_Nonnull authToken){ + }]; XCTAssertFalse(didRotate); // Test that the UUID didn't change. @@ -297,10 +323,13 @@ - (void)testADIDAndIIDWereSet2 { [[FIRCLSInstallIdentifierModel alloc] initWithInstallations:iid]; XCTAssertNotNil(model.installID); - BOOL didRotate = [model regenerateInstallIDIfNeededWithBlock:^(NSString *_Nonnull fiid){ - }]; + BOOL didRotate = [model + regenerateInstallIDIfNeededWithBlock:^(NSString *_Nonnull fiid, NSString *_Nonnull authToken){ + }]; XCTAssertTrue(didRotate); + XCTAssertTrue(iid.authTokenFinished); + XCTAssertTrue(iid.installationIDFinished); // Test that the UUID change. XCTAssertNotEqualObjects(model.installID, @"test_uuid"); XCTAssertEqualObjects([_defaults objectForKey:FABInstallationUUIDKey], model.installID); diff --git a/Crashlytics/UnitTests/FIRCLSLoggingTests.m b/Crashlytics/UnitTests/FIRCLSLoggingTests.m index a5c72f5c73c..b79341fe06e 100644 --- a/Crashlytics/UnitTests/FIRCLSLoggingTests.m +++ b/Crashlytics/UnitTests/FIRCLSLoggingTests.m @@ -365,7 +365,7 @@ - (void)testLoggedError { code:-1 userInfo:@{@"key1" : @"value", @"key2" : @"value2"}]; - FIRCLSUserLoggingRecordError(error, @{@"additional" : @"key"}); + FIRCLSUserLoggingRecordError(error, @{@"additional" : @"key"}, nil); NSArray* errors = [self errorAContents]; @@ -405,7 +405,7 @@ - (void)testWritingMaximumNumberOfLoggedErrors { userInfo:@{@"key1" : @"value", @"key2" : @"value2"}]; for (size_t i = 0; i < _firclsContext.readonly->logging.errorStorage.maxEntries; ++i) { - FIRCLSUserLoggingRecordError(error, nil); + FIRCLSUserLoggingRecordError(error, nil, nil); } NSArray* errors = [self errorAContents]; @@ -414,7 +414,7 @@ - (void)testWritingMaximumNumberOfLoggedErrors { // at this point, if we log one more, we should expect a roll over to the next file - FIRCLSUserLoggingRecordError(error, nil); + FIRCLSUserLoggingRecordError(error, nil, nil); XCTAssertEqual([[self errorAContents] count], 8, @""); XCTAssertEqual([[self errorBContents] count], 1, @""); @@ -422,7 +422,7 @@ - (void)testWritingMaximumNumberOfLoggedErrors { // and our next entry should continue into the B file - FIRCLSUserLoggingRecordError(error, nil); + FIRCLSUserLoggingRecordError(error, nil, nil); XCTAssertEqual([[self errorAContents] count], 8, @""); XCTAssertEqual([[self errorBContents] count], 2, @""); @@ -432,7 +432,7 @@ - (void)testWritingMaximumNumberOfLoggedErrors { - (void)testLoggedErrorWithNullsInAdditionalInfo { NSError* error = [NSError errorWithDomain:@"Domain" code:-1 userInfo:nil]; - FIRCLSUserLoggingRecordError(error, @{@"null-key" : [NSNull null]}); + FIRCLSUserLoggingRecordError(error, @{@"null-key" : [NSNull null]}, nil); NSArray* errors = [self errorAContents]; diff --git a/Crashlytics/UnitTests/FIRCLSReportAdapterTests.m b/Crashlytics/UnitTests/FIRCLSReportAdapterTests.m index 1298298ac9c..ae53871f043 100644 --- a/Crashlytics/UnitTests/FIRCLSReportAdapterTests.m +++ b/Crashlytics/UnitTests/FIRCLSReportAdapterTests.m @@ -32,6 +32,7 @@ @interface FIRCLSReportAdapterTests : XCTestCase @end static NSString *const TestFIID = @"TEST_FIID"; +static NSString *const TestAuthToken = @"TEST_AUTH_TOKEN"; @implementation FIRCLSReportAdapterTests @@ -46,7 +47,8 @@ - (FIRCLSReportAdapter *)constructAdapterWithPath:(NSString *)path return [[FIRCLSReportAdapter alloc] initWithPath:path googleAppId:googleAppID installIDModel:installIDModel - fiid:TestFIID]; + fiid:TestFIID + authToken:TestAuthToken]; } /// Attempt sending a proto report to the reporting endpoint diff --git a/Crashlytics/UnitTests/FIRCLSRolloutsPersistenceManagerTests.m b/Crashlytics/UnitTests/FIRCLSRolloutsPersistenceManagerTests.m new file mode 100644 index 00000000000..aec030d7538 --- /dev/null +++ b/Crashlytics/UnitTests/FIRCLSRolloutsPersistenceManagerTests.m @@ -0,0 +1,70 @@ +// Copyright 2024 Google +// +// 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 +#import + +#import "Crashlytics/Crashlytics/Components/FIRCLSContext.h" +#import "Crashlytics/Crashlytics/Controllers/FIRCLSRolloutsPersistenceManager.h" +#import "Crashlytics/Crashlytics/Models/FIRCLSInternalReport.h" +#import "Crashlytics/UnitTests/Mocks/FIRCLSTempMockFileManager.h" +#if SWIFT_PACKAGE +@import FirebaseCrashlyticsSwift; +#else // Swift Package Manager +#import +#endif // CocoaPods + +NSString *reportId = @"1234567"; + +@interface FIRCLSRolloutsPersistenceManagerTests : XCTestCase +@property(nonatomic, strong) FIRCLSTempMockFileManager *fileManager; +@property(nonatomic, strong) FIRCLSRolloutsPersistenceManager *rolloutsPersistenceManager; +@end + +@implementation FIRCLSRolloutsPersistenceManagerTests +- (void)setUp { + [super setUp]; + FIRCLSContextBaseInit(); + self.fileManager = [[FIRCLSTempMockFileManager alloc] init]; + [self.fileManager createReportDirectories]; + [self.fileManager setupNewPathForExecutionIdentifier:reportId]; + + self.rolloutsPersistenceManager = + [[FIRCLSRolloutsPersistenceManager alloc] initWithFileManager:self.fileManager]; +} + +- (void)tearDown { + [self.fileManager removeItemAtPath:_fileManager.rootPath]; + FIRCLSContextBaseDeinit(); + [super tearDown]; +} + +- (void)testUpdateRolloutsStateToPersistenceWithRollouts { + NSString *encodedStateString = + @"{rollouts:[{\"parameter_key\":\"6d795f66656174757265\",\"parameter_value\":" + @"\"e8bf99e698af7468656d6973e79a84e6b58be8af95e695b0e68daeefbc8ce8be93e585a5e4b8ade69687\"," + @"\"rollout_id\":\"726f6c6c6f75745f31\",\"template_version\":1,\"variant_id\":" + @"\"636f6e74726f6c\"}]}"; + + NSData *data = [encodedStateString dataUsingEncoding:NSUTF8StringEncoding]; + NSString *rolloutsFilePath = + [[[self.fileManager activePath] stringByAppendingPathComponent:reportId] + stringByAppendingPathComponent:FIRCLSReportRolloutsFile]; + + [self.rolloutsPersistenceManager updateRolloutsStateToPersistenceWithRollouts:data + reportID:reportId]; + XCTAssertTrue([[NSFileManager defaultManager] fileExistsAtPath:rolloutsFilePath]); +} + +@end diff --git a/Crashlytics/UnitTests/FIRRecordExceptionModelTests.m b/Crashlytics/UnitTests/FIRRecordExceptionModelTests.m index 1908fea71db..e564614f8ae 100644 --- a/Crashlytics/UnitTests/FIRRecordExceptionModelTests.m +++ b/Crashlytics/UnitTests/FIRRecordExceptionModelTests.m @@ -75,7 +75,7 @@ - (void)testWrittenCLSRecordFile { FIRExceptionModel *exceptionModel = [FIRExceptionModel exceptionModelWithName:name reason:reason]; exceptionModel.stackTrace = stackTrace; - FIRCLSExceptionRecordModel(exceptionModel); + FIRCLSExceptionRecordModel(exceptionModel, nil); NSData *data = [NSData dataWithContentsOfFile:[self.reportPath diff --git a/Crashlytics/UnitTests/Mocks/FIRMockInstallations.h b/Crashlytics/UnitTests/Mocks/FIRMockInstallations.h index f697c9a6654..6d3ab54d1be 100644 --- a/Crashlytics/UnitTests/Mocks/FIRMockInstallations.h +++ b/Crashlytics/UnitTests/Mocks/FIRMockInstallations.h @@ -16,6 +16,9 @@ @interface FIRMockInstallations : FIRInstallations +@property(nonatomic) BOOL authTokenFinished; +@property(nonatomic) BOOL installationIDFinished; + - (instancetype)initWithFID:(NSString *)installationID; - (instancetype)initWithError:(NSError *)error; diff --git a/Crashlytics/UnitTests/Mocks/FIRMockInstallations.m b/Crashlytics/UnitTests/Mocks/FIRMockInstallations.m index 25dda90e724..7d546a6ca8c 100644 --- a/Crashlytics/UnitTests/Mocks/FIRMockInstallations.m +++ b/Crashlytics/UnitTests/Mocks/FIRMockInstallations.m @@ -21,11 +21,24 @@ @interface FIRMockInstallationsImpl : NSObject @property(nonatomic, copy) NSString *installationID; @property(nonatomic, strong) NSError *error; +// the init function is not public for the token result, use as a placeholder to mock the token +// completion block +@property(nonatomic, strong) FIRInstallationsAuthTokenResult *tokenResult; + +@property(nonatomic) BOOL authTokenFinished; +@property(nonatomic) BOOL installationIDFinished; + @end @implementation FIRMockInstallationsImpl +- (void)authTokenWithCompletion:(FIRInstallationsTokenHandler)completion { + self.authTokenFinished = true; + completion(self.tokenResult, self.error); +} + - (void)installationIDWithCompletion:(FIRInstallationsIDHandler)completion { + self.installationIDFinished = true; completion(self.installationID, self.error); } @@ -41,6 +54,8 @@ - (instancetype)initWithFID:(NSString *)installationID { FIRMockInstallationsImpl *mock = [[FIRMockInstallationsImpl alloc] init]; mock.installationID = [installationID copy]; mock.error = nil; + mock.authTokenFinished = false; + mock.installationIDFinished = false; self = (id)mock; return self; } @@ -49,6 +64,8 @@ - (instancetype)initWithError:(NSError *)error { FIRMockInstallationsImpl *mock = [[FIRMockInstallationsImpl alloc] init]; mock.installationID = nil; mock.error = error; + mock.authTokenFinished = false; + mock.installationIDFinished = false; self = (id)mock; return self; } diff --git a/Crashlytics/UnitTestsSwift/CrashlyticsRemoteConfigManagerTests.swift b/Crashlytics/UnitTestsSwift/CrashlyticsRemoteConfigManagerTests.swift new file mode 100644 index 00000000000..6c2e070e47f --- /dev/null +++ b/Crashlytics/UnitTestsSwift/CrashlyticsRemoteConfigManagerTests.swift @@ -0,0 +1,136 @@ +// Copyright 2023 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. +#if SWIFT_PACKAGE + @testable import FirebaseCrashlyticsSwift +#else + @testable import FirebaseCrashlytics +#endif +import FirebaseRemoteConfigInterop +import XCTest + +class RemoteConfigConfigMock: RemoteConfigInterop { + func registerRolloutsStateSubscriber(_ subscriber: FirebaseRemoteConfigInterop + .RolloutsStateSubscriber, + for namespace: String) {} +} + +class PersistanceManagerMock: CrashlyticsPersistenceLog { + func updateRolloutsStateToPersistence(rollouts: Data, reportID: String) {} + func debugLog(message: String) {} +} + +final class CrashlyticsRemoteConfigManagerTests: XCTestCase { + let rollouts: RolloutsState = { + let assignment1 = RolloutAssignment( + rolloutId: "rollout_1", + variantId: "control", + templateVersion: 1, + parameterKey: "my_feature", + parameterValue: "false" + ) + let assignment2 = RolloutAssignment( + rolloutId: "rollout_2", + variantId: "enabled", + templateVersion: 1, + parameterKey: "themis_big_feature", + parameterValue: "1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111" + ) + let rollouts = RolloutsState(assignmentList: [assignment1, assignment2]) + return rollouts + }() + + let singleRollout: RolloutsState = { + let assignment1 = RolloutAssignment( + rolloutId: "rollout_1", + variantId: "control", + templateVersion: 1, + parameterKey: "my_feature", + parameterValue: "这是themis的测试数据,输入中文" // check unicode + ) + let rollouts = RolloutsState(assignmentList: [assignment1]) + return rollouts + }() + + let rcInterop = RemoteConfigConfigMock() + + func testRemoteConfigManagerProperlyProcessRolloutsState() throws { + let rcManager = CrashlyticsRemoteConfigManager( + remoteConfig: rcInterop, + persistenceDelegate: PersistanceManagerMock() + ) + rcManager.updateRolloutsState(rolloutsState: rollouts, reportID: "12R") + XCTAssertEqual(rcManager.rolloutAssignment.count, 2) + + for assignment in rollouts.assignments { + if assignment.parameterKey == "themis_big_feature" { + XCTAssertEqual( + assignment.parameterValue.count, + CrashlyticsRemoteConfigManager.maxParameterValueLength + ) + } + } + } + + func testRemoteConfigManagerGenerateEncodedRolloutAssignmentsJson() throws { + let expectedString = + "[{\"parameter_key\":\"6d795f66656174757265\",\"parameter_value\":\"e8bf99e698af7468656d6973e79a84e6b58be8af95e695b0e68daeefbc8ce8be93e585a5e4b8ade69687\",\"rollout_id\":\"726f6c6c6f75745f31\",\"template_version\":1,\"variant_id\":\"636f6e74726f6c\"}]" + + let rcManager = CrashlyticsRemoteConfigManager( + remoteConfig: rcInterop, + persistenceDelegate: PersistanceManagerMock() + ) + rcManager.updateRolloutsState(rolloutsState: singleRollout, reportID: "456") + + let string = rcManager.getRolloutAssignmentsEncodedJsonString() + XCTAssertEqual(string, expectedString) + } + + func testMultiThreadsUpdateRolloutAssignments() throws { + let rcManager = CrashlyticsRemoteConfigManager( + remoteConfig: rcInterop, + persistenceDelegate: PersistanceManagerMock() + ) + DispatchQueue.main.async { [weak self] in + if let singleRollout = self?.singleRollout { + rcManager.updateRolloutsState(rolloutsState: singleRollout, reportID: "456") + XCTAssertEqual(rcManager.rolloutAssignment.count, 1) + } + } + + DispatchQueue.main.async { [weak self] in + if let rollouts = self?.rollouts { + rcManager.updateRolloutsState(rolloutsState: rollouts, reportID: "456") + XCTAssertEqual(rcManager.rolloutAssignment.count, 2) + } + } + } + + func testMultiThreadsReadAndWriteRolloutAssignments() throws { + let rcManager = CrashlyticsRemoteConfigManager( + remoteConfig: rcInterop, + persistenceDelegate: PersistanceManagerMock() + ) + rcManager.updateRolloutsState(rolloutsState: singleRollout, reportID: "456") + + DispatchQueue.main.async { [weak self] in + if let rollouts = self?.rollouts { + let oldAssignments = rcManager.rolloutAssignment + rcManager.updateRolloutsState(rolloutsState: rollouts, reportID: "456") + XCTAssertEqual(rcManager.rolloutAssignment.count, 2) + XCTAssertEqual(oldAssignments.count, 1) + } + } + XCTAssertEqual(rcManager.rolloutAssignment.count, 1) + } +} diff --git a/Crashlytics/upload-symbols b/Crashlytics/upload-symbols index ace10d505b6..481428e6200 100755 Binary files a/Crashlytics/upload-symbols and b/Crashlytics/upload-symbols differ diff --git a/Example/watchOSSample/Podfile b/Example/watchOSSample/Podfile index 5dd5e804c13..2f862708597 100644 --- a/Example/watchOSSample/Podfile +++ b/Example/watchOSSample/Podfile @@ -19,6 +19,7 @@ target 'SampleWatchAppWatchKitExtension' do pod 'FirebaseDatabase', :path => '../../' pod 'FirebaseAppCheckInterop', :path => '../../' pod 'FirebaseAuthInterop', :path => '../../' + pod 'FirebaseRemoteConfigInterop', :path => '../../' pod 'Firebase/Messaging', :path => '../../' pod 'Firebase/Storage', :path => '../../' diff --git a/Firebase.podspec b/Firebase.podspec index 166ff06f0a1..a56eeced656 100644 --- a/Firebase.podspec +++ b/Firebase.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'Firebase' - s.version = '10.21.0' + s.version = '10.23.0' s.summary = 'Firebase' s.description = <<-DESC @@ -26,7 +26,7 @@ Simplify your app development, grow your user base, and monetize more effectivel s.osx.deployment_target = '10.13' s.tvos.deployment_target = '12.0' - s.cocoapods_version = '>= 1.4.0' + s.cocoapods_version = '>= 1.12.0' s.swift_version = '5.3' @@ -36,14 +36,14 @@ Simplify your app development, grow your user base, and monetize more effectivel ss.ios.deployment_target = '10.0' ss.osx.deployment_target = '10.13' ss.tvos.deployment_target = '12.0' - ss.ios.dependency 'FirebaseAnalytics', '~> 10.21.0' - ss.osx.dependency 'FirebaseAnalytics', '~> 10.21.0' - ss.tvos.dependency 'FirebaseAnalytics', '~> 10.21.0' + ss.ios.dependency 'FirebaseAnalytics', '~> 10.23.0' + ss.osx.dependency 'FirebaseAnalytics', '~> 10.23.0' + ss.tvos.dependency 'FirebaseAnalytics', '~> 10.23.0' ss.dependency 'Firebase/CoreOnly' end s.subspec 'CoreOnly' do |ss| - ss.dependency 'FirebaseCore', '10.21.0' + ss.dependency 'FirebaseCore', '10.23.0' ss.source_files = 'CoreOnly/Sources/Firebase.h' ss.preserve_paths = 'CoreOnly/Sources/module.modulemap' if ENV['FIREBASE_POD_REPO_FOR_DEV_POD'] then @@ -79,13 +79,13 @@ Simplify your app development, grow your user base, and monetize more effectivel ss.ios.deployment_target = '10.0' ss.osx.deployment_target = '10.13' ss.tvos.deployment_target = '12.0' - ss.dependency 'FirebaseAnalytics/WithoutAdIdSupport', '~> 10.21.0' + ss.dependency 'FirebaseAnalytics/WithoutAdIdSupport', '~> 10.23.0' ss.dependency 'Firebase/CoreOnly' end s.subspec 'ABTesting' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseABTesting', '~> 10.21.0' + ss.dependency 'FirebaseABTesting', '~> 10.23.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '11.0' ss.osx.deployment_target = '10.13' @@ -95,13 +95,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', '~> 10.21.0-beta' + ss.ios.dependency 'FirebaseAppDistribution', '~> 10.23.0-beta' ss.ios.deployment_target = '11.0' end s.subspec 'AppCheck' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseAppCheck', '~> 10.21.0' + ss.dependency 'FirebaseAppCheck', '~> 10.23.0' ss.ios.deployment_target = '11.0' ss.osx.deployment_target = '10.13' ss.tvos.deployment_target = '12.0' @@ -110,7 +110,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', '~> 10.21.0' + ss.dependency 'FirebaseAuth', '~> 10.23.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '11.0' ss.osx.deployment_target = '10.13' @@ -120,7 +120,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', '~> 10.21.0' + ss.dependency 'FirebaseCrashlytics', '~> 10.23.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '11.0' ss.osx.deployment_target = '10.13' @@ -130,7 +130,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', '~> 10.21.0' + ss.dependency 'FirebaseDatabase', '~> 10.23.0' # Standard platforms PLUS watchOS 7. ss.ios.deployment_target = '11.0' ss.osx.deployment_target = '10.13' @@ -140,13 +140,13 @@ Simplify your app development, grow your user base, and monetize more effectivel s.subspec 'DynamicLinks' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.ios.dependency 'FirebaseDynamicLinks', '~> 10.21.0' + ss.ios.dependency 'FirebaseDynamicLinks', '~> 10.23.0' ss.ios.deployment_target = '11.0' end s.subspec 'Firestore' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseFirestore', '~> 10.21.0' + ss.dependency 'FirebaseFirestore', '~> 10.23.0' ss.ios.deployment_target = '11.0' ss.osx.deployment_target = '10.13' ss.tvos.deployment_target = '12.0' @@ -154,7 +154,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', '~> 10.21.0' + ss.dependency 'FirebaseFunctions', '~> 10.23.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '11.0' ss.osx.deployment_target = '10.13' @@ -164,20 +164,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', '~> 10.21.0-beta' - ss.tvos.dependency 'FirebaseInAppMessaging', '~> 10.21.0-beta' + ss.ios.dependency 'FirebaseInAppMessaging', '~> 10.23.0-beta' + ss.tvos.dependency 'FirebaseInAppMessaging', '~> 10.23.0-beta' ss.ios.deployment_target = '11.0' ss.tvos.deployment_target = '12.0' end s.subspec 'Installations' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseInstallations', '~> 10.21.0' + ss.dependency 'FirebaseInstallations', '~> 10.23.0' end s.subspec 'Messaging' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseMessaging', '~> 10.21.0' + ss.dependency 'FirebaseMessaging', '~> 10.23.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '11.0' ss.osx.deployment_target = '10.13' @@ -187,7 +187,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', '~> 10.21.0-beta' + ss.dependency 'FirebaseMLModelDownloader', '~> 10.23.0-beta' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '11.0' ss.osx.deployment_target = '10.13' @@ -197,15 +197,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', '~> 10.21.0' - ss.tvos.dependency 'FirebasePerformance', '~> 10.21.0' + ss.ios.dependency 'FirebasePerformance', '~> 10.23.0' + ss.tvos.dependency 'FirebasePerformance', '~> 10.23.0' ss.ios.deployment_target = '11.0' ss.tvos.deployment_target = '12.0' end s.subspec 'RemoteConfig' do |ss| ss.dependency 'Firebase/CoreOnly' - ss.dependency 'FirebaseRemoteConfig', '~> 10.21.0' + ss.dependency 'FirebaseRemoteConfig', '~> 10.23.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '11.0' ss.osx.deployment_target = '10.13' @@ -215,7 +215,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', '~> 10.21.0' + ss.dependency 'FirebaseStorage', '~> 10.23.0' # Standard platforms PLUS watchOS. ss.ios.deployment_target = '11.0' ss.osx.deployment_target = '10.13' diff --git a/FirebaseABTesting.podspec b/FirebaseABTesting.podspec index 6fe6a1f909a..dbd855d6486 100644 --- a/FirebaseABTesting.podspec +++ b/FirebaseABTesting.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseABTesting' - s.version = '10.21.0' + s.version = '10.23.0' s.summary = 'Firebase ABTesting' s.description = <<-DESC @@ -32,7 +32,7 @@ Firebase Cloud Messaging and Firebase Remote Config in your app. s.tvos.deployment_target = tvos_deployment_target s.watchos.deployment_target = watchos_deployment_target - s.cocoapods_version = '>= 1.4.0' + s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false s.swift_version = '5.3' @@ -43,6 +43,9 @@ Firebase Cloud Messaging and Firebase Remote Config in your app. 'Interop/Analytics/Public/*.h', 'FirebaseCore/Extension/*.h', ] + s.resource_bundles = { + "#{s.module_name}_Privacy" => 'FirebaseABTesting/Sources/Resources/PrivacyInfo.xcprivacy' + } s.requires_arc = base_dir + '*.m' s.public_header_files = base_dir + 'Public/FirebaseABTesting/*.h' s.pod_target_xcconfig = { diff --git a/FirebaseABTesting/Sources/Resources/PrivacyInfo.xcprivacy b/FirebaseABTesting/Sources/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 00000000000..c89c88f62f5 --- /dev/null +++ b/FirebaseABTesting/Sources/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,18 @@ + + + + + NSPrivacyTracking + + NSPrivacyTrackingDomains + + + NSPrivacyCollectedDataTypes + + + NSPrivacyAccessedAPITypes + + + + + diff --git a/FirebaseAnalytics.podspec b/FirebaseAnalytics.podspec index d9b4237cb23..2e5ef18ec3a 100644 --- a/FirebaseAnalytics.podspec +++ b/FirebaseAnalytics.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAnalytics' - s.version = '10.21.0' + s.version = '10.23.0' s.summary = 'Firebase Analytics for iOS' s.description = <<-DESC @@ -13,10 +13,10 @@ Pod::Spec.new do |s| s.authors = 'Google, Inc.' s.source = { - :http => 'https://dl.google.com/firebase/ios/analytics/0199e7929b47e2d9/FirebaseAnalytics-10.20.0.tar.gz' + :http => 'https://dl.google.com/firebase/ios/analytics/dedc8d0f648c53b6/FirebaseAnalytics-10.23.0.tar.gz' } - s.cocoapods_version = '>= 1.10.0' + s.cocoapods_version = '>= 1.12.0' s.swift_version = '5.3' s.ios.deployment_target = '10.0' @@ -32,17 +32,17 @@ Pod::Spec.new do |s| s.dependency 'GoogleUtilities/MethodSwizzler', '~> 7.11' s.dependency 'GoogleUtilities/NSData+zlib', '~> 7.11' s.dependency 'GoogleUtilities/Network', '~> 7.11' - s.dependency 'nanopb', '>= 2.30908.0', '< 2.30910.0' + s.dependency 'nanopb', '>= 2.30908.0', '< 2.30911.0' s.default_subspecs = 'AdIdSupport' s.subspec 'AdIdSupport' do |ss| - ss.dependency 'GoogleAppMeasurement', '10.21.0' + ss.dependency 'GoogleAppMeasurement', '10.23.0' ss.vendored_frameworks = 'Frameworks/FirebaseAnalytics.xcframework' end s.subspec 'WithoutAdIdSupport' do |ss| - ss.dependency 'GoogleAppMeasurement/WithoutAdIdSupport', '10.21.0' + ss.dependency 'GoogleAppMeasurement/WithoutAdIdSupport', '10.23.0' ss.vendored_frameworks = 'Frameworks/FirebaseAnalytics.xcframework' end diff --git a/FirebaseAnalyticsOnDeviceConversion.podspec b/FirebaseAnalyticsOnDeviceConversion.podspec index e8e187baa8f..e4ec770b8e5 100644 --- a/FirebaseAnalyticsOnDeviceConversion.podspec +++ b/FirebaseAnalyticsOnDeviceConversion.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAnalyticsOnDeviceConversion' - s.version = '10.21.0' + s.version = '10.23.0' s.summary = 'On device conversion measurement plugin for FirebaseAnalytics. Not intended for direct use.' s.description = <<-DESC @@ -16,9 +16,9 @@ Pod::Spec.new do |s| :tag => 'CocoaPods-' + s.version.to_s } - s.cocoapods_version = '>= 1.10.2' + s.cocoapods_version = '>= 1.12.0' - s.dependency 'GoogleAppMeasurementOnDeviceConversion', '10.21.0' + s.dependency 'GoogleAppMeasurementOnDeviceConversion', '10.23.0' s.static_framework = true diff --git a/FirebaseAnalyticsSwift.podspec b/FirebaseAnalyticsSwift.podspec index 0d13db74422..4fb8790af9d 100644 --- a/FirebaseAnalyticsSwift.podspec +++ b/FirebaseAnalyticsSwift.podspec @@ -27,7 +27,7 @@ Firebase Analytics is a free, out-of-the-box analytics solution that inspires ac s.osx.deployment_target = osx_deployment_target s.tvos.deployment_target = tvos_deployment_target - s.cocoapods_version = '>= 1.10.0' + s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false s.source_files = [ diff --git a/FirebaseAnalyticsSwift/Tests/ObjCAPI/ObjCAPITests.m b/FirebaseAnalyticsSwift/Tests/ObjCAPI/ObjCAPITests.m index a0be6eee954..6392674a812 100644 --- a/FirebaseAnalyticsSwift/Tests/ObjCAPI/ObjCAPITests.m +++ b/FirebaseAnalyticsSwift/Tests/ObjCAPI/ObjCAPITests.m @@ -60,6 +60,8 @@ - (void)consentTests:(NSURL *)url { - (void)onDeviceConversionTests:(NSURL *)url { [FIRAnalytics initiateOnDeviceConversionMeasurementWithEmailAddress:@"a@.a.com"]; [FIRAnalytics initiateOnDeviceConversionMeasurementWithPhoneNumber:@"+15555555555"]; + [FIRAnalytics initiateOnDeviceConversionMeasurementWithHashedEmailAddress:[NSData data]]; + [FIRAnalytics initiateOnDeviceConversionMeasurementWithHashedPhoneNumber:[NSData data]]; } - (NSArray *)eventNames { diff --git a/FirebaseAnalyticsSwift/Tests/SwiftUnit/AnalyticsAPITests.swift b/FirebaseAnalyticsSwift/Tests/SwiftUnit/AnalyticsAPITests.swift index 0b0542f8417..1d6a454f771 100644 --- a/FirebaseAnalyticsSwift/Tests/SwiftUnit/AnalyticsAPITests.swift +++ b/FirebaseAnalyticsSwift/Tests/SwiftUnit/AnalyticsAPITests.swift @@ -73,6 +73,8 @@ final class AnalyticsAPITests { Analytics.initiateOnDeviceConversionMeasurement(emailAddress: "test@gmail.com") Analytics.initiateOnDeviceConversionMeasurement(phoneNumber: "+15555555555") + Analytics.initiateOnDeviceConversionMeasurement(hashedEmailAddress: Data()) + Analytics.initiateOnDeviceConversionMeasurement(hashedPhoneNumber: Data()) // MARK: - EventNames diff --git a/FirebaseAppCheck.podspec b/FirebaseAppCheck.podspec index d3bdec34b4f..da7607cd677 100644 --- a/FirebaseAppCheck.podspec +++ b/FirebaseAppCheck.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAppCheck' - s.version = '10.21.0' + s.version = '10.23.0' s.summary = 'Firebase App Check SDK.' s.description = <<-DESC @@ -29,7 +29,7 @@ Pod::Spec.new do |s| s.tvos.deployment_target = tvos_deployment_target s.watchos.deployment_target = watchos_deployment_target - s.cocoapods_version = '>= 1.4.0' + s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false base_dir = "FirebaseAppCheck/" diff --git a/FirebaseAppCheck/Sources/Core/FIRAppCheck.m b/FirebaseAppCheck/Sources/Core/FIRAppCheck.m index f1ccdab2b6d..3c6001ecdf1 100644 --- a/FirebaseAppCheck/Sources/Core/FIRAppCheck.m +++ b/FirebaseAppCheck/Sources/Core/FIRAppCheck.m @@ -48,8 +48,6 @@ static id _providerFactory; -static NSString *const kDummyFACTokenValue = @"eyJlcnJvciI6IlVOS05PV05fRVJST1IifQ=="; - @interface FIRAppCheck () @property(class, nullable) id providerFactory; diff --git a/FirebaseAppCheckInterop.podspec b/FirebaseAppCheckInterop.podspec index 1485738593b..bc5111f6ae8 100644 --- a/FirebaseAppCheckInterop.podspec +++ b/FirebaseAppCheckInterop.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAppCheckInterop' - s.version = '10.21.0' + s.version = '10.23.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 de683d47ebe..11bbbacd781 100644 --- a/FirebaseAppDistribution.podspec +++ b/FirebaseAppDistribution.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAppDistribution' - s.version = '10.21.0-beta' + s.version = '10.23.0-beta' s.summary = 'App Distribution for Firebase iOS SDK.' s.description = <<-DESC @@ -19,7 +19,7 @@ iOS SDK for App Distribution for Firebase. s.swift_version = '5.3' - s.cocoapods_version = '>= 1.4.0' + s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false base_dir = "FirebaseAppDistribution/Sources/" diff --git a/FirebaseAuth.podspec b/FirebaseAuth.podspec index 5eb2114bf33..2b75b4330dd 100644 --- a/FirebaseAuth.podspec +++ b/FirebaseAuth.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAuth' - s.version = '10.21.0' + s.version = '10.23.0' s.summary = 'Apple platform client for Firebase Authentication' s.description = <<-DESC @@ -31,7 +31,7 @@ supports email and password accounts, as well as several 3rd party authenticatio s.tvos.deployment_target = tvos_deployment_target s.watchos.deployment_target = watchos_deployment_target - s.cocoapods_version = '>= 1.4.0' + s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false source = 'FirebaseAuth/Sources/' @@ -41,7 +41,9 @@ supports email and password accounts, as well as several 3rd party authenticatio source + 'Public/FirebaseAuth/*.h' ] s.public_header_files = source + 'Public/FirebaseAuth/*.h' - + s.resource_bundles = { + "#{s.module_name}_Privacy" => 'FirebaseAuth/Sources/Resources/PrivacyInfo.xcprivacy' + } s.preserve_paths = [ 'FirebaseAuth/README.md', 'FirebaseAuth/CHANGELOG.md' @@ -53,7 +55,7 @@ supports email and password accounts, as well as several 3rd party authenticatio } s.framework = 'Security' s.ios.framework = 'SafariServices' - s.dependency 'FirebaseAuthInterop', '~> 10.9' + s.dependency 'FirebaseAuthInterop', '~> 10.22' s.dependency 'FirebaseAppCheckInterop', '~> 10.17' s.dependency 'FirebaseCore', '~> 10.0' s.dependency 'FirebaseCoreExtension', '~> 10.0' diff --git a/FirebaseAuth/Interop/CMakeLists.txt b/FirebaseAuth/Interop/CMakeLists.txt index e38b5b5aa87..f5107b1e7b6 100644 --- a/FirebaseAuth/Interop/CMakeLists.txt +++ b/FirebaseAuth/Interop/CMakeLists.txt @@ -16,7 +16,7 @@ if(NOT APPLE) return() endif() -file(GLOB headers Public/*.h) +file(GLOB headers Public/FirebaseAuthInterop/*.h) firebase_ios_generate_dummy_source(FirebaseAuthInterop sources) firebase_ios_add_framework( diff --git a/FirebaseAuth/Interop/FIRAuthInterop.h b/FirebaseAuth/Interop/Public/FirebaseAuthInterop/FIRAuthInterop.h similarity index 100% rename from FirebaseAuth/Interop/FIRAuthInterop.h rename to FirebaseAuth/Interop/Public/FirebaseAuthInterop/FIRAuthInterop.h diff --git a/FirebaseAuth/Sources/Resources/PrivacyInfo.xcprivacy b/FirebaseAuth/Sources/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 00000000000..123ccfcd6f7 --- /dev/null +++ b/FirebaseAuth/Sources/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,50 @@ + + + + + NSPrivacyTracking + + NSPrivacyTrackingDomains + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeOtherDiagnosticData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAnalytics + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeUserID + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + + + diff --git a/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeInfo.swift b/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeInfo.swift index 677a8e5819d..09fcac26a46 100644 --- a/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeInfo.swift +++ b/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeInfo.swift @@ -14,24 +14,16 @@ import Foundation -/** @class ActionCodeInfo - @brief Manages information regarding action codes. - */ +/// Manages information regarding action codes. @objc(FIRActionCodeInfo) open class ActionCodeInfo: NSObject { - /** - @brief The operation being performed. - */ + /// The operation being performed. @objc public let operation: ActionCodeOperation - /** @property email - @brief The email address to which the code was sent. The new email address in the case of - `ActionCodeOperationRecoverEmail`. - */ + /// The email address to which the code was sent. The new email address in the case of + /// `ActionCodeOperation.recoverEmail`. @objc public let email: String? - /** @property previousEmail - @brief The email that is being recovered in the case of `ActionCodeOperationRecoverEmail`. - */ + /// The email that is being recovered in the case of `ActionCodeOperation.recoverEmail`. @objc public let previousEmail: String? init(withOperation operation: ActionCodeOperation, email: String, newEmail: String?) { @@ -45,11 +37,9 @@ import Foundation } } - /** @fn actionCodeOperationForRequestType: - @brief Returns the corresponding operation type per provided request type string. - @param requestType Request type returned in in the server response. - @return The corresponding ActionCodeOperation for the supplied request type. - */ + /// Map a request type string to the corresponding operation type. + /// - Parameter requestType: Request type returned in in the server response. + /// - Returns: The corresponding ActionCodeOperation for the supplied request type. class func actionCodeOperation(forRequestType requestType: String?) -> ActionCodeOperation { switch requestType { case "PASSWORD_RESET": return .passwordReset diff --git a/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeOperation.swift b/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeOperation.swift index 14dc28dd19a..e345b6e91e8 100644 --- a/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeOperation.swift +++ b/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeOperation.swift @@ -14,28 +14,26 @@ import Foundation -/** - @brief Operations which can be performed with action codes. - */ +/// Operations which can be performed with action codes. @objc(FIRActionCodeOperation) public enum ActionCodeOperation: Int, @unchecked Sendable { - /** Action code for unknown operation. */ + /// Action code for unknown operation. case unknown = 0 - /** Action code for password reset operation. */ + /// Action code for password reset operation. case passwordReset = 1 - /** Action code for verify email operation. */ + /// Action code for verify email operation. case verifyEmail = 2 - /** Action code for recover email operation. */ + /// Action code for recover email operation. case recoverEmail = 3 - /** Action code for email link operation. */ + /// Action code for email link operation. case emailLink = 4 - /** Action code for verifying and changing email */ + /// Action code for verifying and changing email. case verifyAndChangeEmail = 5 - /** Action code for reverting second factor addition */ + /// Action code for reverting second factor addition. case revertSecondFactorAddition = 6 } diff --git a/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeSettings.swift b/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeSettings.swift index 7c04d3a1ec8..f9cb95190d3 100644 --- a/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeSettings.swift +++ b/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeSettings.swift @@ -14,68 +14,49 @@ import Foundation -/** @class FIRActionCodeSettings - @brief Used to set and retrieve settings related to handling action codes. - */ +/// Used to set and retrieve settings related to handling action codes. @objc(FIRActionCodeSettings) open class ActionCodeSettings: NSObject { - /** @property URL - @brief This URL represents the state/Continue URL in the form of a universal link. - @remarks This URL can should be constructed as a universal link that would either directly open - the app where the action code would be handled or continue to the app after the action code - is handled by Firebase. - */ + /// This URL represents the state/Continue URL in the form of a universal link. + /// + /// This URL can should be constructed as a universal link that would either directly open + /// the app where the action code would be handled or continue to the app after the action code + /// is handled by Firebase. @objc(URL) open var url: URL? - /** @property handleCodeInApp - @brief Indicates whether the action code link will open the app directly or after being - redirected from a Firebase owned web widget. - */ + /// Indicates whether the action code link will open the app directly or after being + /// redirected from a Firebase owned web widget. @objc open var handleCodeInApp: Bool = false - /** @property iOSBundleID - @brief The iOS bundle ID, if available. The default value is the current app's bundle ID. - */ + /// The iOS bundle ID, if available. The default value is the current app's bundle ID. @objc open var iOSBundleID: String? - /** @property androidPackageName - @brief The Android package name, if available. - */ + /// The Android package name, if available. @objc open var androidPackageName: String? - /** @property androidMinimumVersion - @brief The minimum Android version supported, if available. - */ + /// The minimum Android version supported, if available. @objc open var androidMinimumVersion: String? - /** @property androidInstallIfNotAvailable - @brief Indicates whether the Android app should be installed on a device where it is not - available. - */ + /// Indicates whether the Android app should be installed on a device where it is not available. @objc open var androidInstallIfNotAvailable: Bool = false - /** @property dynamicLinkDomain - @brief The Firebase Dynamic Link domain used for out of band code flow. - */ + /// The Firebase Dynamic Link domain used for out of band code flow. @objc open var dynamicLinkDomain: String? - /** @fn - @brief Sets the iOS bundle Id. - */ - + /// Sets the iOS bundle ID. @objc override public init() { iOSBundleID = Bundle.main.bundleIdentifier } - /** @fn - @brief Sets the Android package name, the flag to indicate whether or not to install the app - and the minimum Android version supported. - @param androidPackageName The Android package name. - @param installIfNotAvailable Indicates whether or not the app should be installed if not - available. - @param minimumVersion The minimum version of Android supported. - @remarks If installIfNotAvailable is set to YES and the link is opened on an android device, it - will try to install the app if not already available. Otherwise the web URL is used. - */ + /// Sets the Android package name, the flag to indicate whether or not to install the app, + /// and the minimum Android version supported. + /// + /// If `installIfNotAvailable` is set to `true` and the link is opened on an android device, it + /// will try to install the app if not already available. Otherwise the web URL is used. + /// - Parameters: + /// - androidPackageName: The Android package name. + /// - installIfNotAvailable: Indicates whether or not the app should be installed if not + /// available. + /// - minimumVersion: The minimum version of Android supported. @objc open func setAndroidPackageName(_ androidPackageName: String, installIfNotAvailable: Bool, minimumVersion: String?) { @@ -84,6 +65,7 @@ import Foundation androidMinimumVersion = minimumVersion } + /// Sets the iOS bundle ID. open func setIOSBundleID(_ bundleID: String) { iOSBundleID = bundleID } diff --git a/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeURL.swift b/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeURL.swift index 48c99710c31..29bb8070c1a 100644 --- a/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeURL.swift +++ b/FirebaseAuth/Sources/Swift/ActionCode/ActionCodeURL.swift @@ -14,41 +14,29 @@ import Foundation -/** @class FIRActionCodeURL - @brief This class will allow developers to easily extract information about out of band links. - */ +/// This class will allow developers to easily extract information about out of band links. @objc(FIRActionCodeURL) open class ActionCodeURL: NSObject { - /** @property APIKey - @brief Returns the API key from the link. nil, if not provided. - */ + /// Returns the API key from the link. nil, if not provided. @objc(APIKey) public let apiKey: String? - /** @property operation - @brief Returns the mode of oob action. The property will be of `FIRActionCodeOperation` type. - It will return `FIRActionCodeOperationUnknown` if no oob action is provided. - */ + /// Returns the mode of oob action. + /// + /// The property will be of `ActionCodeOperation` type. + /// It will return `.unknown` if no oob action is provided. @objc public let operation: ActionCodeOperation - /** @property code - @brief Returns the email action code from the link. nil, if not provided. - */ + /// Returns the email action code from the link. nil, if not provided. @objc public let code: String? - /** @property continueURL - @brief Returns the continue URL from the link. nil, if not provided. - */ + /// Returns the continue URL from the link. nil, if not provided. @objc public let continueURL: URL? - /** @property languageCode - @brief Returns the language code from the link. nil, if not provided. - */ + /// Returns the language code from the link. nil, if not provided. @objc public let languageCode: String? - /** @fn actionCodeURLWithLink: - @brief Construct an `ActionCodeURL` from an out of band link (e.g. email link). - @param link The oob link string used to construct the action code URL. - @return The `ActionCodeURL` object constructed based on the oob link provided. - */ + /// Construct an `ActionCodeURL` from an out of band link (e.g. email link). + /// - Parameter link: The oob link string used to construct the action code URL. + /// - Returns: The ActionCodeURL object constructed based on the oob link provided. @objc(actionCodeURLWithLink:) public init?(link: String) { var queryItems = ActionCodeURL.parseURL(link) if queryItems.count == 0 { diff --git a/FirebaseAuth/Sources/Swift/Auth/Auth.swift b/FirebaseAuth/Sources/Swift/Auth/Auth.swift index 279ba10a416..8c9e92a7bd6 100644 --- a/FirebaseAuth/Sources/Swift/Auth/Auth.swift +++ b/FirebaseAuth/Sources/Swift/Auth/Auth.swift @@ -82,9 +82,12 @@ import FirebaseCoreExtension @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) extension Auth: AuthInterop { + /// Retrieves the Firebase authentication token, possibly refreshing it if it has expired. + /// + /// This method is not for public use. It is for Firebase clients of AuthInterop. @objc(getTokenForcingRefresh:withCallback:) - open func getToken(forcingRefresh forceRefresh: Bool, - completion callback: @escaping (String?, Error?) -> Void) { + public func getToken(forcingRefresh forceRefresh: Bool, + completion callback: @escaping (String?, Error?) -> Void) { kAuthGlobalWorkQueue.async { [weak self] in if let strongSelf = self { // Enable token auto-refresh if not already enabled. @@ -134,22 +137,22 @@ extension Auth: AuthInterop { } } + /// Get the current Auth user's UID. Returns nil if there is no user signed in. + /// + /// This method is not for public use. It is for Firebase clients of AuthInterop. open func getUserID() -> String? { return currentUser?.uid } } -/** @class Auth - @brief Manages authentication for Firebase apps. - @remarks This class is thread-safe. - */ +/// Manages authentication for Firebase apps. +/// +/// This class is thread-safe. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @objc(FIRAuth) open class Auth: NSObject { - /** @fn auth - @brief Gets the auth object for the default Firebase app. - @remarks The default Firebase app must have already been configured or an exception will be - raised. - */ + /// Gets the auth object for the default Firebase app. + /// + /// The default Firebase app must have already been configured or an exception will be raised. @objc open class func auth() -> Auth { guard let defaultApp = FirebaseApp.app() else { fatalError("The default FirebaseApp instance must be configured before the default Auth " + @@ -161,32 +164,25 @@ extension Auth: AuthInterop { return auth(app: defaultApp) } - /** @fn authWithApp: - @brief Gets the auth object for a `FirebaseApp`. - - @param app The app for which to retrieve the associated `Auth` instance. - @return The `Auth` instance associated with the given app. - */ + /// Gets the auth object for a `FirebaseApp`. + /// - Parameter app: The app for which to retrieve the associated `Auth` instance. + /// - Returns: The `Auth` instance associated with the given app. @objc open class func auth(app: FirebaseApp) -> Auth { - return ComponentType.instance(for: AuthProvider.self, in: app.container).auth() + return ComponentType.instance(for: AuthInterop.self, in: app.container) as! Auth } - /** @property app - @brief Gets the `FirebaseApp` object that this auth object is connected to. - */ + /// Gets the `FirebaseApp` object that this auth object is connected to. @objc public internal(set) weak var app: FirebaseApp? - /** @property currentUser - @brief Synchronously gets the cached current user, or null if there is none. - */ + /// Synchronously gets the cached current user, or null if there is none. @objc public internal(set) var currentUser: User? - /** @property languageCode - @brief The current user language code. This property can be set to the app's current language by - calling `useAppLanguage()`. - - @remarks The string used to set this property must be a language code that follows BCP 47. - */ + /// The current user language code. + /// + /// This property can be set to the app's current language by + /// calling `useAppLanguage()`. + /// + /// The string used to set this property must be a language code that follows BCP 47. @objc open var languageCode: String? { get { kAuthGlobalWorkQueue.sync { @@ -200,42 +196,34 @@ extension Auth: AuthInterop { } } - /** @property settings - @brief Contains settings related to the auth object. - */ + /// Contains settings related to the auth object. @NSCopying @objc open var settings: AuthSettings? - /** @property userAccessGroup - @brief The current user access group that the Auth instance is using. Default is nil. - */ + /// The current user access group that the Auth instance is using. + /// + /// Default is `nil`. @objc public internal(set) var userAccessGroup: String? - /** @property shareAuthStateAcrossDevices - @brief Contains shareAuthStateAcrossDevices setting related to the auth object. - @remarks If userAccessGroup is not set, setting shareAuthStateAcrossDevices will - have no effect. You should set shareAuthStateAcrossDevices to it's desired - state and then set the userAccessGroup after. - */ + /// Contains shareAuthStateAcrossDevices setting related to the auth object. + /// + /// If userAccessGroup is not set, setting shareAuthStateAcrossDevices will + /// have no effect. You should set shareAuthStateAcrossDevices to it's desired + /// state and then set the userAccessGroup after. @objc open var shareAuthStateAcrossDevices: Bool = false - /** @property tenantID - @brief The tenant ID of the auth instance. nil if none is available. - */ + /// The tenant ID of the auth instance. `nil` if none is available. @objc open var tenantID: String? - /** - * @property customAuthDomain - * @brief The custom authentication domain used to handle all sign-in redirects. End-users will see - * this domain when signing in. This domain must be allowlisted in the Firebase Console. - */ + /// The custom authentication domain used to handle all sign-in redirects. + /// End-users will see + /// this domain when signing in. This domain must be allowlisted in the Firebase Console. @objc open var customAuthDomain: String? - /** @fn updateCurrentUser:completion: - @brief Sets the `currentUser` on the receiver to the provided user object. - @param user The user object to be set as the current user of the calling Auth instance. - @param completion Optionally; a block invoked after the user of the calling Auth instance has - been updated or an error was encountered. - */ + /// Sets the `currentUser` on the receiver to the provided user object. + /// - Parameters: + /// - user: The user object to be set as the current user of the calling Auth instance. + /// - completion: Optionally; a block invoked after the user of the calling Auth instance has + /// been updated or an error was encountered. @objc open func updateCurrentUser(_ user: User?, completion: ((Error?) -> Void)? = nil) { kAuthGlobalWorkQueue.async { guard let user else { @@ -268,12 +256,10 @@ extension Auth: AuthInterop { } } - /** @fn updateCurrentUser:completion: - @brief Sets the `currentUser` on the receiver to the provided user object. - @param user The user object to be set as the current user of the calling Auth instance. - @param completion Optionally; a block invoked after the user of the calling Auth instance has - been updated or an error was encountered. - */ + /// Sets the `currentUser` on the receiver to the provided user object. + /// - Parameter user: The user object to be set as the current user of the calling Auth instance. + /// - Parameter completion: Optionally; a block invoked after the user of the calling Auth + /// instance has been updated or an error was encountered. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) open func updateCurrentUser(_ user: User) async throws { return try await withCheckedThrowingContinuation { continuation in @@ -287,22 +273,18 @@ extension Auth: AuthInterop { } } - /** @fn fetchSignInMethodsForEmail:completion: - @brief [Deprecated] Fetches the list of all sign-in methods previously used for the provided - email address. This method returns an empty list when [Email Enumeration - Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) - is enabled, irrespective of the number of authentication methods available for the given email. - @param email The email address for which to obtain a list of sign-in methods. - @param completion Optionally; a block which is invoked when the list of sign in methods for the - specified email address is ready or an error was encountered. Invoked asynchronously on the - main thread in the future. - - @remarks Possible error codes: - - + `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed. - - @remarks See @c AuthErrors for a list of error codes that are common to all API methods. - */ + /// [Deprecated] Fetches the list of all sign-in methods previously used for the provided + /// email address. This method returns an empty list when [Email Enumeration + /// Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) + /// is enabled, irrespective of the number of authentication methods available for the given + /// email. + /// + /// Possible error codes: `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed. + /// + /// - Parameter email: The email address for which to obtain a list of sign-in methods. + /// - Parameter completion: Optionally; a block which is invoked when the list of sign in methods + /// for the specified email address is ready or an error was encountered. Invoked asynchronously + /// on the main thread in the future. @available( *, deprecated, @@ -325,19 +307,16 @@ extension Auth: AuthInterop { } } - /** @fn fetchSignInMethodsForEmail:completion: - @brief [Deprecated] Fetches the list of all sign-in methods previously used for the provided - email address. This method returns an empty list when [Email Enumeration - Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) - is enabled, irrespective of the number of authentication methods available for the given email. - @param email The email address for which to obtain a list of sign-in methods. - - @remarks Possible error codes: - - + `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed. - - @remarks See @c AuthErrors for a list of error codes that are common to all API methods. - */ + /// [Deprecated] Fetches the list of all sign-in methods previously used for the provided + /// email address. This method returns an empty list when [Email Enumeration + /// Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) + /// is enabled, irrespective of the number of authentication methods available for the given + /// email. + /// + /// Possible error codes: `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed. + /// + /// - Parameter email: The email address for which to obtain a list of sign-in methods. + /// - Returns: List of sign-in methods @available( *, deprecated, @@ -355,29 +334,25 @@ extension Auth: AuthInterop { } } - /** @fn signInWithEmail:password:completion: - @brief Signs in using an email address and password. When [Email Enumeration - Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) - is enabled, this method fails with FIRAuthErrorCodeInvalidCredentials in case of an invalid - email/password. - - @param email The user's email address. - @param password The user's password. - @param completion Optionally; a block which is invoked when the sign in flow finishes, or is - canceled. Invoked asynchronously on the main thread in the future. - - @remarks Possible error codes: - - + `AuthErrorCodeOperationNotAllowed` - Indicates that email and password - accounts are not enabled. Enable them in the Auth section of the - Firebase console. - + `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled. - + `AuthErrorCodeWrongPassword` - Indicates the user attempted - sign in with an incorrect password. - + `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed. - - @remarks See `AuthErrors` for a list of error codes that are common to all API methods. - */ + /// Signs in using an email address and password. + /// + /// When [Email Enumeration + /// Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) + /// is enabled, this method fails with an error in case of an invalid + /// email/password. + /// + /// Possible error codes: + /// * `AuthErrorCodeOperationNotAllowed` - Indicates that email and password + /// accounts are not enabled. Enable them in the Auth section of the + /// Firebase console. + /// * `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled. + /// * `AuthErrorCodeWrongPassword` - Indicates the user attempted + /// sign in with an incorrect password. + /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed. + /// - Parameter email: The user's email address. + /// - Parameter password: The user's password. + /// - Parameter completion: Optionally; a block which is invoked when the sign in flow finishes, + /// or is canceled. Invoked asynchronously on the main thread in the future. @objc open func signIn(withEmail email: String, password: String, completion: ((AuthDataResult?, Error?) -> Void)? = nil) { @@ -397,18 +372,23 @@ extension Auth: AuthInterop { } } - /** @fn signInWithEmail:password:callback: - @brief Signs in using an email address and password. When [Email Enumeration - Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) - is enabled, this method fails with FIRAuthErrorCodeInvalidCredentials in case of an invalid - email/password. - @param email The user's email address. - @param password The user's password. - @param callback A block which is invoked when the sign in finishes (or is cancelled.) Invoked - asynchronously on the global auth work queue in the future. - @remarks This is the internal counterpart of this method, which uses a callback that does not - update the current user. - */ + /// Signs in using an email address and password. + /// + /// When [Email Enumeration + /// Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) + /// is enabled, this method throws in case of an invalid email/password. + /// + /// Possible error codes: + /// * `AuthErrorCodeOperationNotAllowed` - Indicates that email and password + /// accounts are not enabled. Enable them in the Auth section of the + /// Firebase console. + /// * `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled. + /// * `AuthErrorCodeWrongPassword` - Indicates the user attempted + /// sign in with an incorrect password. + /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed. + /// - Parameter email: The user's email address. + /// - Parameter password: The user's password. + /// - Returns: The signed in user. func internalSignInUser(withEmail email: String, password: String) async throws -> User { let request = VerifyPasswordRequest(email: email, @@ -431,24 +411,19 @@ extension Auth: AuthInterop { ) } - /** @fn signInWithEmail:password:completion: - @brief Signs in using an email address and password. - - @param email The user's email address. - @param password The user's password. - - @remarks Possible error codes: - - + `AuthErrorCodeOperationNotAllowed` - Indicates that email and password - accounts are not enabled. Enable them in the Auth section of the - Firebase console. - + `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled. - + `AuthErrorCodeWrongPassword` - Indicates the user attempted - sign in with an incorrect password. - + `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed. - - @remarks See `AuthErrors` for a list of error codes that are common to all API methods. - */ + /// Signs in using an email address and password. + /// + /// Possible error codes: + /// * `AuthErrorCodeOperationNotAllowed` - Indicates that email and password + /// accounts are not enabled. Enable them in the Auth section of the + /// Firebase console. + /// * `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled. + /// * `AuthErrorCodeWrongPassword` - Indicates the user attempted + /// sign in with an incorrect password. + /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed. + /// - Parameter email: The user's email address. + /// - Parameter password: The user's password. + /// - Returns: The `AuthDataResult` after a successful signin. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @discardableResult open func signIn(withEmail email: String, password: String) async throws -> AuthDataResult { @@ -463,24 +438,20 @@ extension Auth: AuthInterop { } } - /** @fn signInWithEmail:link:completion: - @brief Signs in using an email address and email sign-in link. - - @param email The user's email address. - @param link The email sign-in link. - @param completion Optionally; a block which is invoked when the sign in flow finishes, or is - canceled. Invoked asynchronously on the main thread in the future. - - @remarks Possible error codes: - - + `AuthErrorCodeOperationNotAllowed` - Indicates that email and email sign-in link - accounts are not enabled. Enable them in the Auth section of the - Firebase console. - + `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled. - + `AuthErrorCodeInvalidEmail` - Indicates the email address is invalid. - - @remarks See `AuthErrors` for a list of error codes that are common to all API methods. - */ + /// Signs in using an email address and email sign-in link. + /// + /// Possible error codes: + /// * `AuthErrorCodeOperationNotAllowed` - Indicates that email and password + /// accounts are not enabled. Enable them in the Auth section of the + /// Firebase console. + /// * `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled. + /// * `AuthErrorCodeWrongPassword` - Indicates the user attempted + /// sign in with an incorrect password. + /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed. + /// - Parameter email: The user's email address. + /// - Parameter link: The email sign-in link. + /// - Parameter completion: Optionally; a block which is invoked when the sign in flow finishes, + /// or is canceled. Invoked asynchronously on the main thread in the future. @objc open func signIn(withEmail email: String, link: String, completion: ((AuthDataResult?, Error?) -> Void)? = nil) { @@ -499,24 +470,18 @@ extension Auth: AuthInterop { } } - /** @fn signInWithEmail:link:completion: - @brief Signs in using an email address and email sign-in link. - - @param email The user's email address. - @param link The email sign-in link. - @param completion Optionally; a block which is invoked when the sign in flow finishes, or is - canceled. Invoked asynchronously on the main thread in the future. - - @remarks Possible error codes: - - + `AuthErrorCodeOperationNotAllowed` - Indicates that email and email sign-in link - accounts are not enabled. Enable them in the Auth section of the - Firebase console. - + `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled. - + `AuthErrorCodeInvalidEmail` - Indicates the email address is invalid. - - @remarks See `AuthErrors` for a list of error codes that are common to all API methods. - */ + /// Signs in using an email address and email sign-in link. + /// Possible error codes: + /// * `AuthErrorCodeOperationNotAllowed` - Indicates that email and password + /// accounts are not enabled. Enable them in the Auth section of the + /// Firebase console. + /// * `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled. + /// * `AuthErrorCodeWrongPassword` - Indicates the user attempted + /// sign in with an incorrect password. + /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed. + /// - Parameter email: The user's email address. + /// - Parameter link: The email sign-in link. + /// - Returns: The `AuthDataResult` after a successful signin. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) open func signIn(withEmail email: String, link: String) async throws -> AuthDataResult { return try await withCheckedThrowingContinuation { continuation in @@ -531,54 +496,40 @@ extension Auth: AuthInterop { } #if os(iOS) + /// Signs in using the provided auth provider instance. + /// + /// Possible error codes: + /// * `AuthErrorCodeOperationNotAllowed` - Indicates that email and password + /// accounts are not enabled. Enable them in the Auth section of the + /// Firebase console. + /// * `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled. + /// * `AuthErrorCodeWrongPassword` - Indicates the user attempted + /// sign in with an incorrect password. + /// * `AuthErrorCodeWebNetworkRequestFailed` - Indicates that a network request within a + /// SFSafariViewController or WKWebView failed. + /// * `AuthErrorCodeWebInternalError` - Indicates that an internal error occurred within a + /// SFSafariViewController or WKWebView. + /// * `AuthErrorCodeWebSignInUserInteractionFailure` - Indicates a general failure during + /// a web sign-in flow. + /// * `AuthErrorCodeWebContextAlreadyPresented` - Indicates that an attempt was made to + /// present a new web context while one was already being presented. + /// * `AuthErrorCodeWebContextCancelled` - Indicates that the URL presentation was + /// cancelled prematurely by the user. + /// * `AuthErrorCodeAccountExistsWithDifferentCredential` - Indicates the email asserted + /// by the credential (e.g. the email in a Facebook access token) is already in use by an + /// existing account, that cannot be authenticated with this sign-in method. Call + /// fetchProvidersForEmail for this user’s email and then prompt them to sign in with any of + /// the sign-in providers returned. This error will only be thrown if the "One account per + /// email address" setting is enabled in the Firebase console, under Auth settings. + /// - Parameter provider: An instance of an auth provider used to initiate the sign-in flow. + /// - Parameter uiDelegate: Optionally an instance of a class conforming to the AuthUIDelegate + /// protocol, this is used for presenting the web context. If nil, a default AuthUIDelegate + /// will be used. + /// - Parameter completion: Optionally; a block which is invoked when the sign in flow finishes, + /// or is canceled. Invoked asynchronously on the main thread in the future. @available(tvOS, unavailable) @available(macOS, unavailable) @available(watchOS, unavailable) - /** @fn signInWithProvider:UIDelegate:completion: - @brief Signs in using the provided auth provider instance. - This method is available on iOS, macOS Catalyst, and tvOS only. - - @param provider An instance of an auth provider used to initiate the sign-in flow. - @param uiDelegate Optionally an instance of a class conforming to the AuthUIDelegate - protocol, this is used for presenting the web context. If nil, a default AuthUIDelegate - will be used. - @param completion Optionally; a block which is invoked when the sign in flow finishes, or is - canceled. Invoked asynchronously on the main thread in the future. - - @remarks Possible error codes: -
    -
  • @c AuthErrorCodeOperationNotAllowed - Indicates that email and password - accounts are not enabled. Enable them in the Auth section of the - Firebase console. -
  • -
  • @c AuthErrorCodeUserDisabled - Indicates the user's account is disabled. -
  • -
  • @c AuthErrorCodeWebNetworkRequestFailed - Indicates that a network request within a - SFSafariViewController or WKWebView failed. -
  • -
  • @c AuthErrorCodeWebInternalError - Indicates that an internal error occurred within a - SFSafariViewController or WKWebView. -
  • -
  • @c AuthErrorCodeWebSignInUserInteractionFailure - Indicates a general failure during - a web sign-in flow. -
  • -
  • @c AuthErrorCodeWebContextAlreadyPresented - Indicates that an attempt was made to - present a new web context while one was already being presented. -
  • -
  • @c AuthErrorCodeWebContextCancelled - Indicates that the URL presentation was - cancelled prematurely by the user. -
  • -
  • @c AuthErrorCodeAccountExistsWithDifferentCredential - Indicates the email asserted - by the credential (e.g. the email in a Facebook access token) is already in use by an - existing account, that cannot be authenticated with this sign-in method. Call - fetchProvidersForEmail for this user’s email and then prompt them to sign in with any of - the sign-in providers returned. This error will only be thrown if the "One account per - email address" setting is enabled in the Firebase console, under Auth settings. -
  • -
- - @remarks See @c AuthErrors for a list of error codes that are common to all API methods. - */ @objc(signInWithProvider:UIDelegate:completion:) open func signIn(with provider: FederatedAuthProvider, uiDelegate: AuthUIDelegate?, @@ -600,49 +551,36 @@ extension Auth: AuthInterop { } } - /** @fn signInWithProvider:UIDelegate:completion: - @brief Signs in using the provided auth provider instance. - This method is available on iOS, macOS Catalyst, and tvOS only. - - @param provider An instance of an auth provider used to initiate the sign-in flow. - @param uiDelegate Optionally an instance of a class conforming to the AuthUIDelegate - protocol, this is used for presenting the web context. If nil, a default AuthUIDelegate - will be used. - - @remarks Possible error codes: -
    -
  • @c AuthErrorCodeOperationNotAllowed - Indicates that email and password - accounts are not enabled. Enable them in the Auth section of the - Firebase console. -
  • -
  • @c AuthErrorCodeUserDisabled - Indicates the user's account is disabled. -
  • -
  • @c AuthErrorCodeWebNetworkRequestFailed - Indicates that a network request within a - SFSafariViewController or WKWebView failed. -
  • -
  • @c AuthErrorCodeWebInternalError - Indicates that an internal error occurred within a - SFSafariViewController or WKWebView. -
  • -
  • @c AuthErrorCodeWebSignInUserInteractionFailure - Indicates a general failure during - a web sign-in flow. -
  • -
  • @c AuthErrorCodeWebContextAlreadyPresented - Indicates that an attempt was made to - present a new web context while one was already being presented. -
  • -
  • @c AuthErrorCodeWebContextCancelled - Indicates that the URL presentation was - cancelled prematurely by the user. -
  • -
  • @c AuthErrorCodeAccountExistsWithDifferentCredential - Indicates the email asserted - by the credential (e.g. the email in a Facebook access token) is already in use by an - existing account, that cannot be authenticated with this sign-in method. Call - fetchProvidersForEmail for this user’s email and then prompt them to sign in with any of - the sign-in providers returned. This error will only be thrown if the "One account per - email address" setting is enabled in the Firebase console, under Auth settings. -
  • -
- - @remarks See @c AuthErrors for a list of error codes that are common to all API methods. - */ + /// Signs in using the provided auth provider instance. + /// + /// Possible error codes: + /// * `AuthErrorCodeOperationNotAllowed` - Indicates that email and password + /// accounts are not enabled. Enable them in the Auth section of the + /// Firebase console. + /// * `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled. + /// * `AuthErrorCodeWrongPassword` - Indicates the user attempted + /// sign in with an incorrect password. + /// * `AuthErrorCodeWebNetworkRequestFailed` - Indicates that a network request within a + /// SFSafariViewController or WKWebView failed. + /// * `AuthErrorCodeWebInternalError` - Indicates that an internal error occurred within a + /// SFSafariViewController or WKWebView. + /// * `AuthErrorCodeWebSignInUserInteractionFailure` - Indicates a general failure during + /// a web sign-in flow. + /// * `AuthErrorCodeWebContextAlreadyPresented` - Indicates that an attempt was made to + /// present a new web context while one was already being presented. + /// * `AuthErrorCodeWebContextCancelled` - Indicates that the URL presentation was + /// cancelled prematurely by the user. + /// * `AuthErrorCodeAccountExistsWithDifferentCredential` - Indicates the email asserted + /// by the credential (e.g. the email in a Facebook access token) is already in use by an + /// existing account, that cannot be authenticated with this sign-in method. Call + /// fetchProvidersForEmail for this user’s email and then prompt them to sign in with any of + /// the sign-in providers returned. This error will only be thrown if the "One account per + /// email address" setting is enabled in the Firebase console, under Auth settings. + /// - Parameter provider: An instance of an auth provider used to initiate the sign-in flow. + /// - Parameter uiDelegate: Optionally an instance of a class conforming to the AuthUIDelegate + /// protocol, this is used for presenting the web context. If nil, a default AuthUIDelegate + /// will be used. + /// - Returns: The `AuthDataResult` after the successful signin. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @available(tvOS, unavailable) @available(macOS, unavailable) @@ -662,44 +600,38 @@ extension Auth: AuthInterop { } #endif // iOS - /** @fn signInWithCredential:completion: - @brief Asynchronously signs in to Firebase with the given 3rd-party credentials (e.g. a Facebook - login Access Token, a Google ID Token/Access Token pair, etc.) and returns additional - identity provider data. - - @param credential The credential supplied by the IdP. - @param completion Optionally; a block which is invoked when the sign in flow finishes, or is - canceled. Invoked asynchronously on the main thread in the future. - - @remarks Possible error codes: - - + `AuthErrorCodeInvalidCredential` - Indicates the supplied credential is invalid. - This could happen if it has expired or it is malformed. - + `AuthErrorCodeOperationNotAllowed` - Indicates that accounts - with the identity provider represented by the credential are not enabled. - Enable them in the Auth section of the Firebase console. - + `AuthErrorCodeAccountExistsWithDifferentCredential` - Indicates the email asserted - by the credential (e.g. the email in a Facebook access token) is already in use by an - existing account, that cannot be authenticated with this sign-in method. Call - fetchProvidersForEmail for this user’s email and then prompt them to sign in with any of - the sign-in providers returned. This error will only be thrown if the "One account per - email address" setting is enabled in the Firebase console, under Auth settings. - + `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled. - + `AuthErrorCodeWrongPassword` - Indicates the user attempted sign in with an - incorrect password, if credential is of the type EmailPasswordAuthCredential. - + `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed. - + `AuthErrorCodeMissingVerificationID` - Indicates that the phone auth credential was - created with an empty verification ID. - + `AuthErrorCodeMissingVerificationCode` - Indicates that the phone auth credential - was created with an empty verification code. - + `AuthErrorCodeInvalidVerificationCode` - Indicates that the phone auth credential - was created with an invalid verification Code. - + `AuthErrorCodeInvalidVerificationID` - Indicates that the phone auth credential was - created with an invalid verification ID. - + `AuthErrorCodeSessionExpired` - Indicates that the SMS code has expired. - - @remarks See `AuthErrors` for a list of error codes that are common to all API methods - */ + /// Asynchronously signs in to Firebase with the given 3rd-party credentials (e.g. a Facebook + /// login Access Token, a Google ID Token/Access Token pair, etc.) and returns additional + /// identity provider data. + /// + /// Possible error codes: + /// * `AuthErrorCodeInvalidCredential` - Indicates the supplied credential is invalid. + /// This could happen if it has expired or it is malformed. + /// * `AuthErrorCodeOperationNotAllowed` - Indicates that accounts + /// with the identity provider represented by the credential are not enabled. + /// Enable them in the Auth section of the Firebase console. + /// * `AuthErrorCodeAccountExistsWithDifferentCredential` - Indicates the email asserted + /// by the credential (e.g. the email in a Facebook access token) is already in use by an + /// existing account, that cannot be authenticated with this sign-in method. Call + /// fetchProvidersForEmail for this user’s email and then prompt them to sign in with any of + /// the sign-in providers returned. This error will only be thrown if the "One account per + /// email address" setting is enabled in the Firebase console, under Auth settings. + /// * `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled. + /// * `AuthErrorCodeWrongPassword` - Indicates the user attempted sign in with an + /// incorrect password, if credential is of the type EmailPasswordAuthCredential. + /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed. + /// * `AuthErrorCodeMissingVerificationID` - Indicates that the phone auth credential was + /// created with an empty verification ID. + /// * `AuthErrorCodeMissingVerificationCode` - Indicates that the phone auth credential + /// was created with an empty verification code. + /// * `AuthErrorCodeInvalidVerificationCode` - Indicates that the phone auth credential + /// was created with an invalid verification Code. + /// * `AuthErrorCodeInvalidVerificationID` - Indicates that the phone auth credential was + /// created with an invalid verification ID. + /// * `AuthErrorCodeSessionExpired` - Indicates that the SMS code has expired. + /// - Parameter credential: The credential supplied by the IdP. + /// - Parameter completion: Optionally; a block which is invoked when the sign in flow finishes, + /// or is canceled. Invoked asynchronously on the main thread in the future. @objc(signInWithCredential:completion:) open func signIn(with credential: AuthCredential, completion: ((AuthDataResult?, Error?) -> Void)? = nil) { @@ -717,44 +649,37 @@ extension Auth: AuthInterop { } } - /** @fn signInWithCredential:completion: - @brief Asynchronously signs in to Firebase with the given 3rd-party credentials (e.g. a Facebook - login Access Token, a Google ID Token/Access Token pair, etc.) and returns additional - identity provider data. - - @param credential The credential supplied by the IdP. - @param completion Optionally; a block which is invoked when the sign in flow finishes, or is - canceled. Invoked asynchronously on the main thread in the future. - - @remarks Possible error codes: - - + `AuthErrorCodeInvalidCredential` - Indicates the supplied credential is invalid. - This could happen if it has expired or it is malformed. - + `AuthErrorCodeOperationNotAllowed` - Indicates that accounts - with the identity provider represented by the credential are not enabled. - Enable them in the Auth section of the Firebase console. - + `AuthErrorCodeAccountExistsWithDifferentCredential` - Indicates the email asserted - by the credential (e.g. the email in a Facebook access token) is already in use by an - existing account, that cannot be authenticated with this sign-in method. Call - fetchProvidersForEmail for this user’s email and then prompt them to sign in with any of - the sign-in providers returned. This error will only be thrown if the "One account per - email address" setting is enabled in the Firebase console, under Auth settings. - + `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled. - + `AuthErrorCodeWrongPassword` - Indicates the user attempted sign in with an - incorrect password, if credential is of the type EmailPasswordAuthCredential. - + `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed. - + `AuthErrorCodeMissingVerificationID` - Indicates that the phone auth credential was - created with an empty verification ID. - + `AuthErrorCodeMissingVerificationCode` - Indicates that the phone auth credential - was created with an empty verification code. - + `AuthErrorCodeInvalidVerificationCode` - Indicates that the phone auth credential - was created with an invalid verification Code. - + `AuthErrorCodeInvalidVerificationID` - Indicates that the phone auth credential was - created with an invalid verification ID. - + `AuthErrorCodeSessionExpired` - Indicates that the SMS code has expired. - - @remarks See `AuthErrors` for a list of error codes that are common to all API methods - */ + /// Asynchronously signs in to Firebase with the given 3rd-party credentials (e.g. a Facebook + /// login Access Token, a Google ID Token/Access Token pair, etc.) and returns additional + /// identity provider data. + /// + /// Possible error codes: + /// * `AuthErrorCodeInvalidCredential` - Indicates the supplied credential is invalid. + /// This could happen if it has expired or it is malformed. + /// * `AuthErrorCodeOperationNotAllowed` - Indicates that accounts + /// with the identity provider represented by the credential are not enabled. + /// Enable them in the Auth section of the Firebase console. + /// * `AuthErrorCodeAccountExistsWithDifferentCredential` - Indicates the email asserted + /// by the credential (e.g. the email in a Facebook access token) is already in use by an + /// existing account, that cannot be authenticated with this sign-in method. Call + /// fetchProvidersForEmail for this user’s email and then prompt them to sign in with any of + /// the sign-in providers returned. This error will only be thrown if the "One account per + /// email address" setting is enabled in the Firebase console, under Auth settings. + /// * `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled. + /// * `AuthErrorCodeWrongPassword` - Indicates the user attempted sign in with an + /// incorrect password, if credential is of the type EmailPasswordAuthCredential. + /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed. + /// * `AuthErrorCodeMissingVerificationID` - Indicates that the phone auth credential was + /// created with an empty verification ID. + /// * `AuthErrorCodeMissingVerificationCode` - Indicates that the phone auth credential + /// was created with an empty verification code. + /// * `AuthErrorCodeInvalidVerificationCode` - Indicates that the phone auth credential + /// was created with an invalid verification Code. + /// * `AuthErrorCodeInvalidVerificationID` - Indicates that the phone auth credential was + /// created with an invalid verification ID. + /// * `AuthErrorCodeSessionExpired` - Indicates that the SMS code has expired. + /// - Parameter credential: The credential supplied by the IdP. + /// - Returns: The `AuthDataResult` after the successful signin. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @discardableResult open func signIn(with credential: AuthCredential) async throws -> AuthDataResult { @@ -769,21 +694,16 @@ extension Auth: AuthInterop { } } - /** @fn signInAnonymouslyWithCompletion: - @brief Asynchronously creates and becomes an anonymous user. - @param completion Optionally; a block which is invoked when the sign in finishes, or is - canceled. Invoked asynchronously on the main thread in the future. - - @remarks If there is already an anonymous user signed in, that user will be returned instead. - If there is any other existing user signed in, that user will be signed out. - - @remarks Possible error codes: - - + `AuthErrorCodeOperationNotAllowed` - Indicates that anonymous accounts are - not enabled. Enable them in the Auth section of the Firebase console. - - @remarks See `AuthErrors` for a list of error codes that are common to all API methods. - */ + /// Asynchronously creates and becomes an anonymous user. + /// + /// If there is already an anonymous user signed in, that user will be returned instead. + /// If there is any other existing user signed in, that user will be signed out. + /// + /// Possible error codes: + /// * `AuthErrorCodeOperationNotAllowed` - Indicates that anonymous accounts are + /// not enabled. Enable them in the Auth section of the Firebase console. + /// - Parameter completion: Optionally; a block which is invoked when the sign in finishes, or is + /// canceled. Invoked asynchronously on the main thread in the future. @objc open func signInAnonymously(completion: ((AuthDataResult?, Error?) -> Void)? = nil) { kAuthGlobalWorkQueue.async { let decoratedCallback = self.signInFlowAuthDataResultCallback(byDecorating: completion) @@ -816,19 +736,15 @@ extension Auth: AuthInterop { } } - /** @fn signInAnonymouslyWithCompletion: - @brief Asynchronously creates and becomes an anonymous user. - - @remarks If there is already an anonymous user signed in, that user will be returned instead. - If there is any other existing user signed in, that user will be signed out. - - @remarks Possible error codes: - - + `AuthErrorCodeOperationNotAllowed` - Indicates that anonymous accounts are - not enabled. Enable them in the Auth section of the Firebase console. - - @remarks See `AuthErrors` for a list of error codes that are common to all API methods. - */ + /// Asynchronously creates and becomes an anonymous user. + /// + /// If there is already an anonymous user signed in, that user will be returned instead. + /// If there is any other existing user signed in, that user will be signed out. + /// + /// Possible error codes: + /// * `AuthErrorCodeOperationNotAllowed` - Indicates that anonymous accounts are + /// not enabled. Enable them in the Auth section of the Firebase console. + /// - Returns: The `AuthDataResult` after the successful signin. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @discardableResult @objc open func signInAnonymously() async throws -> AuthDataResult { @@ -843,22 +759,16 @@ extension Auth: AuthInterop { } } - /** @fn signInWithCustomToken:completion: - @brief Asynchronously signs in to Firebase with the given Auth token. - - @param token A self-signed custom auth token. - @param completion Optionally; a block which is invoked when the sign in finishes, or is - canceled. Invoked asynchronously on the main thread in the future. - - @remarks Possible error codes: - - + `AuthErrorCodeInvalidCustomToken` - Indicates a validation error with - the custom token. - + `AuthErrorCodeCustomTokenMismatch` - Indicates the service account and the API key - belong to different projects. - - @remarks See `AuthErrors` for a list of error codes that are common to all API methods. - */ + /// Asynchronously signs in to Firebase with the given Auth token. + /// + /// Possible error codes: + /// * `AuthErrorCodeInvalidCustomToken` - Indicates a validation error with + /// the custom token. + /// * `AuthErrorCodeCustomTokenMismatch` - Indicates the service account and the API key + /// belong to different projects. + /// - Parameter token: A self-signed custom auth token. + /// - Parameter completion: Optionally; a block which is invoked when the sign in finishes, or is + /// canceled. Invoked asynchronously on the main thread in the future. @objc open func signIn(withCustomToken token: String, completion: ((AuthDataResult?, Error?) -> Void)? = nil) { kAuthGlobalWorkQueue.async { @@ -888,22 +798,15 @@ extension Auth: AuthInterop { } } - /** @fn signInWithCustomToken:completion: - @brief Asynchronously signs in to Firebase with the given Auth token. - - @param token A self-signed custom auth token. - @param completion Optionally; a block which is invoked when the sign in finishes, or is - canceled. Invoked asynchronously on the main thread in the future. - - @remarks Possible error codes: - - + `AuthErrorCodeInvalidCustomToken` - Indicates a validation error with - the custom token. - + `AuthErrorCodeCustomTokenMismatch` - Indicates the service account and the API key - belong to different projects. - - @remarks See `AuthErrors` for a list of error codes that are common to all API methods. - */ + /// Asynchronously signs in to Firebase with the given Auth token. + /// + /// Possible error codes: + /// * `AuthErrorCodeInvalidCustomToken` - Indicates a validation error with + /// the custom token. + /// * `AuthErrorCodeCustomTokenMismatch` - Indicates the service account and the API key + /// belong to different projects. + /// - Parameter token: A self-signed custom auth token. + /// - Returns: The `AuthDataResult` after the successful signin. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @discardableResult open func signIn(withCustomToken token: String) async throws -> AuthDataResult { @@ -918,28 +821,22 @@ extension Auth: AuthInterop { } } - /** @fn createUserWithEmail:password:completion: - @brief Creates and, on success, signs in a user with the given email address and password. - - @param email The user's email address. - @param password The user's desired password. - @param completion Optionally; a block which is invoked when the sign up flow finishes, or is - canceled. Invoked asynchronously on the main thread in the future. - - @remarks Possible error codes: - - + `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed. - + `AuthErrorCodeEmailAlreadyInUse` - Indicates the email used to attempt sign up - already exists. Call fetchProvidersForEmail to check which sign-in mechanisms the user - used, and prompt the user to sign in with one of those. - + `AuthErrorCodeOperationNotAllowed` - Indicates that email and password accounts - are not enabled. Enable them in the Auth section of the Firebase console. - + `AuthErrorCodeWeakPassword` - Indicates an attempt to set a password that is - considered too weak. The NSLocalizedFailureReasonErrorKey field in the NSError.userInfo - dictionary object will contain more detailed explanation that can be shown to the user. - - @remarks See `AuthErrors` for a list of error codes that are common to all API methods. - */ + /// Creates and, on success, signs in a user with the given email address and password. + /// + /// Possible error codes: + /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed. + /// * `AuthErrorCodeEmailAlreadyInUse` - Indicates the email used to attempt sign up + /// already exists. Call fetchProvidersForEmail to check which sign-in mechanisms the user + /// used, and prompt the user to sign in with one of those. + /// * `AuthErrorCodeOperationNotAllowed` - Indicates that email and password accounts + /// are not enabled. Enable them in the Auth section of the Firebase console. + /// * `AuthErrorCodeWeakPassword` - Indicates an attempt to set a password that is + /// considered too weak. The NSLocalizedFailureReasonErrorKey field in the NSError.userInfo + /// dictionary object will contain more detailed explanation that can be shown to the user. + /// - Parameter email: The user's email address. + /// - Parameter password: The user's desired password. + /// - Parameter completion: Optionally; a block which is invoked when the sign up flow finishes, + /// or is canceled. Invoked asynchronously on the main thread in the future. @objc open func createUser(withEmail email: String, password: String, completion: ((AuthDataResult?, Error?) -> Void)? = nil) { @@ -1011,28 +908,21 @@ extension Auth: AuthInterop { } } - /** @fn createUserWithEmail:password:completion: - @brief Creates and, on success, signs in a user with the given email address and password. - - @param email The user's email address. - @param password The user's desired password. - @param completion Optionally; a block which is invoked when the sign up flow finishes, or is - canceled. Invoked asynchronously on the main thread in the future. - - @remarks Possible error codes: - - + `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed. - + `AuthErrorCodeEmailAlreadyInUse` - Indicates the email used to attempt sign up - already exists. Call fetchProvidersForEmail to check which sign-in mechanisms the user - used, and prompt the user to sign in with one of those. - + `AuthErrorCodeOperationNotAllowed` - Indicates that email and password accounts - are not enabled. Enable them in the Auth section of the Firebase console. - + `AuthErrorCodeWeakPassword` - Indicates an attempt to set a password that is - considered too weak. The NSLocalizedFailureReasonErrorKey field in the NSError.userInfo - dictionary object will contain more detailed explanation that can be shown to the user. - - @remarks See `AuthErrors` for a list of error codes that are common to all API methods. - */ + /// Creates and, on success, signs in a user with the given email address and password. + /// + /// Possible error codes: + /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed. + /// * `AuthErrorCodeEmailAlreadyInUse` - Indicates the email used to attempt sign up + /// already exists. Call fetchProvidersForEmail to check which sign-in mechanisms the user + /// used, and prompt the user to sign in with one of those. + /// * `AuthErrorCodeOperationNotAllowed` - Indicates that email and password accounts + /// are not enabled. Enable them in the Auth section of the Firebase console. + /// * `AuthErrorCodeWeakPassword` - Indicates an attempt to set a password that is + /// considered too weak. The NSLocalizedFailureReasonErrorKey field in the NSError.userInfo + /// dictionary object will contain more detailed explanation that can be shown to the user. + /// - Parameter email: The user's email address. + /// - Parameter password: The user's desired password. + /// - Returns: The `AuthDataResult` after the successful signin. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @discardableResult open func createUser(withEmail email: String, password: String) async throws -> AuthDataResult { @@ -1047,25 +937,20 @@ extension Auth: AuthInterop { } } - /** @fn confirmPasswordResetWithCode:newPassword:completion: - @brief Resets the password given a code sent to the user outside of the app and a new password - for the user. - - @param newPassword The new password. - @param completion Optionally; a block which is invoked when the request finishes. Invoked - asynchronously on the main thread in the future. - - @remarks Possible error codes: - - + `AuthErrorCodeWeakPassword` - Indicates an attempt to set a password that is - considered too weak. - + `AuthErrorCodeOperationNotAllowed` - Indicates the administrator disabled sign - in with the specified identity provider. - + `AuthErrorCodeExpiredActionCode` - Indicates the OOB code is expired. - + `AuthErrorCodeInvalidActionCode` - Indicates the OOB code is invalid. - - @remarks See `AuthErrors` for a list of error codes that are common to all API methods. - */ + /// Resets the password given a code sent to the user outside of the app and a new password + /// for the user. + /// + /// Possible error codes: + /// * `AuthErrorCodeWeakPassword` - Indicates an attempt to set a password that is + /// considered too weak. + /// * `AuthErrorCodeOperationNotAllowed` - Indicates the administrator disabled sign + /// in with the specified identity provider. + /// * `AuthErrorCodeExpiredActionCode` - Indicates the OOB code is expired. + /// * `AuthErrorCodeInvalidActionCode` - Indicates the OOB code is invalid. + /// - Parameter code: The reset code. + /// - Parameter newPassword: The new password. + /// - Parameter completion: Optionally; a block which is invoked when the request finishes. + /// Invoked asynchronously on the main thread in the future. @objc open func confirmPasswordReset(withCode code: String, newPassword: String, completion: @escaping (Error?) -> Void) { kAuthGlobalWorkQueue.async { @@ -1076,25 +961,18 @@ extension Auth: AuthInterop { } } - /** @fn confirmPasswordResetWithCode:newPassword:completion: - @brief Resets the password given a code sent to the user outside of the app and a new password - for the user. - - @param newPassword The new password. - @param completion Optionally; a block which is invoked when the request finishes. Invoked - asynchronously on the main thread in the future. - - @remarks Possible error codes: - - + `AuthErrorCodeWeakPassword` - Indicates an attempt to set a password that is - considered too weak. - + `AuthErrorCodeOperationNotAllowed` - Indicates the administrator disabled sign - in with the specified identity provider. - + `AuthErrorCodeExpiredActionCode` - Indicates the OOB code is expired. - + `AuthErrorCodeInvalidActionCode` - Indicates the OOB code is invalid. - - @remarks See `AuthErrors` for a list of error codes that are common to all API methods. - */ + /// Resets the password given a code sent to the user outside of the app and a new password + /// for the user. + /// + /// Possible error codes: + /// * `AuthErrorCodeWeakPassword` - Indicates an attempt to set a password that is + /// considered too weak. + /// * `AuthErrorCodeOperationNotAllowed` - Indicates the administrator disabled sign + /// in with the specified identity provider. + /// * `AuthErrorCodeExpiredActionCode` - Indicates the OOB code is expired. + /// * `AuthErrorCodeInvalidActionCode` - Indicates the OOB code is invalid. + /// - Parameter code: The reset code. + /// - Parameter newPassword: The new password. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) open func confirmPasswordReset(withCode code: String, newPassword: String) async throws { return try await withCheckedThrowingContinuation { continuation in @@ -1108,13 +986,11 @@ extension Auth: AuthInterop { } } - /** @fn checkActionCode:completion: - @brief Checks the validity of an out of band code. - - @param code The out of band code to check validity. - @param completion Optionally; a block which is invoked when the request finishes. Invoked - asynchronously on the main thread in the future. - */ + /// Checks the validity of an out of band code. + /// - Parameter code: The out of band code to check validity. + /// - Parameter completion: Optionally; a block which is invoked when the request finishes. + /// Invoked + /// asynchronously on the main thread in the future. @objc open func checkActionCode(_ code: String, completion: @escaping (ActionCodeInfo?, Error?) -> Void) { kAuthGlobalWorkQueue.async { @@ -1140,13 +1016,9 @@ extension Auth: AuthInterop { } } - /** @fn checkActionCode:completion: - @brief Checks the validity of an out of band code. - - @param code The out of band code to check validity. - @param completion Optionally; a block which is invoked when the request finishes. Invoked - asynchronously on the main thread in the future. - */ + /// Checks the validity of an out of band code. + /// - Parameter code: The out of band code to check validity. + /// - Returns: An `ActionCodeInfo`. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) open func checkActionCode(_ code: String) async throws -> ActionCodeInfo { return try await withCheckedThrowingContinuation { continuation in @@ -1160,13 +1032,10 @@ extension Auth: AuthInterop { } } - /** @fn verifyPasswordResetCode:completion: - @brief Checks the validity of a verify password reset code. - - @param code The password reset code to be verified. - @param completion Optionally; a block which is invoked when the request finishes. Invoked - asynchronously on the main thread in the future. - */ + /// Checks the validity of a verify password reset code. + /// - Parameter code: The password reset code to be verified. + /// - Parameter completion: Optionally; a block which is invoked when the request finishes. + /// Invoked asynchronously on the main thread in the future. @objc open func verifyPasswordResetCode(_ code: String, completion: @escaping (String?, Error?) -> Void) { checkActionCode(code) { info, error in @@ -1178,13 +1047,9 @@ extension Auth: AuthInterop { } } - /** @fn verifyPasswordResetCode:completion: - @brief Checks the validity of a verify password reset code. - - @param code The password reset code to be verified. - @param completion Optionally; a block which is invoked when the request finishes. Invoked - asynchronously on the main thread in the future. - */ + /// Checks the validity of a verify password reset code. + /// - Parameter code: The password reset code to be verified. + /// - Returns: An email. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) open func verifyPasswordResetCode(_ code: String) async throws -> String { return try await withCheckedThrowingContinuation { continuation in @@ -1198,16 +1063,13 @@ extension Auth: AuthInterop { } } - /** @fn applyActionCode:completion: - @brief Applies out of band code. - - @param code The out of band code to be applied. - @param completion Optionally; a block which is invoked when the request finishes. Invoked - asynchronously on the main thread in the future. - - @remarks This method will not work for out of band codes which require an additional parameter, - such as password reset code. - */ + /// Applies out of band code. + /// + /// This method will not work for out of band codes which require an additional parameter, + /// such as password reset code. + /// - Parameter code: The out of band code to be applied. + /// - Parameter completion: Optionally; a block which is invoked when the request finishes. + /// Invoked asynchronously on the main thread in the future. @objc open func applyActionCode(_ code: String, completion: @escaping (Error?) -> Void) { kAuthGlobalWorkQueue.async { let request = SetAccountInfoRequest(requestConfiguration: self.requestConfiguration) @@ -1216,16 +1078,11 @@ extension Auth: AuthInterop { } } - /** @fn applyActionCode:completion: - @brief Applies out of band code. - - @param code The out of band code to be applied. - @param completion Optionally; a block which is invoked when the request finishes. Invoked - asynchronously on the main thread in the future. - - @remarks This method will not work for out of band codes which require an additional parameter, - such as password reset code. - */ + /// Applies out of band code. + /// + /// This method will not work for out of band codes which require an additional parameter, + /// such as password reset code. + /// - Parameter code: The out of band code to be applied. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) open func applyActionCode(_ code: String) async throws { return try await withCheckedThrowingContinuation { continuation in @@ -1239,58 +1096,56 @@ extension Auth: AuthInterop { } } - /** @fn sendPasswordResetWithEmail:completion: - @brief Initiates a password reset for the given email address. This method does not throw an - error when there's no user account with the given email address and [Email Enumeration - Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) - is enabled. - - @param email The email address of the user. - @param completion Optionally; a block which is invoked when the request finishes. Invoked - asynchronously on the main thread in the future. - - @remarks Possible error codes: - - + `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was - sent in the request. - + `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in - the console for this action. - + `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for - sending update email. - - */ + /// Initiates a password reset for the given email address. + /// + /// This method does not throw an + /// error when there's no user account with the given email address and [Email Enumeration + /// Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) + /// is enabled. + /// + /// Possible error codes: + /// * `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was + /// sent in the request. + /// * `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in + /// the console for this action. + /// * `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for + /// sending update email. + /// - Parameter email: The email address of the user. + /// - Parameter completion: Optionally; a block which is invoked when the request finishes. + /// Invoked + /// asynchronously on the main thread in the future. @objc open func sendPasswordReset(withEmail email: String, completion: ((Error?) -> Void)? = nil) { sendPasswordReset(withEmail: email, actionCodeSettings: nil, completion: completion) } - /** @fn sendPasswordResetWithEmail:actionCodeSetting:completion: - @brief Initiates a password reset for the given email address and `ActionCodeSettings` object. - - @param email The email address of the user. - @param actionCodeSettings An `ActionCodeSettings` object containing settings related to - handling action codes. - @param completion Optionally; a block which is invoked when the request finishes. Invoked - asynchronously on the main thread in the future. - - @remarks Possible error codes: - - + `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was - sent in the request. - + `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in - the console for this action. - + `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for - sending update email. - + `AuthErrorCodeMissingIosBundleID` - Indicates that the iOS bundle ID is missing when - `handleCodeInApp` is set to true. - + `AuthErrorCodeMissingAndroidPackageName` - Indicates that the android package name - is missing when the `androidInstallApp` flag is set to true. - + `AuthErrorCodeUnauthorizedDomain` - Indicates that the domain specified in the - continue URL is not allowlisted in the Firebase console. - + `AuthErrorCodeInvalidContinueURI` - Indicates that the domain specified in the - continue URL is not valid. - - */ + /// Initiates a password reset for the given email address and `ActionCodeSettings` object. + /// + /// This method does not throw an + /// error when there's no user account with the given email address and [Email Enumeration + /// Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) + /// is enabled. + /// + /// Possible error codes: + /// * `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was + /// sent in the request. + /// * `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in + /// the console for this action. + /// * `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for + /// sending update email. + /// * `AuthErrorCodeMissingIosBundleID` - Indicates that the iOS bundle ID is missing when + /// `handleCodeInApp` is set to true. + /// * `AuthErrorCodeMissingAndroidPackageName` - Indicates that the android package name + /// is missing when the `androidInstallApp` flag is set to true. + /// * `AuthErrorCodeUnauthorizedDomain` - Indicates that the domain specified in the + /// continue URL is not allowlisted in the Firebase console. + /// * `AuthErrorCodeInvalidContinueURI` - Indicates that the domain specified in the + /// continue URL is not valid. + /// - Parameter email: The email address of the user. + /// - Parameter actionCodeSettings: An `ActionCodeSettings` object containing settings related to + /// handling action codes. + /// - Parameter completion: Optionally; a block which is invoked when the request finishes. + /// Invoked asynchronously on the main thread in the future. @objc open func sendPasswordReset(withEmail email: String, actionCodeSettings: ActionCodeSettings?, completion: ((Error?) -> Void)? = nil) { @@ -1315,37 +1170,31 @@ extension Auth: AuthInterop { } } - /** @fn sendPasswordResetWithEmail:actionCodeSetting:completion: - @brief Initiates a password reset for the given email address and `ActionCodeSettings` object. - This method does not throw an - error when there's no user account with the given email address and [Email Enumeration - Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) - is enabled. - - @param email The email address of the user. - @param actionCodeSettings An `ActionCodeSettings` object containing settings related to - handling action codes. - @param completion Optionally; a block which is invoked when the request finishes. Invoked - asynchronously on the main thread in the future. - - @remarks Possible error codes: - - + `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was - sent in the request. - + `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in - the console for this action. - + `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for - sending update email. - + `AuthErrorCodeMissingIosBundleID` - Indicates that the iOS bundle ID is missing when - `handleCodeInApp` is set to true. - + `AuthErrorCodeMissingAndroidPackageName` - Indicates that the android package name - is missing when the `androidInstallApp` flag is set to true. - + `AuthErrorCodeUnauthorizedDomain` - Indicates that the domain specified in the - continue URL is not allowlisted in the Firebase console. - + `AuthErrorCodeInvalidContinueURI` - Indicates that the domain specified in the - continue URL is not valid. - - */ + /// Initiates a password reset for the given email address and `ActionCodeSettings` object. + /// + /// This method does not throw an + /// error when there's no user account with the given email address and [Email Enumeration + /// Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) + /// is enabled. + /// + /// Possible error codes: + /// * `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was + /// sent in the request. + /// * `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in + /// the console for this action. + /// * `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for + /// sending update email. + /// * `AuthErrorCodeMissingIosBundleID` - Indicates that the iOS bundle ID is missing when + /// `handleCodeInApp` is set to true. + /// * `AuthErrorCodeMissingAndroidPackageName` - Indicates that the android package name + /// is missing when the `androidInstallApp` flag is set to true. + /// * `AuthErrorCodeUnauthorizedDomain` - Indicates that the domain specified in the + /// continue URL is not allowlisted in the Firebase console. + /// * `AuthErrorCodeInvalidContinueURI` - Indicates that the domain specified in the + /// continue URL is not valid. + /// - Parameter email: The email address of the user. + /// - Parameter actionCodeSettings: An `ActionCodeSettings` object containing settings related to + /// handling action codes. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) open func sendPasswordReset(withEmail email: String, actionCodeSettings: ActionCodeSettings? = nil) async throws { @@ -1360,15 +1209,12 @@ extension Auth: AuthInterop { } } - /** @fn sendSignInLinkToEmail:actionCodeSettings:completion: - @brief Sends a sign in with email link to provided email address. - - @param email The email address of the user. - @param actionCodeSettings An `ActionCodeSettings` object containing settings related to - handling action codes. - @param completion Optionally; a block which is invoked when the request finishes. Invoked - asynchronously on the main thread in the future. - */ + /// Sends a sign in with email link to provided email address. + /// - Parameter email: The email address of the user. + /// - Parameter actionCodeSettings: An `ActionCodeSettings` object containing settings related to + /// handling action codes. + /// - Parameter completion: Optionally; a block which is invoked when the request finishes. + /// Invoked asynchronously on the main thread in the future. @objc open func sendSignInLink(toEmail email: String, actionCodeSettings: ActionCodeSettings, completion: ((Error?) -> Void)? = nil) { @@ -1397,15 +1243,10 @@ extension Auth: AuthInterop { } } - /** @fn sendSignInLinkToEmail:actionCodeSettings:completion: - @brief Sends a sign in with email link to provided email address. - - @param email The email address of the user. - @param actionCodeSettings An `ActionCodeSettings` object containing settings related to - handling action codes. - @param completion Optionally; a block which is invoked when the request finishes. Invoked - asynchronously on the main thread in the future. - */ + /// Sends a sign in with email link to provided email address. + /// - Parameter email: The email address of the user. + /// - Parameter actionCodeSettings: An `ActionCodeSettings` object containing settings related to + /// handling action codes. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) open func sendSignInLink(toEmail email: String, actionCodeSettings: ActionCodeSettings) async throws { @@ -1420,20 +1261,12 @@ extension Auth: AuthInterop { } } - /** @fn signOut: - @brief Signs out the current user. - - @param error Optionally; if an error occurs, upon return contains an NSError object that - describes the problem; is nil otherwise. - @return @YES when the sign out request was successful. @NO otherwise. - - @remarks Possible error codes: - - + `AuthErrorCodeKeychainError` - Indicates an error occurred when accessing the - keychain. The `NSLocalizedFailureReasonErrorKey` field in the `userInfo` - dictionary will contain more information about the error encountered. - - */ + /// Signs out the current user. + /// + /// Possible error codes: + /// * `AuthErrorCodeKeychainError` - Indicates an error occurred when accessing the + /// keychain. The `NSLocalizedFailureReasonErrorKey` field in the `userInfo` + /// dictionary will contain more information about the error encountered. @objc(signOut:) open func signOut() throws { try kAuthGlobalWorkQueue.sync { guard self.currentUser != nil else { @@ -1443,12 +1276,9 @@ extension Auth: AuthInterop { } } - /** @fn isSignInWithEmailLink - @brief Checks if link is an email sign-in link. - - @param link The email sign-in link. - @return Returns true when the link passed matches the expected format of an email sign-in link. - */ + /// Checks if link is an email sign-in link. + /// - Parameter link: The email sign-in link. + /// - Returns: `true` when the link passed matches the expected format of an email sign-in link. @objc open func isSignIn(withEmailLink link: String) -> Bool { guard link.count > 0 else { return false @@ -1463,13 +1293,11 @@ extension Auth: AuthInterop { } #if os(iOS) && !targetEnvironment(macCatalyst) - /** @fn initializeRecaptchaConfigWithCompletion:completion: - @brief Initializes reCAPTCHA using the settings configured for the project or - tenant. - If you change the tenant ID of the `Auth` instance, the configuration will be - reloaded. - */ + /// Initializes reCAPTCHA using the settings configured for the project or tenant. + /// + /// If you change the tenant ID of the `Auth` instance, the configuration will be + /// reloaded. @objc(initializeRecaptchaConfigWithCompletion:) open func initializeRecaptchaConfig(completion: ((Error?) -> Void)?) { Task { @@ -1486,13 +1314,10 @@ extension Auth: AuthInterop { } } - /** @fn initializeRecaptchaConfig - @brief Initializes reCAPTCHA using the settings configured for the project or - tenant. - - If you change the tenant ID of the `Auth` instance, the configuration will be - reloaded. - */ + /// Initializes reCAPTCHA using the settings configured for the project or tenant. + /// + /// If you change the tenant ID of the `Auth` instance, the configuration will be + /// reloaded. open func initializeRecaptchaConfig() async throws { // Trigger recaptcha verification flow to initialize the recaptcha client and // config. Recaptcha token will be returned. @@ -1501,24 +1326,21 @@ extension Auth: AuthInterop { } #endif - /** @fn addAuthStateDidChangeListener: - @brief Registers a block as an "auth state did change" listener. To be invoked when: - - + The block is registered as a listener, - + A user with a different UID from the current user has signed in, or - + The current user has signed out. - - @param listener The block to be invoked. The block is always invoked asynchronously on the main - thread, even for it's initial invocation after having been added as a listener. - - @remarks The block is invoked immediately after adding it according to it's standard invocation - semantics, asynchronously on the main thread. Users should pay special attention to - making sure the block does not inadvertently retain objects which should not be retained by - the long-lived block. The block itself will be retained by `Auth` until it is - unregistered or until the `Auth` instance is otherwise deallocated. - - @return A handle useful for manually unregistering the block as a listener. - */ + /// Registers a block as an "auth state did change" listener. + /// + /// To be invoked when: + /// * The block is registered as a listener, + /// * A user with a different UID from the current user has signed in, or + /// * The current user has signed out. + /// + /// The block is invoked immediately after adding it according to it's standard invocation + /// semantics, asynchronously on the main thread. Users should pay special attention to + /// making sure the block does not inadvertently retain objects which should not be retained by + /// the long-lived block. The block itself will be retained by `Auth` until it is + /// unregistered or until the `Auth` instance is otherwise deallocated. + /// - Parameter listener: The block to be invoked. The block is always invoked asynchronously on + /// the main thread, even for it's initial invocation after having been added as a listener. + /// - Returns: A handle useful for manually unregistering the block as a listener. @objc(addAuthStateDidChangeListener:) open func addStateDidChangeListener(_ listener: @escaping (Auth, User?) -> Void) -> NSObjectProtocol { @@ -1534,11 +1356,8 @@ extension Auth: AuthInterop { } } - /** @fn removeAuthStateDidChangeListener: - @brief Unregisters a block as an "auth state did change" listener. - - @param listenerHandle The handle for the listener. - */ + /// Unregisters a block as an "auth state did change" listener. + /// - Parameter listenerHandle: The handle for the listener. @objc(removeAuthStateDidChangeListener:) open func removeStateDidChangeListener(_ listenerHandle: NSObjectProtocol) { NotificationCenter.default.removeObserver(listenerHandle) @@ -1547,25 +1366,22 @@ extension Auth: AuthInterop { listenerHandles.remove(listenerHandle) } - /** @fn addIDTokenDidChangeListener: - @brief Registers a block as an "ID token did change" listener. To be invoked when: - - + The block is registered as a listener, - + A user with a different UID from the current user has signed in, - + The ID token of the current user has been refreshed, or - + The current user has signed out. - - @param listener The block to be invoked. The block is always invoked asynchronously on the main - thread, even for it's initial invocation after having been added as a listener. - - @remarks The block is invoked immediately after adding it according to it's standard invocation - semantics, asynchronously on the main thread. Users should pay special attention to - making sure the block does not inadvertently retain objects which should not be retained by - the long-lived block. The block itself will be retained by `Auth` until it is - unregistered or until the `Auth` instance is otherwise deallocated. - - @return A handle useful for manually unregistering the block as a listener. - */ + /// Registers a block as an "ID token did change" listener. + /// + /// To be invoked when: + /// * The block is registered as a listener, + /// * A user with a different UID from the current user has signed in, + /// * The ID token of the current user has been refreshed, or + /// * The current user has signed out. + /// + /// The block is invoked immediately after adding it according to it's standard invocation + /// semantics, asynchronously on the main thread. Users should pay special attention to + /// making sure the block does not inadvertently retain objects which should not be retained by + /// the long-lived block. The block itself will be retained by `Auth` until it is + /// unregistered or until the `Auth` instance is otherwise deallocated. + /// - Parameter listener: The block to be invoked. The block is always invoked asynchronously on + /// the main thread, even for it's initial invocation after having been added as a listener. + /// - Returns: A handle useful for manually unregistering the block as a listener. @objc open func addIDTokenDidChangeListener(_ listener: @escaping (Auth, User?) -> Void) -> NSObjectProtocol { let handle = NotificationCenter.default.addObserver( @@ -1586,11 +1402,8 @@ extension Auth: AuthInterop { return handle } - /** @fn removeIDTokenDidChangeListener: - @brief Unregisters a block as an "ID token did change" listener. - - @param listenerHandle The handle for the listener. - */ + /// Unregisters a block as an "ID token did change" listener. + /// - Parameter listenerHandle: The handle for the listener. @objc open func removeIDTokenDidChangeListener(_ listenerHandle: NSObjectProtocol) { NotificationCenter.default.removeObserver(listenerHandle) objc_sync_enter(Auth.self) @@ -1598,18 +1411,14 @@ extension Auth: AuthInterop { objc_sync_exit(Auth.self) } - /** @fn useAppLanguage - @brief Sets `languageCode` to the app's current language. - */ + /// Sets `languageCode` to the app's current language. @objc open func useAppLanguage() { kAuthGlobalWorkQueue.sync { self.requestConfiguration.languageCode = Locale.preferredLanguages.first } } - /** @fn useEmulatorWithHost:port - @brief Configures Firebase Auth to connect to an emulated host instead of the remote backend. - */ + /// Configures Firebase Auth to connect to an emulated host instead of the remote backend. @objc open func useEmulator(withHost host: String, port: Int) { guard host.count > 0 else { fatalError("Cannot connect to empty host") @@ -1624,11 +1433,9 @@ extension Auth: AuthInterop { } } - /** @fn revokeTokenWithAuthorizationCode:Completion - @brief Revoke the users token with authorization code. - @param completion (Optional) the block invoked when the request to revoke the token is - complete, or fails. Invoked asynchronously on the main thread in the future. - */ + /// Revoke the users token with authorization code. + /// - Parameter completion: (Optional) the block invoked when the request to revoke the token is + /// complete, or fails. Invoked asynchronously on the main thread in the future. @objc open func revokeToken(withAuthorizationCode authorizationCode: String, completion: ((Error?) -> Void)? = nil) { currentUser?.internalGetToken { idToken, error in @@ -1646,11 +1453,9 @@ extension Auth: AuthInterop { } } - /** @fn revokeTokenWithAuthorizationCode:Completion - @brief Revoke the users token with authorization code. - @param completion (Optional) the block invoked when the request to revoke the token is - complete, or fails. Invoked asynchronously on the main thread in the future. - */ + /// Revoke the users token with authorization code. + /// - Parameter completion: (Optional) the block invoked when the request to revoke the token is + /// complete, or fails. Invoked asynchronously on the main thread in the future. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) open func revokeToken(withAuthorizationCode authorizationCode: String) async throws { return try await withCheckedThrowingContinuation { continuation in @@ -1664,10 +1469,7 @@ extension Auth: AuthInterop { } } - /** @fn useUserAccessGroup:error: - @brief Switch userAccessGroup and current user to the given accessGroup and the user stored in - it. - */ + /// Switch userAccessGroup and current user to the given accessGroup and the user stored in it. @objc open func useUserAccessGroup(_ accessGroup: String?) throws { // self.storedUserManager is initialized asynchronously. Make sure it is done. kAuthGlobalWorkQueue.sync {} @@ -1686,12 +1488,11 @@ extension Auth: AuthInterop { lastNotifiedUserToken = user?.rawAccessToken() } - /** @fn getStoredUserForAccessGroup:error: - @brief Get the stored user in the given accessGroup. - @note This API is not supported on tvOS when `shareAuthStateAcrossDevices` is set to `true`. - This case will return `nil`. - Please refer to https://github.com/firebase/firebase-ios-sdk/issues/8878 for details. - */ + /// Get the stored user in the given accessGroup. + /// + /// This API is not supported on tvOS when `shareAuthStateAcrossDevices` is set to `true`. + /// and will return `nil`. + /// Please refer to https://github.com/firebase/firebase-ios-sdk/issues/8878 for details. @available(swift 1000.0) // Objective-C only API @objc(getStoredUserForAccessGroup:error:) open func __getStoredUser(forAccessGroup accessGroup: String?, @@ -1704,12 +1505,12 @@ extension Auth: AuthInterop { } } - /** @fn getStoredUserForAccessGroup - @brief Get the stored user in the given accessGroup. - @note This API is not supported on tvOS when `shareAuthStateAcrossDevices` is set to `true`. - This case will return `nil`. - Please refer to https://github.com/firebase/firebase-ios-sdk/issues/8878 for details. - */ + /// Get the stored user in the given accessGroup. + /// + /// This API is not supported on tvOS when `shareAuthStateAcrossDevices` is set to `true`. + /// and will return `nil`. + /// + /// Please refer to https://github.com/firebase/firebase-ios-sdk/issues/8878 for details. open func getStoredUser(forAccessGroup accessGroup: String?) throws -> User? { var user: User? if let accessGroup { @@ -1742,24 +1543,63 @@ extension Auth: AuthInterop { } #if os(iOS) + /// The APNs token used for phone number authentication. + /// + /// The type of the token (production or sandbox) will be automatically + /// detected based on your provisioning profile. + /// + /// This property is available on iOS only. + /// + /// If swizzling is disabled, the APNs Token must be set for phone number auth to work, + /// by either setting this property or by calling `setAPNSToken(_:type:)`. @objc(APNSToken) open var apnsToken: Data? { kAuthGlobalWorkQueue.sync { self.tokenManager.token?.data } } + /// Sets the APNs token along with its type. + /// + /// This method is available on iOS only. + /// + /// If swizzling is disabled, the APNs Token must be set for phone number auth to work, + /// by either setting calling this method or by setting the `APNSToken` property. @objc open func setAPNSToken(_ token: Data, type: AuthAPNSTokenType) { kAuthGlobalWorkQueue.sync { self.tokenManager.token = AuthAPNSToken(withData: token, type: type) } } + /// Whether the specific remote notification is handled by `Auth` . + /// + /// This method is available on iOS only. + /// + /// If swizzling is disabled, related remote notifications must be forwarded to this method + /// for phone number auth to work. + /// - Parameter userInfo: A dictionary that contains information related to the + /// notification in question. + /// - Returns: Whether or the notification is handled. A return value of `true` means the + /// notification is for Firebase Auth so the caller should ignore the notification from further + /// processing, and `false` means the notification is for the app (or another library) so + /// the caller should continue handling this notification as usual. @objc open func canHandleNotification(_ userInfo: [AnyHashable: Any]) -> Bool { kAuthGlobalWorkQueue.sync { self.notificationManager.canHandle(notification: userInfo) } } + /// Whether the specific URL is handled by `Auth` . + /// + /// This method is available on iOS only. + /// + /// If swizzling is disabled, URLs received by the application delegate must be forwarded + /// to this method for phone number auth to work. + /// - Parameter url: The URL received by the application delegate from any of the openURL + /// method. + /// - Returns: Whether or the URL is handled. `true` means the URL is for Firebase Auth + /// so the caller should ignore the URL from further processing, and `false` means the + /// the URL is for the app (or another library) so the caller should continue handling + /// this URL as usual. @objc(canHandleURL:) open func canHandle(_ url: URL) -> Bool { kAuthGlobalWorkQueue.sync { guard let authURLPresenter = self.authURLPresenter as? AuthURLPresenter else { @@ -1770,6 +1610,10 @@ extension Auth: AuthInterop { } #endif + /// The name of the `NSNotificationCenter` notification which is posted when the auth state + /// changes (for example, a new token has been produced, a user signs in or signs out). + /// + /// The object parameter of the notification is the sender `Auth` instance. public static let authStateDidChangeNotification = NSNotification.Name(rawValue: "FIRAuthStateDidChangeNotification") @@ -1937,10 +1781,8 @@ extension Auth: AuthInterop { return user } - /** @fn keychainServiceNameForAppName: - @brief Gets the keychain service name global data for the particular app by name. - @param appName The name of the Firebase app to get keychain service name for. - */ + /// Gets the keychain service name global data for the particular app by name. + /// - Parameter appName: The name of the Firebase app to get keychain service name for. class func keychainServiceForAppID(_ appID: String) -> String { return "firebase_auth_\(appID)" } @@ -1960,38 +1802,31 @@ extension Auth: AuthInterop { return nil } - /** @var gKeychainServiceNameForAppName - @brief A map from Firebase app name to keychain service names. - @remarks This map is needed for looking up the keychain service name after the FIRApp instance - is deleted, to remove the associated keychain item. Accessing should occur within a - @syncronized([FIRAuth class]) context."" - */ + /// A map from Firebase app name to keychain service names. + /// + /// This map is needed for looking up the keychain service name after the FirebaseApp instance + /// is deleted, to remove the associated keychain item. Accessing should occur within a + /// @syncronized([FIRAuth class]) context. fileprivate static var gKeychainServiceNameForAppName: [String: String] = [:] - /** @fn setKeychainServiceNameForApp - @brief Sets the keychain service name global data for the particular app. - @param app The Firebase app to set keychain service name for. - */ + /// Sets the keychain service name global data for the particular app. + /// - Parameter app: The Firebase app to set keychain service name for. class func setKeychainServiceNameForApp(_ app: FirebaseApp) { objc_sync_enter(Auth.self) gKeychainServiceNameForAppName[app.name] = "firebase_auth_\(app.options.googleAppID)" objc_sync_exit(Auth.self) } - /** @fn keychainServiceNameForAppName: - @brief Gets the keychain service name global data for the particular app by name. - @param appName The name of the Firebase app to get keychain service name for. - */ + /// Gets the keychain service name global data for the particular app by name. + /// - Parameter appName: The name of the Firebase app to get keychain service name for. class func keychainServiceName(forAppName appName: String) -> String? { objc_sync_enter(Auth.self) defer { objc_sync_exit(Auth.self) } return gKeychainServiceNameForAppName[appName] } - /** @fn deleteKeychainServiceNameForAppName: - @brief Deletes the keychain service name global data for the particular app by name. - @param appName The name of the Firebase app to delete keychain service name for. - */ + /// Deletes the keychain service name global data for the particular app by name. + /// - Parameter appName: The name of the Firebase app to delete keychain service name for. class func deleteKeychainServiceNameForAppName(_ appName: String) { objc_sync_enter(Auth.self) gKeychainServiceNameForAppName.removeValue(forKey: appName) @@ -2007,9 +1842,7 @@ extension Auth: AuthInterop { // MARK: Private methods - /** @fn possiblyPostAuthStateChangeNotification - @brief Posts the auth state change notificaton if current user's token has been changed. - */ + /// Posts the auth state change notificaton if current user's token has been changed. private func possiblyPostAuthStateChangeNotification() { let token = currentUser?.rawAccessToken() if lastNotifiedUserToken == token || @@ -2039,23 +1872,20 @@ extension Auth: AuthInterop { } } - /** @fn scheduleAutoTokenRefreshWithDelay: - @brief Schedules a task to automatically refresh tokens on the current user. The0 token refresh - is scheduled 5 minutes before the scheduled expiration time. - @remarks If the token expires in less than 5 minutes, schedule the token refresh immediately. - */ + /// Schedules a task to automatically refresh tokens on the current user. The0 token refresh + /// is scheduled 5 minutes before the scheduled expiration time. + /// + /// If the token expires in less than 5 minutes, schedule the token refresh immediately. private func scheduleAutoTokenRefresh() { let tokenExpirationInterval = (currentUser?.accessTokenExpirationDate()?.timeIntervalSinceNow ?? 0) - 5 * 60 scheduleAutoTokenRefresh(withDelay: max(tokenExpirationInterval, 0), retry: false) } - /** @fn scheduleAutoTokenRefreshWithDelay: - @brief Schedules a task to automatically refresh tokens on the current user. - @param delay The delay in seconds after which the token refresh task should be scheduled to be - executed. - @param retry Flag to determine whether the invocation is a retry attempt or not. - */ + /// Schedules a task to automatically refresh tokens on the current user. + /// - Parameter delay: The delay in seconds after which the token refresh task should be scheduled + /// to be executed. + /// - Parameter retry: Flag to determine whether the invocation is a retry attempt or not. private func scheduleAutoTokenRefresh(withDelay delay: TimeInterval, retry: Bool) { guard let accessToken = currentUser?.rawAccessToken() else { return @@ -2099,16 +1929,15 @@ extension Auth: AuthInterop { } } - /** @fn updateCurrentUser:byForce:savingToDisk:error: - @brief Update the current user; initializing the user's internal properties correctly, and - optionally saving the user to disk. - @remarks This method is called during: sign in and sign out events, as well as during class - initialization time. The only time the saveToDisk parameter should be set to NO is during - class initialization time because the user was just read from disk. - @param user The user to use as the current user (including nil, which is passed at sign out - time.) - @param saveToDisk Indicates the method should persist the user data to disk. - */ + /// Update the current user; initializing the user's internal properties correctly, and + /// optionally saving the user to disk. + /// + /// This method is called during: sign in and sign out events, as well as during class + /// initialization time. The only time the saveToDisk parameter should be set to NO is during + /// class initialization time because the user was just read from disk. + /// - Parameter user: The user to use as the current user (including nil, which is passed at sign + /// out time.) + /// - Parameter saveToDisk: Indicates the method should persist the user data to disk. func updateCurrentUser(_ user: User?, byForce force: Bool, savingToDisk saveToDisk: Bool) throws { if user == currentUser { @@ -2169,15 +1998,11 @@ extension Auth: AuthInterop { } } - /** @fn completeSignInWithTokenService:callback: - @brief Completes a sign-in flow once we have access and refresh tokens for the user. - @param accessToken The STS access token. - @param accessTokenExpirationDate The approximate expiration date of the access token. - @param refreshToken The STS refresh token. - @param anonymous Whether or not the user is anonymous. - @param callback Called when the user has been signed in or when an error occurred. Invoked - asynchronously on the global auth work queue in the future. - */ + /// Completes a sign-in flow once we have access and refresh tokens for the user. + /// - Parameter accessToken: The STS access token. + /// - Parameter accessTokenExpirationDate: The approximate expiration date of the access token. + /// - Parameter refreshToken: The STS refresh token. + /// - Parameter anonymous: Whether or not the user is anonymous. @discardableResult func completeSignIn(withAccessToken accessToken: String?, accessTokenExpirationDate: Date?, @@ -2190,15 +2015,12 @@ extension Auth: AuthInterop { anonymous: anonymous) } - /** @fn internalSignInAndRetrieveDataWithEmail:password:callback: - @brief Signs in using an email address and password. - @param email The user's email address. - @param password The user's password. - @param completion A block which is invoked when the sign in finishes (or is cancelled.) Invoked - asynchronously on the global auth work queue in the future. - @remarks This is the internal counterpart of this method, which uses a callback that does not - update the current user. - */ + /// Signs in using an email address and password. + /// + /// This is the internal counterpart of this method, which uses a callback that does not + /// update the current user. + /// - Parameter email: The user's email address. + /// - Parameter password: The user's password. private func internalSignInAndRetrieveData(withEmail email: String, password: String) async throws -> AuthDataResult { let credential = EmailAuthCredential(withEmail: email, password: password) @@ -2286,13 +2108,9 @@ extension Auth: AuthInterop { } #if os(iOS) - /** @fn signInWithPhoneCredential:callback: - @brief Signs in using a phone credential. - @param credential The Phone Auth credential used to sign in. - @param operation The type of operation for which this sign-in attempt is initiated. - @param callback A block which is invoked when the sign in finishes (or is cancelled.) Invoked - asynchronously on the global auth work queue in the future. - */ + /// Signs in using a phone credential. + /// - Parameter credential: The Phone Auth credential used to sign in. + /// - Parameter operation: The type of operation for which this sign-in attempt is initiated. private func signIn(withPhoneCredential credential: PhoneAuthCredential, operation: AuthOperationType) async throws -> VerifyPhoneNumberResponse { switch credential.credentialKind { @@ -2319,12 +2137,8 @@ extension Auth: AuthInterop { #endif #if !os(watchOS) - /** @fn signInAndRetrieveDataWithGameCenterCredential:callback: - @brief Signs in using a game center credential. - @param credential The Game Center Auth Credential used to sign in. - @param callback A block which is invoked when the sign in finished (or is cancelled). Invoked - asynchronously on the global auth work queue in the future. - */ + /// Signs in using a game center credential. + /// - Parameter credential: The Game Center Auth Credential used to sign in. private func signInAndRetrieveData(withGameCenterCredential credential: GameCenterAuthCredential) async throws -> AuthDataResult { guard let publicKeyURL = credential.publicKeyURL, @@ -2358,13 +2172,9 @@ extension Auth: AuthInterop { #endif - /** @fn internalSignInAndRetrieveDataWithEmail:link:completion: - @brief Signs in using an email and email sign-in link. - @param email The user's email address. - @param link The email sign-in link. - @param callback A block which is invoked when the sign in finishes (or is cancelled.) Invoked - asynchronously on the global auth work queue in the future. - */ + /// Signs in using an email and email sign-in link. + /// - Parameter email: The user's email address. + /// - Parameter link: The email sign-in link. private func internalSignInAndRetrieveData(withEmail email: String, link: String) async throws -> AuthDataResult { guard isSignIn(withEmailLink: link) else { @@ -2404,15 +2214,15 @@ extension Auth: AuthInterop { return queryItems } - /** @fn signInFlowAuthDataResultCallbackByDecoratingCallback: - @brief Creates a AuthDataResultCallback block which wraps another AuthDataResultCallback; - trying to update the current user before forwarding it's invocations along to a subject block. - @param callback Called when the user has been updated or when an error has occurred. Invoked - asynchronously on the main thread in the future. - @return Returns a block that updates the current user. - @remarks Typically invoked as part of the complete sign-in flow. For any other uses please - consider alternative ways of updating the current user. - */ + /// Creates a AuthDataResultCallback block which wraps another AuthDataResultCallback; + /// trying to update the current user before forwarding it's invocations along to a subject + /// block. + /// + /// Typically invoked as part of the complete sign-in flow. For any other uses please + /// consider alternative ways of updating the current user. + /// - Parameter callback: Called when the user has been updated or when an error has occurred. + /// Invoked asynchronously on the main thread in the future. + /// - Returns: Returns a block that updates the current user. func signInFlowAuthDataResultCallback(byDecorating callback: ((AuthDataResult?, Error?) -> Void)?) -> (AuthDataResult?, Error?) -> Void { let authDataCallback: (((AuthDataResult?, Error?) -> Void)?, AuthDataResult?, Error?) -> Void = @@ -2514,106 +2324,72 @@ extension Auth: AuthInterop { // MARK: Internal properties - /** @property mainBundle - @brief Allow tests to swap in an alternate mainBundle. - */ + /// Allow tests to swap in an alternate mainBundle. var mainBundleUrlTypes: [[String: Any]]! - /** @property requestConfiguration - @brief The configuration object comprising of parameters needed to make a request to Firebase - Auth's backend. - */ + /// The configuration object comprising of parameters needed to make a request to Firebase + /// Auth's backend. var requestConfiguration: AuthRequestConfiguration #if os(iOS) - /** @property tokenManager - @brief The manager for APNs tokens used by phone number auth. - */ + + /// The manager for APNs tokens used by phone number auth. var tokenManager: AuthAPNSTokenManager! - /** @property appCredentailManager - @brief The manager for app credentials used by phone number auth. - */ + /// The manager for app credentials used by phone number auth. var appCredentialManager: AuthAppCredentialManager! - /** @property notificationManager - @brief The manager for remote notifications used by phone number auth. - */ + /// The manager for remote notifications used by phone number auth. var notificationManager: AuthNotificationManager! - /** @property authURLPresenter - @brief An object that takes care of presenting URLs via the auth instance. - */ + /// An object that takes care of presenting URLs via the auth instance. var authURLPresenter: AuthWebViewControllerDelegate #endif // TARGET_OS_IOS // MARK: Private properties - /** @property storedUserManager - @brief The stored user manager. - */ + /// The stored user manager. private var storedUserManager: AuthStoredUserManager! - /** @var _firebaseAppName - @brief The Firebase app name. - */ + /// The Firebase app name. private let firebaseAppName: String - /** @var _keychainServices - @brief The keychain service. - */ + /// The keychain service. private var keychainServices: AuthKeychainServices! - /** @var _lastNotifiedUserToken - @brief The user access (ID) token used last time for posting auth state changed notification. - */ + /// The user access (ID) token used last time for posting auth state changed notification. private var lastNotifiedUserToken: String? - /** @var _autoRefreshTokens - @brief This flag denotes whether or not tokens should be automatically refreshed. - @remarks Will only be set to @YES if the another Firebase service is included (additionally to - Firebase Auth). - */ + /// This flag denotes whether or not tokens should be automatically refreshed. + /// Will only be set to `true` if the another Firebase service is included (additionally to + /// Firebase Auth). private var autoRefreshTokens = false - /** @var _autoRefreshScheduled - @brief Whether or not token auto-refresh is currently scheduled. - */ + /// Whether or not token auto-refresh is currently scheduled. private var autoRefreshScheduled = false - /** @var _isAppInBackground - @brief A flag that is set to YES if the app is put in the background and no when the app is - returned to the foreground. - */ + /// A flag that is set to YES if the app is put in the background and no when the app is + /// returned to the foreground. private var isAppInBackground = false - /** @var _applicationDidBecomeActiveObserver - @brief An opaque object to act as the observer for UIApplicationDidBecomeActiveNotification. - */ + /// An opaque object to act as the observer for UIApplicationDidBecomeActiveNotification. private var applicationDidBecomeActiveObserver: NSObjectProtocol? - /** @var _applicationDidBecomeActiveObserver - @brief An opaque object to act as the observer for - UIApplicationDidEnterBackgroundNotification. - */ + /// An opaque object to act as the observer for + /// UIApplicationDidEnterBackgroundNotification. private var applicationDidEnterBackgroundObserver: NSObjectProtocol? - /** @var _protectedDataDidBecomeAvailableObserver - @brief An opaque object to act as the observer for - UIApplicationProtectedDataDidBecomeAvailable. - */ + /// An opaque object to act as the observer for + /// UIApplicationProtectedDataDidBecomeAvailable. private var protectedDataDidBecomeAvailableObserver: NSObjectProtocol? - /** @var kUserKey - @brief Key of user stored in the keychain. Prefixed with a Firebase app name. - */ + /// Key of user stored in the keychain. Prefixed with a Firebase app name. private let kUserKey = "_firebase_user" - /** @var _listenerHandles - @brief Handles returned from @c NSNotificationCenter for blocks which are "auth state did - change" notification listeners. - @remarks Mutations should occur within a @syncronized(self) context. - */ + /// Handles returned from `NSNotificationCenter` for blocks which are "auth state did + /// change" notification listeners. + /// + /// Mutations should occur within a @syncronized(self) context. private var listenerHandles: NSMutableArray = [] } diff --git a/FirebaseAuth/Sources/Swift/Auth/AuthComponent.swift b/FirebaseAuth/Sources/Swift/Auth/AuthComponent.swift index 277fcce76b1..aa73a11fbc4 100644 --- a/FirebaseAuth/Sources/Swift/Auth/AuthComponent.swift +++ b/FirebaseAuth/Sources/Swift/Auth/AuthComponent.swift @@ -12,19 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. +import Foundation + import FirebaseAppCheckInterop +import FirebaseAuthInterop import FirebaseCore import FirebaseCoreExtension -import Foundation - -@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -@objc(FIRAuthProvider) protocol AuthProvider { - @objc func auth() -> Auth -} @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @objc(FIRAuthComponent) -class AuthComponent: NSObject, Library, AuthProvider, ComponentLifecycleMaintainer { +class AuthComponent: NSObject, Library, ComponentLifecycleMaintainer { // MARK: - Private Variables /// The app associated with all Auth instances in this container. @@ -47,17 +44,17 @@ class AuthComponent: NSObject, Library, AuthProvider, ComponentLifecycleMaintain // MARK: - Library conformance static func componentsToRegister() -> [Component] { + let authCreationBlock: ComponentCreationBlock = { container, isCacheable in + guard let app = container.app else { return nil } + isCacheable.pointee = true + return Auth(app: app) + } let appCheckInterop = Dependency(with: AppCheckInterop.self, isRequired: false) - return [Component(AuthProvider.self, - instantiationTiming: .alwaysEager, - dependencies: [appCheckInterop]) { container, isCacheable in - guard let app = container.app else { return nil } - isCacheable.pointee = true - let newComponent = AuthComponent(app: app) - // Set up instances early enough so User on keychain will be decoded. - newComponent.auth() - return newComponent - }] + let authInterop = Component(AuthInterop.self, + instantiationTiming: .alwaysEager, + dependencies: [appCheckInterop], + creationBlock: authCreationBlock) + return [authInterop] } // MARK: - AuthProvider conformance diff --git a/FirebaseAuth/Sources/Swift/Auth/AuthDataResult.swift b/FirebaseAuth/Sources/Swift/Auth/AuthDataResult.swift index 7462ff87725..bd09c1ddac2 100644 --- a/FirebaseAuth/Sources/Swift/Auth/AuthDataResult.swift +++ b/FirebaseAuth/Sources/Swift/Auth/AuthDataResult.swift @@ -17,35 +17,29 @@ import Foundation @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) extension AuthDataResult: NSSecureCoding {} -/** @class AuthDataResult - @brief Helper object that contains the result of a successful sign-in, link and reauthenticate - action. It contains references to a `User` instance and a `AdditionalUserInfo` instance. - */ +/// Helper object that contains the result of a successful sign-in, link and reauthenticate +/// action. +/// +/// It contains references to a `User` instance and an `AdditionalUserInfo` instance. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @objc(FIRAuthDataResult) open class AuthDataResult: NSObject { - /** @property user - @brief The signed in user. - */ + /// The signed in user. @objc public let user: User - /** @property additionalUserInfo - @brief If available contains the additional IdP specific information about signed in user. - */ + /// If available, contains the additional IdP specific information about signed in user. @objc public let additionalUserInfo: AdditionalUserInfo? - /** @property credential - @brief This property will be non-nil after a successful headful-lite sign-in via - `signIn(with:uiDelegate:completion:)`. May be used to obtain the accessToken and/or IDToken - pertaining to a recently signed-in user. - */ + /// This property will be non-nil after a successful headful-lite sign-in via + /// `signIn(with:uiDelegate:completion:)`. + /// + /// May be used to obtain the accessToken and/or IDToken + /// pertaining to a recently signed-in user. @objc public let credential: OAuthCredential? - /** @fn initWithUser:additionalUserInfo: - @brief Designated initializer. - @param user The signed in user reference. - @param additionalUserInfo The additional user info. - @param credential The updated OAuth credential if available. - */ + /// Designated initializer. + /// - Parameter user: The signed in user reference. + /// - Parameter additionalUserInfo: The additional user info. + /// - Parameter credential: The updated OAuth credential if available. init(withUser user: User, additionalUserInfo: AdditionalUserInfo?, credential: OAuthCredential? = nil) { diff --git a/FirebaseAuth/Sources/Swift/Auth/AuthDispatcher.swift b/FirebaseAuth/Sources/Swift/Auth/AuthDispatcher.swift index b2bc47322de..6373cdfb4cc 100644 --- a/FirebaseAuth/Sources/Swift/Auth/AuthDispatcher.swift +++ b/FirebaseAuth/Sources/Swift/Auth/AuthDispatcher.swift @@ -14,26 +14,22 @@ import Foundation -/** @class AuthDispatcher - @brief A utility class used to facilitate scheduling tasks to be executed in the future. - */ +/// A utility class used to facilitate scheduling tasks to be executed in the future. class AuthDispatcher { static let shared = AuthDispatcher() - /** @property dispatchAfterImplementation - @brief Allows custom implementation of dispatchAfterDelay:queue:callback:. - @remarks Set to nil to restore default implementation. - */ + /// Allows custom implementation of dispatchAfterDelay:queue:callback:. + /// + /// Set to nil to restore default implementation. var dispatchAfterImplementation: ((TimeInterval, DispatchQueue, @escaping () -> Void) -> Void)? - /** @fn dispatchAfterDelay:queue:callback: - @brief Schedules task in the future after a specified delay. - - @param delay The delay in seconds after which the task will be scheduled to execute. - @param queue The dispatch queue on which the task will be submitted. - @param task The task (block) to be scheduled for future execution. - */ - func dispatch(afterDelay delay: TimeInterval, queue: DispatchQueue, task: @escaping () -> Void) { + /// Schedules task in the future after a specified delay. + /// - Parameter delay: The delay in seconds after which the task will be scheduled to execute. + /// - Parameter queue: The dispatch queue on which the task will be submitted. + /// - Parameter task: The task(block) to be scheduled for future execution. + func dispatch(afterDelay delay: TimeInterval, + queue: DispatchQueue, + task: @escaping () -> Void) { if let dispatchAfterImplementation { dispatchAfterImplementation(delay, queue, task) } else { diff --git a/FirebaseAuth/Sources/Swift/Auth/AuthOperationType.swift b/FirebaseAuth/Sources/Swift/Auth/AuthOperationType.swift index c21b561b9b6..08ffe5cf8c0 100644 --- a/FirebaseAuth/Sources/Swift/Auth/AuthOperationType.swift +++ b/FirebaseAuth/Sources/Swift/Auth/AuthOperationType.swift @@ -13,28 +13,21 @@ // limitations under the License. import Foundation -/** - @brief Indicates the type of operation performed for RPCs that support the operation - parameter. - */ + +/// Indicates the type of operation performed for RPCs that support the operation parameter. enum AuthOperationType: Int { - /** Indicates that the operation type is uspecified. - */ + /// Indicates that the operation type is uspecified. case unspecified = 0 - /** Indicates that the operation type is sign in or sign up. - */ + /// Indicates that the operation type is sign in or sign up. case signUpOrSignIn = 1 - /** Indicates that the operation type is reauthentication. - */ + /// Indicates that the operation type is reauthentication. case reauth = 2 - /** Indicates that the operation type is update. - */ + /// Indicates that the operation type is update. case update = 3 - /** Indicates that the operation type is link. - */ + /// Indicates that the operation type is link. case link = 4 } diff --git a/FirebaseAuth/Sources/Swift/Auth/AuthSettings.swift b/FirebaseAuth/Sources/Swift/Auth/AuthSettings.swift index 10ce42396dc..dbd77df5306 100644 --- a/FirebaseAuth/Sources/Swift/Auth/AuthSettings.swift +++ b/FirebaseAuth/Sources/Swift/Auth/AuthSettings.swift @@ -14,14 +14,12 @@ import Foundation -/** @class AuthSettings - @brief Determines settings related to an auth object. - */ +/// Determines settings related to an auth object. @objc(FIRAuthSettings) open class AuthSettings: NSObject, NSCopying { - /** @property appVerificationDisabledForTesting - @brief Flag to determine whether app verification should be disabled for testing or not. - */ + /// Flag to determine whether app verification should be disabled for testing or not. @objc open var appVerificationDisabledForTesting: Bool + + /// Flag to determine whether app verification should be disabled for testing or not. @objc open var isAppVerificationDisabledForTesting: Bool { get { return appVerificationDisabledForTesting diff --git a/FirebaseAuth/Sources/Swift/Auth/AuthTokenResult.swift b/FirebaseAuth/Sources/Swift/Auth/AuthTokenResult.swift index 47c3e592947..76bdbb73204 100644 --- a/FirebaseAuth/Sources/Swift/Auth/AuthTokenResult.swift +++ b/FirebaseAuth/Sources/Swift/Auth/AuthTokenResult.swift @@ -16,48 +16,35 @@ import Foundation extension AuthTokenResult: NSSecureCoding {} -/** @class FIRAuthTokenResult - @brief A data class containing the ID token JWT string and other properties associated with the - token including the decoded payload claims. - */ +/// A data class containing the ID token JWT string and other properties associated with the +/// token including the decoded payload claims. @objc(FIRAuthTokenResult) open class AuthTokenResult: NSObject { - /** @property token - @brief Stores the JWT string of the ID token. - */ + /// Stores the JWT string of the ID token. @objc open var token: String - /** @property expirationDate - @brief Stores the ID token's expiration date. - */ + /// Stores the ID token's expiration date. @objc open var expirationDate: Date - /** @property authDate - @brief Stores the ID token's authentication date. - @remarks This is the date the user was signed in and NOT the date the token was refreshed. - */ + /// Stores the ID token's authentication date. + /// + /// This is the date the user was signed in and NOT the date the token was refreshed. @objc open var authDate: Date - /** @property issuedAtDate - @brief Stores the date that the ID token was issued. - @remarks This is the date last refreshed and NOT the last authentication date. - */ + /// Stores the date that the ID token was issued. + /// + /// This is the date last refreshed and NOT the last authentication date. @objc open var issuedAtDate: Date - /** @property signInProvider - @brief Stores sign-in provider through which the token was obtained. - @remarks This does not necessarily map to provider IDs. - */ + /// Stores sign-in provider through which the token was obtained. @objc open var signInProvider: String - /** @property signInSecondFactor - @brief Stores sign-in second factor through which the token was obtained. - */ + /// Stores sign-in second factor through which the token was obtained. @objc open var signInSecondFactor: String - /** @property claims - @brief Stores the entire payload of claims found on the ID token. This includes the standard - reserved claims as well as custom claims set by the developer via the Admin SDK. - */ + /// Stores the entire payload of claims found on the ID token. + /// + /// This includes the standard + /// reserved claims as well as custom claims set by the developer via the Admin SDK. @objc open var claims: [String: Any] private class func getTokenPayloadData(_ token: String) -> Data? { @@ -68,12 +55,12 @@ extension AuthTokenResult: NSSecureCoding {} return nil } - // The token payload is always the second index of the array. + /// The token payload is always the second index of the array. let IDToken = tokenStringArray[1] - // Convert the base64URL encoded string to a base64 encoded string. - // Replace "_" with "/" - // Replace "-" with "+" + /// Convert the base64URL encoded string to a base64 encoded string. + /// * Replace "_" with "/" + /// * Replace "-" with "+" var tokenPayload = IDToken.replacingOccurrences(of: "_", with: "/") .replacingOccurrences(of: "-", with: "+") @@ -104,11 +91,9 @@ extension AuthTokenResult: NSSecureCoding {} return jwt } - /** @fn tokenResultWithToken: - @brief Parse a token string to a structured token. - @param token The token string to parse. - @return A structured token result. - */ + /// Parse a token string to a structured token. + /// - Parameter token: The token string to parse. + /// - Returns: A structured token result. @objc open class func tokenResult(token: String) -> AuthTokenResult? { guard let payloadData = getTokenPayloadData(token), let claims = getTokenPayloadDictionary(payloadData), diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/AuthCredential.swift b/FirebaseAuth/Sources/Swift/AuthProvider/AuthCredential.swift index ea2cebbe7d3..0500875b10f 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/AuthCredential.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/AuthCredential.swift @@ -14,12 +14,12 @@ import Foundation -/** - @brief Public representation of a credential. - */ +/// Public representation of a credential. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @objc(FIRAuthCredential) open class AuthCredential: NSObject { + /// The name of the identity provider for the credential. @objc public let provider: String + init(provider: String) { self.provider = provider } diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/AuthProviderStrings.swift b/FirebaseAuth/Sources/Swift/AuthProvider/AuthProviderStrings.swift index a11e7e7ec25..978497fc8f4 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/AuthProviderStrings.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/AuthProviderStrings.swift @@ -14,6 +14,7 @@ import Foundation +/// Enumeration of the available Auth Providers. public enum AuthProviderString: String { case apple = "apple.com" case email = "password" diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/EmailAuthProvider.swift b/FirebaseAuth/Sources/Swift/AuthProvider/EmailAuthProvider.swift index 047df6a46a9..0675f02f737 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/EmailAuthProvider.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/EmailAuthProvider.swift @@ -14,31 +14,24 @@ import Foundation -/** - @brief A concrete implementation of `AuthProvider` for Email & Password Sign In. - */ +/// A concrete implementation of `AuthProvider` for Email & Password Sign In. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @objc(FIREmailAuthProvider) open class EmailAuthProvider: NSObject { + /// A string constant identifying the email & password identity provider. @objc public static let id = "password" - /** - @brief Creates an `AuthCredential` for an email & password sign in. - - @param email The user's email address. - @param password The user's password. - @return An `AuthCredential` containing the email & password credential. - */ + /// Creates an `AuthCredential` for an email & password sign in + /// - Parameter email: The user's email address. + /// - Parameter password: The user's password. + /// - Returns: An `AuthCredential` containing the email & password credential. @objc open class func credential(withEmail email: String, password: String) -> AuthCredential { return EmailAuthCredential(withEmail: email, password: password) } - /** @fn credentialWithEmail:Link: - @brief Creates an `AuthCredential` for an email & link sign in. - - @param email The user's email address. - @param link The email sign-in link. - @return An `AuthCredential` containing the email & link credential. - */ + /// Creates an `AuthCredential` for an email & link sign in. + /// - Parameter email: The user's email address. + /// - Parameter link: The email sign-in link. + /// - Returns: An `AuthCredential` containing the email & link credential. @objc open class func credential(withEmail email: String, link: String) -> AuthCredential { return EmailAuthCredential(withEmail: email, link: link) } diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/FacebookAuthProvider.swift b/FirebaseAuth/Sources/Swift/AuthProvider/FacebookAuthProvider.swift index e868f1c9b91..9ffbec55dba 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/FacebookAuthProvider.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/FacebookAuthProvider.swift @@ -14,19 +14,15 @@ import Foundation -/** - @brief Utility class for constructing Facebook Sign In credentials. - */ +/// Utility class for constructing Facebook Sign In credentials. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @objc(FIRFacebookAuthProvider) open class FacebookAuthProvider: NSObject { + /// A string constant identifying the Facebook identity provider. @objc public static let id = "facebook.com" - /** - @brief Creates an `AuthCredential` for a Facebook sign in. - - @param accessToken The Access Token from Facebook. - @return An AuthCredential containing the Facebook credentials. - */ + /// Creates an `AuthCredential` for a Facebook sign in. + /// - Parameter accessToken: The Access Token from Facebook. + /// - Returns: An `AuthCredential` containing the Facebook credentials. @objc open class func credential(withAccessToken accessToken: String) -> AuthCredential { return FacebookAuthCredential(withAccessToken: accessToken) } diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/FederatedAuthProvider.swift b/FirebaseAuth/Sources/Swift/AuthProvider/FederatedAuthProvider.swift index 44969082bec..419f87f06af 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/FederatedAuthProvider.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/FederatedAuthProvider.swift @@ -14,19 +14,16 @@ import Foundation -/** - Utility type for constructing federated auth provider credentials. - */ +/// Utility type for constructing federated auth provider credentials. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @objc(FIRFederatedAuthProvider) public protocol FederatedAuthProvider: NSObjectProtocol { #if os(iOS) - /** @fn getCredentialWithUIDelegate:completion: - @brief Used to obtain an auth credential via a mobile web flow. - This method is available on iOS only. - @param UIDelegate An optional UI delegate used to present the mobile web flow. - */ + + /// Used to obtain an auth credential via a mobile web flow. + /// This method is available on iOS only. + /// - Parameter uiDelegate: An optional UI delegate used to present the mobile web flow. @available(iOS 13, tvOS 13, macOS 10.15, watchOS 8, *) @objc(getCredentialWithUIDelegate:completion:) - func credential(with UIDelegate: AuthUIDelegate?) async throws -> AuthCredential + func credential(with uiDelegate: AuthUIDelegate?) async throws -> AuthCredential #endif } diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/GameCenterAuthProvider.swift b/FirebaseAuth/Sources/Swift/AuthProvider/GameCenterAuthProvider.swift index 45168981047..aecab7ca77d 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/GameCenterAuthProvider.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/GameCenterAuthProvider.swift @@ -28,16 +28,13 @@ @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) extension GameCenterAuthProvider: WarningWorkaround {} - /** - @brief A concrete implementation of `AuthProvider` for Game Center Sign In. Not available on watchOS. - */ + /// A concrete implementation of `AuthProvider` for Game Center Sign In. Not available on watchOS. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @objc(FIRGameCenterAuthProvider) open class GameCenterAuthProvider: NSObject { + /// A string constant identifying the Game Center identity provider. @objc public static let id = "gc.apple.com" - /** @fn - @brief Creates an `AuthCredential` for a Game Center sign in. - */ + /// Creates an `AuthCredential` for a Game Center sign in. @objc open class func getCredential(completion: @escaping (AuthCredential?, Error?) -> Void) { /** Linking GameKit.framework without using it on macOS results in App Store rejection. @@ -92,7 +89,7 @@ completion(nil, error) } else { /** - @c `localPlayer.alias` is actually the displayname needed, instead of + `localPlayer.alias` is actually the displayname needed, instead of `localPlayer.displayname`. For more information, check https://developer.apple.com/documentation/gamekit/gkplayer **/ @@ -110,9 +107,7 @@ } } - /** @fn - @brief Creates an `AuthCredential` for a Game Center sign in. - */ + /// Creates an `AuthCredential` for a Game Center sign in. @available(iOS 13, tvOS 13, macOS 10.15, watchOS 8, *) open class func getCredential() async throws -> AuthCredential { return try await withCheckedThrowingContinuation { continuation in @@ -133,8 +128,8 @@ } @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) - @objc(FIRGameCenterAuthCredential) class GameCenterAuthCredential: AuthCredential, - NSSecureCoding { + @objc(FIRGameCenterAuthCredential) + class GameCenterAuthCredential: AuthCredential, NSSecureCoding { let playerID: String let teamPlayerID: String? let gamePlayerID: String? @@ -144,17 +139,14 @@ let timestamp: UInt64 let displayName: String - /** - @brief Designated initializer. - @param playerID The ID of the Game Center local player. - @param teamPlayerID The teamPlayerID of the Game Center local player. - @param gamePlayerID The gamePlayerID of the Game Center local player. - @param publicKeyURL The URL for the public encryption key. - @param signature The verification signature generated. - @param salt A random string used to compute the hash and keep it randomized. - @param timestamp The date and time that the signature was created. - @param displayName The display name of the Game Center player. - */ + /// - Parameter playerID: The ID of the Game Center local player. + /// - Parameter teamPlayerID: The teamPlayerID of the Game Center local player. + /// - Parameter gamePlayerID: The gamePlayerID of the Game Center local player. + /// - Parameter publicKeyURL: The URL for the public encryption key. + /// - Parameter signature: The verification signature generated. + /// - Parameter salt: A random string used to compute the hash and keep it randomized. + /// - Parameter timestamp: The date and time that the signature was created. + /// - Parameter displayName: The display name of the Game Center player. init(withPlayerID playerID: String, teamPlayerID: String?, gamePlayerID: String?, publicKeyURL: URL?, signature: Data?, salt: Data?, timestamp: UInt64, displayName: String) { diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/GitHubAuthProvider.swift b/FirebaseAuth/Sources/Swift/AuthProvider/GitHubAuthProvider.swift index 0385cfca5b4..4704e4fe014 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/GitHubAuthProvider.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/GitHubAuthProvider.swift @@ -14,19 +14,15 @@ import Foundation -/** - @brief Utility class for constructing GitHub Sign In credentials. - */ +/// Utility class for constructing GitHub Sign In credentials. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @objc(FIRGitHubAuthProvider) open class GitHubAuthProvider: NSObject { + /// A string constant identifying the GitHub identity provider. @objc public static let id = "github.com" - /** - @brief Creates an `AuthCredential` for a GitHub sign in. - - @param token The GitHub OAuth access token. - @return An AuthCredential containing the GitHub credentials. - */ + /// Creates an `AuthCredential` for a GitHub sign in. + /// - Parameter token: The GitHub OAuth access token. + /// - Returns: An AuthCredential containing the GitHub credentials. @objc open class func credential(withToken token: String) -> AuthCredential { return GitHubAuthCredential(withToken: token) } diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/GoogleAuthProvider.swift b/FirebaseAuth/Sources/Swift/AuthProvider/GoogleAuthProvider.swift index ce198358803..b4f3b806837 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/GoogleAuthProvider.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/GoogleAuthProvider.swift @@ -14,23 +14,19 @@ import Foundation -/** - @brief Utility class for constructing Google Sign In credentials. - */ +/// Utility class for constructing Google Sign In credentials. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @objc(FIRGoogleAuthProvider) open class GoogleAuthProvider: NSObject { + /// A string constant identifying the Google identity provider. @objc public static let id = "google.com" - /** - @brief Creates an `AuthCredential` for a Google sign in. - - @param IDToken The ID Token from Google. - @param accessToken The Access Token from Google. - @return An AuthCredential containing the Google credentials. - */ - @objc open class func credential(withIDToken IDToken: String, + /// Creates an `AuthCredential` for a Google sign in. + /// - Parameter idToken: The ID Token from Google. + /// - Parameter accessToken: The Access Token from Google. + /// - Returns: An AuthCredential containing the Google credentials. + @objc open class func credential(withIDToken idToken: String, accessToken: String) -> AuthCredential { - return GoogleAuthCredential(withIDToken: IDToken, accessToken: accessToken) + return GoogleAuthCredential(withIDToken: idToken, accessToken: accessToken) } @available(*, unavailable) diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/OAuthCredential.swift b/FirebaseAuth/Sources/Swift/AuthProvider/OAuthCredential.swift index 8d21df49cff..a7fc244e16e 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/OAuthCredential.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/OAuthCredential.swift @@ -14,23 +14,19 @@ import Foundation +/// Internal implementation of `AuthCredential` for generic credentials. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @objc(FIROAuthCredential) open class OAuthCredential: AuthCredential, NSSecureCoding { - /** @property IDToken - @brief The ID Token associated with this credential. - */ + /// The ID Token associated with this credential. @objc(IDToken) public let idToken: String? - /** @property accessToken - @brief The access token associated with this credential. - */ + /// The access token associated with this credential. @objc public let accessToken: String? - /** @property secret - @brief The secret associated with this credential. This will be nil for OAuth 2.0 providers. - @detail OAuthCredential already exposes a providerId getter. This will help the developer - determine whether an access token/secret pair is needed. - */ + /// The secret associated with this credential. This will be nil for OAuth 2.0 providers. + /// + /// OAuthCredential already exposes a `provider` getter. This will help the developer + /// determine whether an access token / secret pair is needed. @objc public let secret: String? // internal diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/OAuthProvider.swift b/FirebaseAuth/Sources/Swift/AuthProvider/OAuthProvider.swift index 2cdb8b33611..1566513855f 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/OAuthProvider.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/OAuthProvider.swift @@ -15,55 +15,40 @@ import CommonCrypto import Foundation -/** - @brief Utility class for constructing OAuth Sign In credentials. - */ +/// Utility class for constructing OAuth Sign In credentials. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @objc(FIROAuthProvider) open class OAuthProvider: NSObject, FederatedAuthProvider { @objc public static let id = "OAuth" - /** @property scopes - @brief Array used to configure the OAuth scopes. - */ + /// Array used to configure the OAuth scopes. @objc open var scopes: [String]? - /** @property customParameters - @brief Dictionary used to configure the OAuth custom parameters. - */ + /// Dictionary used to configure the OAuth custom parameters. @objc open var customParameters: [String: String]? - /** @property providerID - @brief The provider ID indicating the specific OAuth provider this OAuthProvider instance - represents. - */ + /// The provider ID indicating the specific OAuth provider this OAuthProvider instance represents. @objc public let providerID: String - /** - @param providerID The provider ID of the IDP for which this auth provider instance will be - configured. - @return An instance of `OAuthProvider` corresponding to the specified provider ID. - */ + /// - Parameter providerID: The provider ID of the IDP for which this auth provider instance will + /// be configured. + /// - Returns: An instance of OAuthProvider corresponding to the specified provider ID. @objc(providerWithProviderID:) open class func provider(providerID: String) -> OAuthProvider { return OAuthProvider(providerID: providerID, auth: Auth.auth()) } - /** - @param providerID The provider ID of the IDP for which this auth provider instance will be - configured. - @param auth The auth instance to be associated with the `OAuthProvider` instance. - @return An instance of `OAuthProvider` corresponding to the specified provider ID. - */ + /// - Parameter providerID: The provider ID of the IDP for which this auth provider instance will + /// be configured. + /// - Parameter auth: The auth instance to be associated with the OAuthProvider instance. + /// - Returns: An instance of OAuthProvider corresponding to the specified provider ID. @objc(providerWithProviderID:auth:) open class func provider(providerID: String, auth: Auth) -> OAuthProvider { return OAuthProvider(providerID: providerID, auth: auth) } - /** - @param providerID The provider ID of the IDP for which this auth provider instance will be - configured. - @param auth The auth instance to be associated with the `OAuthProvider` instance. - @return An instance of `OAuthProvider` corresponding to the specified provider ID. - */ + /// - Parameter providerID: The provider ID of the IDP for which this auth provider instance will + /// be configured. + /// - Parameter auth: The auth instance to be associated with the OAuthProvider instance. + /// - Returns: An instance of OAuthProvider corresponding to the specified provider ID. public init(providerID: String, auth: Auth = Auth.auth()) { if auth.requestConfiguration.emulatorHostAndPort == nil { if providerID == FacebookAuthProvider.id { @@ -99,16 +84,13 @@ import Foundation } } - /** - @brief Creates an `AuthCredential` for the OAuth 2 provider identified by provider ID, ID - token, and access token. - - @param providerID The provider ID associated with the Auth credential being created. - @param idToken The IDToken associated with the Auth credential being created. - @param accessToken The access token associated with the Auth credential be created, if - available. - @return A `AuthCredential` for the specified provider ID, ID token and access token. - */ + /// Creates an `AuthCredential` for the OAuth 2 provider identified by provider ID, ID + /// token, and access token. + /// - Parameter providerID: The provider ID associated with the Auth credential being created. + /// - Parameter idToken: The IDToken associated with the Auth credential being created. + /// - Parameter accessToken: The access token associated with the Auth credential be created, if + /// available. + /// - Returns: An AuthCredential for the specified provider ID, ID token and access token. @objc(credentialWithProviderID:IDToken:accessToken:) public static func credential(withProviderID providerID: String, idToken: String, @@ -116,31 +98,25 @@ import Foundation return OAuthCredential(withProviderID: providerID, idToken: idToken, accessToken: accessToken) } - /** - @brief Creates an `AuthCredential` for the OAuth 2 provider identified by provider ID using - an ID token. - - @param providerID The provider ID associated with the Auth credential being created. - @param accessToken The access token associated with the Auth credential be created - @return An `AuthCredential`. - */ + /// Creates an `AuthCredential` for the OAuth 2 provider identified by provider ID using + /// an ID token. + /// - Parameter providerID: The provider ID associated with the Auth credential being created. + /// - Parameter accessToken: The access token associated with the Auth credential be created + /// - Returns: An AuthCredential. @objc(credentialWithProviderID:accessToken:) public static func credential(withProviderID providerID: String, accessToken: String) -> OAuthCredential { return OAuthCredential(withProviderID: providerID, accessToken: accessToken) } - /** - @brief Creates an `AuthCredential` for that OAuth 2 provider identified by provider ID, ID - token, raw nonce, and access token. - - @param providerID The provider ID associated with the Auth credential being created. - @param idToken The IDToken associated with the Auth credential being created. - @param rawNonce The raw nonce associated with the Auth credential being created. - @param accessToken The access token associated with the Auth credential be created, if - available. - @return A `AuthCredential` for the specified provider ID, ID token and access token. - */ + /// Creates an `AuthCredential` for that OAuth 2 provider identified by provider ID, ID + /// token, raw nonce, and access token. + /// - Parameter providerID: The provider ID associated with the Auth credential being created. + /// - Parameter idToken: The IDToken associated with the Auth credential being created. + /// - Parameter rawNonce: The raw nonce associated with the Auth credential being created. + /// - Parameter accessToken: The access token associated with the Auth credential be created, if + /// available. + /// - Returns: An AuthCredential for the specified provider ID, ID token and access token. @objc(credentialWithProviderID:IDToken:rawNonce:accessToken:) public static func credential(withProviderID providerID: String, idToken: String, rawNonce: String, @@ -153,15 +129,12 @@ import Foundation ) } - /** - @brief Creates an `AuthCredential` for that OAuth 2 provider identified by providerID using - an ID token and raw nonce. - - @param providerID The provider ID associated with the Auth credential being created. - @param idToken The IDToken associated with the Auth credential being created. - @param rawNonce The raw nonce associated with the Auth credential being created. - @return A `AuthCredential`. - */ + /// Creates an `AuthCredential` for that OAuth 2 provider identified by providerID using + /// an ID token and raw nonce. + /// - Parameter providerID: The provider ID associated with the Auth credential being created. + /// - Parameter idToken: The IDToken associated with the Auth credential being created. + /// - Parameter rawNonce: The raw nonce associated with the Auth credential being created. + /// - Returns: An AuthCredential. @objc(credentialWithProviderID:IDToken:rawNonce:) public static func credential(withProviderID providerID: String, idToken: String, rawNonce: String) -> OAuthCredential { @@ -169,13 +142,12 @@ import Foundation } #if os(iOS) - /** @fn getCredentialWithUIDelegate:completion: - @brief Used to obtain an auth credential via a mobile web flow. - This method is available on iOS only. - @param uiDelegate An optional UI delegate used to present the mobile web flow. - @param completion Optionally; a block which is invoked asynchronously on the main thread when - the mobile web flow is completed. - */ + /// Used to obtain an auth credential via a mobile web flow. + /// + /// This method is available on iOS only. + /// - Parameter uiDelegate: An optional UI delegate used to present the mobile web flow. + /// - Parameter completion: Optionally; a block which is invoked asynchronously on the main + /// thread when the mobile web flow is completed. open func getCredentialWith(_ uiDelegate: AuthUIDelegate?, completion: ((AuthCredential?, Error?) -> Void)? = nil) { guard let urlTypes = auth.mainBundleUrlTypes, @@ -243,11 +215,9 @@ import Foundation } } - /** @fn getCredentialWithUIDelegate:completion: - @brief Used to obtain an auth credential via a mobile web flow. - This method is available on iOS only. - @param uiDelegate An optional UI delegate used to present the mobile web flow. - */ + /// Used to obtain an auth credential via a mobile web flow. + /// This method is available on iOS only. + /// - Parameter uiDelegate: An optional UI delegate used to present the mobile web flow. @available(iOS 13, tvOS 13, macOS 10.15, watchOS 8, *) @objc(getCredentialWithUIDelegate:completion:) open func credential(with uiDelegate: AuthUIDelegate?) async throws -> AuthCredential { @@ -263,18 +233,16 @@ import Foundation } #endif - /** @fn appleCredentialWithIDToken:rawNonce:fullName: - * @brief Creates an `AuthCredential` for the Sign in with Apple OAuth 2 provider identified by ID - * token, raw nonce, and full name. This method is specific to the Sign in with Apple OAuth 2 - * provider as this provider requires the full name to be passed explicitly. - * - * @param idToken The IDToken associated with the Sign in with Apple Auth credential being created. - * @param rawNonce The raw nonce associated with the Sign in with Apple Auth credential being - * created. - * @param fullName The full name associated with the Sign in with Apple Auth credential being - * created. - * @return An `AuthCredential`. - */ + /// Creates an `AuthCredential` for the Sign in with Apple OAuth 2 provider identified by ID + /// token, raw nonce, and full name.This method is specific to the Sign in with Apple OAuth 2 + /// provider as this provider requires the full name to be passed explicitly. + /// - Parameter idToken: The IDToken associated with the Sign in with Apple Auth credential being + /// created. + /// - Parameter rawNonce: The raw nonce associated with the Sign in with Apple Auth credential + /// being created. + /// - Parameter fullName: The full name associated with the Sign in with Apple Auth credential + /// being created. + /// - Returns: An AuthCredential. @objc(appleCredentialWithIDToken:rawNonce:fullName:) public static func appleCredential(withIDToken idToken: String, rawNonce: String?, @@ -287,12 +255,9 @@ import Foundation // MARK: - Private Methods - /** @fn OAuthResponseForURL:error: - @brief Parses the redirected URL and returns a string representation of the OAuth response URL. - @param URL The url to be parsed for an OAuth response URL. - @param error The error that occurred if any. - @return The OAuth response if successful. - */ + /// Parses the redirected URL and returns a string representation of the OAuth response URL. + /// - Parameter url: The url to be parsed for an OAuth response URL. + /// - Returns: The OAuth response if successful. private func oAuthResponseForURL(url: URL) -> (String?, Error?) { var urlQueryItems = AuthWebUtils.dictionary(withHttpArgumentsString: url.query) if let item = urlQueryItems["deep_link_id"], @@ -317,14 +282,11 @@ import Foundation )) } - /** @fn getHeadfulLiteURLWithEventID - @brief Constructs a URL used for opening a headful-lite flow using a given event - ID and session ID. - @param eventID The event ID used for this purpose. - @param sessionID The session ID used when completing the headful lite flow. - @param completion The callback invoked after the URL has been constructed or an error - has been encountered. - */ + /// Constructs a URL used for opening a headful-lite flow using a given event + /// ID and session ID. + /// - Parameter eventID: The event ID used for this purpose. + /// - Parameter sessionID: The session ID used when completing the headful lite flow. + /// - Returns: A url. private func getHeadfulLiteUrl(eventID: String, sessionID: String) async throws -> URL? { let authDomain = try await AuthWebUtils @@ -395,11 +357,9 @@ import Foundation return components?.url } - /** @fn hashforString: - @brief Returns the SHA256 hash representation of a given string object. - @param string The string for which a SHA256 hash is desired. - @return An hexadecimal string representation of the SHA256 hash. - */ + /// Returns the SHA256 hash representation of a given string object. + /// - Parameter string: The string for which a SHA256 hash is desired. + /// - Returns: An hexadecimal string representation of the SHA256 hash. private func hash(forString string: String) -> String { guard let sessionIdData = string.data(using: .utf8) as? NSData else { fatalError("FirebaseAuth Internal error: Failed to create hash for sessionID") diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthCredential.swift b/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthCredential.swift index 0b55c5979e7..2b42df11e7d 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthCredential.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthCredential.swift @@ -14,10 +14,9 @@ import Foundation -/** @class PhoneAuthCredential - @brief Implementation of FIRAuthCredential for Phone Auth credentials. - This class is available on iOS only. - */ +/// Implementation of AuthCredential for Phone Auth credentials. +/// +/// This class is available on iOS only. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @objc(FIRPhoneAuthCredential) open class PhoneAuthCredential: AuthCredential, NSSecureCoding { enum CredentialKind { diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift b/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift index f6bd4d1228e..61a78271347 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift @@ -15,47 +15,40 @@ import FirebaseCore import Foundation -/** - @brief A concrete implementation of `AuthProvider` for phone auth providers. - This class is available on iOS only. - */ +/// A concrete implementation of `AuthProvider` for phone auth providers. +/// +/// This class is available on iOS only. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @objc(FIRPhoneAuthProvider) open class PhoneAuthProvider: NSObject { + /// A string constant identifying the phone identity provider. @objc public static let id = "phone" #if os(iOS) - /** - @brief Returns an instance of `PhoneAuthProvider` for the default `Auth` object. - */ + /// Returns an instance of `PhoneAuthProvider` for the default `Auth` object. @objc(provider) open class func provider() -> PhoneAuthProvider { return PhoneAuthProvider(auth: Auth.auth()) } - /** - @brief Returns an instance of `PhoneAuthProvider` for the provided `Auth` object. - @param auth The auth object to associate with the phone auth provider instance. - */ + /// Returns an instance of `PhoneAuthProvider` for the provided `Auth` object. + /// - Parameter auth: The auth object to associate with the phone auth provider instance. @objc(providerWithAuth:) open class func provider(auth: Auth) -> PhoneAuthProvider { return PhoneAuthProvider(auth: auth) } - /** - @brief Starts the phone number authentication flow by sending a verification code to the - specified phone number. - @param phoneNumber The phone number to be verified. - @param uiDelegate An object used to present the SFSafariViewController. The object is retained - by this method until the completion block is executed. - @param completion The callback to be invoked when the verification flow is finished. - @remarks Possible error codes: - - + `AuthErrorCodeCaptchaCheckFailed` - Indicates that the reCAPTCHA token obtained by - the Firebase Auth is invalid or has expired. - + `AuthErrorCodeQuotaExceeded` - Indicates that the phone verification quota for this - project has been exceeded. - + `AuthErrorCodeInvalidPhoneNumber` - Indicates that the phone number provided is - invalid. - + `AuthErrorCodeMissingPhoneNumber` - Indicates that a phone number was not provided. - */ + /// Starts the phone number authentication flow by sending a verification code to the + /// specified phone number. + /// + /// Possible error codes: + /// * `AuthErrorCodeCaptchaCheckFailed` - Indicates that the reCAPTCHA token obtained by + /// the Firebase Auth is invalid or has expired. + /// * `AuthErrorCodeQuotaExceeded` - Indicates that the phone verification quota for this + /// project has been exceeded. + /// * `AuthErrorCodeInvalidPhoneNumber` - Indicates that the phone number provided is invalid. + /// * `AuthErrorCodeMissingPhoneNumber` - Indicates that a phone number was not provided. + /// - Parameter phoneNumber: The phone number to be verified. + /// - Parameter uiDelegate: An object used to present the SFSafariViewController. The object is + /// retained by this method until the completion block is executed. + /// - Parameter completion: The callback to be invoked when the verification flow is finished. @objc(verifyPhoneNumber:UIDelegate:completion:) open func verifyPhoneNumber(_ phoneNumber: String, uiDelegate: AuthUIDelegate? = nil, @@ -66,16 +59,14 @@ import Foundation completion: completion) } - /** - @brief Verify ownership of the second factor phone number by the current user. - @param phoneNumber The phone number to be verified. - @param uiDelegate An object used to present the SFSafariViewController. The object is retained - by this method until the completion block is executed. - @param multiFactorSession A session to identify the MFA flow. For enrollment, this identifies the user - trying to enroll. For sign-in, this identifies that the user already passed the first - factor challenge. - @param completion The callback to be invoked when the verification flow is finished. - */ + /// Verify ownership of the second factor phone number by the current user. + /// - Parameter phoneNumber: The phone number to be verified. + /// - Parameter uiDelegate: An object used to present the SFSafariViewController. The object is + /// retained by this method until the completion block is executed. + /// - Parameter multiFactorSession: A session to identify the MFA flow. For enrollment, this + /// identifies the user trying to enroll. For sign-in, this identifies that the user already + /// passed the first factor challenge. + /// - Parameter completion: The callback to be invoked when the verification flow is finished. @objc(verifyPhoneNumber:UIDelegate:multiFactorSession:completion:) open func verifyPhoneNumber(_ phoneNumber: String, uiDelegate: AuthUIDelegate? = nil, @@ -103,16 +94,14 @@ import Foundation } } - /** - @brief Verify ownership of the second factor phone number by the current user. - @param phoneNumber The phone number to be verified. - @param uiDelegate An object used to present the SFSafariViewController. The object is retained - by this method until the completion block is executed. - @param multiFactorSession A session to identify the MFA flow. For enrollment, this identifies the user - trying to enroll. For sign-in, this identifies that the user already passed the first - factor challenge. - @returns The verification ID - */ + /// Verify ownership of the second factor phone number by the current user. + /// - Parameter phoneNumber: The phone number to be verified. + /// - Parameter uiDelegate: An object used to present the SFSafariViewController. The object is + /// retained by this method until the completion block is executed. + /// - Parameter multiFactorSession: A session to identify the MFA flow. For enrollment, this + /// identifies the user trying to enroll. For sign-in, this identifies that the user already + /// passed the first factor challenge. + /// - Returns: The verification ID @available(iOS 13, tvOS 13, macOS 10.15, watchOS 8, *) open func verifyPhoneNumber(_ phoneNumber: String, uiDelegate: AuthUIDelegate? = nil, @@ -131,16 +120,14 @@ import Foundation } } - /** - @brief Verify ownership of the second factor phone number by the current user. - @param multiFactorInfo The phone multi factor whose number need to be verified. - @param uiDelegate An object used to present the SFSafariViewController. The object is retained - by this method until the completion block is executed. - @param multiFactorSession A session to identify the MFA flow. For enrollment, this identifies the user - trying to enroll. For sign-in, this identifies that the user already passed the first - factor challenge. - @param completion The callback to be invoked when the verification flow is finished. - */ + /// Verify ownership of the second factor phone number by the current user. + /// - Parameter multiFactorInfo: The phone multi factor whose number need to be verified. + /// - Parameter uiDelegate: An object used to present the SFSafariViewController. The object is + /// retained by this method until the completion block is executed. + /// - Parameter multiFactorSession: A session to identify the MFA flow. For enrollment, this + /// identifies the user trying to enroll. For sign-in, this identifies that the user already + /// passed the first factor challenge. + /// - Parameter completion: The callback to be invoked when the verification flow is finished. @objc(verifyPhoneNumberWithMultiFactorInfo:UIDelegate:multiFactorSession:completion:) open func verifyPhoneNumber(with multiFactorInfo: PhoneMultiFactorInfo, uiDelegate: AuthUIDelegate? = nil, @@ -153,6 +140,14 @@ import Foundation completion: completion) } + /// Verify ownership of the second factor phone number by the current user. + /// - Parameter multiFactorInfo: The phone multi factor whose number need to be verified. + /// - Parameter uiDelegate: An object used to present the SFSafariViewController. The object is + /// retained by this method until the completion block is executed. + /// - Parameter multiFactorSession: A session to identify the MFA flow. For enrollment, this + /// identifies the user trying to enroll. For sign-in, this identifies that the user already + /// passed the first factor challenge. + /// - Returns: The verification ID. @available(iOS 13, tvOS 13, macOS 10.15, watchOS 8, *) open func verifyPhoneNumber(with multiFactorInfo: PhoneMultiFactorInfo, uiDelegate: AuthUIDelegate? = nil, @@ -170,16 +165,14 @@ import Foundation } } - /** - @brief Creates an `AuthCredential` for the phone number provider identified by the - verification ID and verification code. - - @param verificationID The verification ID obtained from invoking - verifyPhoneNumber:completion: - @param verificationCode The verification code obtained from the user. - @return The corresponding phone auth credential for the verification ID and verification code - provided. - */ + /// Creates an `AuthCredential` for the phone number provider identified by the + /// verification ID and verification code. + /// + /// - Parameter verificationID: The verification ID obtained from invoking + /// verifyPhoneNumber:completion: + /// - Parameter verificationCode: The verification code obtained from the user. + /// - Returns: The corresponding phone auth credential for the verification ID and verification + /// code provided. @objc(credentialWithVerificationID:verificationCode:) open func credential(withVerificationID verificationID: String, verificationCode: String) -> PhoneAuthCredential { @@ -207,14 +200,12 @@ import Foundation uiDelegate: uiDelegate) } - /** @fn - @brief Starts the flow to verify the client via silent push notification. - @param retryOnInvalidAppCredential Whether of not the flow should be retried if an - AuthErrorCodeInvalidAppCredential error is returned from the backend. - @param phoneNumber The phone number to be verified. - @param callback The callback to be invoked on the global work queue when the flow is - finished. - */ + /// Starts the flow to verify the client via silent push notification. + /// - Parameter retryOnInvalidAppCredential: Whether of not the flow should be retried if an + /// AuthErrorCodeInvalidAppCredential error is returned from the backend. + /// - Parameter phoneNumber: The phone number to be verified. + /// - Parameter callback: The callback to be invoked on the global work queue when the flow is + /// finished. private func verifyClAndSendVerificationCode(toPhoneNumber phoneNumber: String, retryOnInvalidAppCredential: Bool, uiDelegate: AuthUIDelegate?) async throws @@ -237,14 +228,10 @@ import Foundation } } - /** @fn - @brief Starts the flow to verify the client via silent push notification. - @param retryOnInvalidAppCredential Whether of not the flow should be retried if an - AuthErrorCodeInvalidAppCredential error is returned from the backend. - @param phoneNumber The phone number to be verified. - @param callback The callback to be invoked on the global work queue when the flow is - finished. - */ + /// Starts the flow to verify the client via silent push notification. + /// - Parameter retryOnInvalidAppCredential: Whether of not the flow should be retried if an + /// AuthErrorCodeInvalidAppCredential error is returned from the backend. + /// - Parameter phoneNumber: The phone number to be verified. private func verifyClAndSendVerificationCode(toPhoneNumber phoneNumber: String, retryOnInvalidAppCredential: Bool, multiFactorSession session: MultiFactorSession?, @@ -316,10 +303,7 @@ import Foundation throw error } - /** @fn - @brief Continues the flow to verify the client via silent push notification. - @param completion The callback to be invoked when the client verification flow is finished. - */ + /// Continues the flow to verify the client via silent push notification. private func verifyClient(withUIDelegate uiDelegate: AuthUIDelegate?) async throws -> CodeIdentity { // Remove the simulator check below after FCM supports APNs in simulators @@ -374,10 +358,7 @@ import Foundation } } - /** @fn - @brief Continues the flow to verify the client via silent push notification. - @param completion The callback to be invoked when the client verification flow is finished. - */ + /// Continues the flow to verify the client via silent push notification. private func reCAPTCHAFlowWithUIDelegate(withUIDelegate uiDelegate: AuthUIDelegate?) async throws -> String { let eventID = AuthWebUtils.randomString(withLength: 10) @@ -412,12 +393,9 @@ import Foundation } } - /** - @brief Parses the reCAPTCHA URL and returns the reCAPTCHA token. - @param URL The url to be parsed for a reCAPTCHA token. - @param error The error that occurred if any. - @return The reCAPTCHA token if successful. - */ + /// Parses the reCAPTCHA URL and returns the reCAPTCHA token. + /// - Parameter url: The url to be parsed for a reCAPTCHA token. + /// - Returns: The reCAPTCHA token if successful. private func reCAPTCHAToken(forURL url: URL?) throws -> String { guard let url = url else { let reason = "Internal Auth Error: nil URL trying to access RECAPTCHA token" @@ -457,13 +435,8 @@ import Foundation throw AuthErrorUtils.appVerificationUserInteractionFailure(reason: reason) } - /** @fn - @brief Constructs a URL used for opening a reCAPTCHA app verification flow using a given event - ID. - @param eventID The event ID used for this purpose. - @param completion The callback invoked after the URL has been constructed or an error - has been encountered. - */ + /// Constructs a URL used for opening a reCAPTCHA app verification flow using a given event ID. + /// - Parameter eventID: The event ID used for this purpose. private func reCAPTCHAURL(withEventID eventID: String) async throws -> URL? { let authDomain = try await AuthWebUtils .fetchAuthDomain(withRequestConfiguration: auth.requestConfiguration) @@ -505,7 +478,7 @@ import Foundation private let callbackScheme: String private let usingClientIDScheme: Bool - private init(auth: Auth) { + init(auth: Auth) { self.auth = auth if let clientID = auth.app?.options.clientID { let reverseClientIDScheme = clientID.components(separatedBy: ".").reversed() diff --git a/FirebaseAuth/Sources/Swift/AuthProvider/TwitterAuthProvider.swift b/FirebaseAuth/Sources/Swift/AuthProvider/TwitterAuthProvider.swift index dd1984892d1..177b08ed894 100644 --- a/FirebaseAuth/Sources/Swift/AuthProvider/TwitterAuthProvider.swift +++ b/FirebaseAuth/Sources/Swift/AuthProvider/TwitterAuthProvider.swift @@ -14,20 +14,16 @@ import Foundation -/** - @brief Utility class for constructing Twitter Sign In credentials. - */ +/// Utility class for constructing Twitter Sign In credentials. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @objc(FIRTwitterAuthProvider) open class TwitterAuthProvider: NSObject { + /// A string constant identifying the Twitter identity provider. @objc public static let id = "twitter.com" - /** - @brief Creates an `AuthCredential` for a Twitter sign in. - - @param token The Twitter OAuth token. - @param secret The Twitter OAuth secret. - @return An AuthCredential containing the Twitter credentials. - */ + /// Creates an `AuthCredential` for a Twitter sign in. + /// - Parameter token: The Twitter OAuth token. + /// - Parameter secret: The Twitter OAuth secret. + /// - Returns: An AuthCredential containing the Twitter credentials. @objc open class func credential(withToken token: String, secret: String) -> AuthCredential { return TwitterAuthCredential(withToken: token, secret: secret) } diff --git a/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift b/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift index ba7bb4e6137..8aa81c56419 100644 --- a/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift +++ b/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift @@ -24,15 +24,12 @@ import Foundation @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) protocol AuthBackendRPCIssuer: NSObjectProtocol { - /** @fn - @brief Asynchronously sends a HTTP request. - @param requestConfiguration The request to be made. - @param URL The request URL. - @param body Request body. - @param contentType Content type of the body. - @param handler provided that handles HTTP response. Invoked asynchronously on the auth global - work queue in the future. - */ + /// Asynchronously send a HTTP request. + /// - Parameter request: The request to be made. + /// - Parameter body: Request body. + /// - Parameter contentType: Content type of the body. + /// - Parameter completionHandler: Handles HTTP response. Invoked asynchronously + /// on the auth global work queue in the future. func asyncCallToURL(with request: T, body: Data?, contentType: String, @@ -152,19 +149,16 @@ private class AuthBackendRPCImplementation: NSObject, AuthBackendImplementation rpcIssuer = AuthBackendRPCIssuerImplementation() } - /** @fn call - @brief Calls the RPC using HTTP request. - @remarks Possible error responses: - @see FIRAuthInternalErrorCodeRPCRequestEncodingError - @see FIRAuthInternalErrorCodeJSONSerializationError - @see FIRAuthInternalErrorCodeNetworkError - @see FIRAuthInternalErrorCodeUnexpectedErrorResponse - @see FIRAuthInternalErrorCodeUnexpectedResponse - @see FIRAuthInternalErrorCodeRPCResponseDecodingError - @param request The request. - @param response The empty response to be filled. - @param callback The callback for both success and failure. - */ + /// Calls the RPC using HTTP request. + /// Possible error responses: + /// * See FIRAuthInternalErrorCodeRPCRequestEncodingError + /// * See FIRAuthInternalErrorCodeJSONSerializationError + /// * See FIRAuthInternalErrorCodeNetworkError + /// * See FIRAuthInternalErrorCodeUnexpectedErrorResponse + /// * See FIRAuthInternalErrorCodeUnexpectedResponse + /// * See FIRAuthInternalErrorCodeRPCResponseDecodingError + /// - Parameter request: The request. + /// - Returns: The response. fileprivate func call(with request: T) async throws -> T.Response { let response = try await callInternal(with: request) if let auth = request.requestConfiguration().auth, @@ -233,19 +227,17 @@ private class AuthBackendRPCImplementation: NSObject, AuthBackendImplementation } #endif - /** @fn call - @brief Calls the RPC using HTTP request. - @remarks Possible error responses: - @see FIRAuthInternalErrorCodeRPCRequestEncodingError - @see FIRAuthInternalErrorCodeJSONSerializationError - @see FIRAuthInternalErrorCodeNetworkError - @see FIRAuthInternalErrorCodeUnexpectedErrorResponse - @see FIRAuthInternalErrorCodeUnexpectedResponse - @see FIRAuthInternalErrorCodeRPCResponseDecodingError - @param request The request. - @param response The empty response to be filled. - @param callback The callback for both success and failure. - */ + /// Calls the RPC using HTTP request. + /// + /// Possible error responses: + /// * See FIRAuthInternalErrorCodeRPCRequestEncodingError + /// * See FIRAuthInternalErrorCodeJSONSerializationError + /// * See FIRAuthInternalErrorCodeNetworkError + /// * See FIRAuthInternalErrorCodeUnexpectedErrorResponse + /// * See FIRAuthInternalErrorCodeUnexpectedResponse + /// * See FIRAuthInternalErrorCodeRPCResponseDecodingError + /// - Parameter request: The request. + /// - Returns: The response. fileprivate func callInternal(with request: T) async throws -> T.Response { var bodyData: Data? if request.containsPostBody { diff --git a/FirebaseAuth/Sources/Swift/Backend/AuthRPCResponse.swift b/FirebaseAuth/Sources/Swift/Backend/AuthRPCResponse.swift index 02651cf818a..b1284762fb2 100644 --- a/FirebaseAuth/Sources/Swift/Backend/AuthRPCResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/AuthRPCResponse.swift @@ -18,21 +18,17 @@ protocol AuthRPCResponse { /// Bare initializer for a response. init() - /** @fn setFieldsWithDictionary:error: - @brief Sets the response instance from the decoded JSON response. - @param dictionary The dictionary decoded from HTTP JSON response. - @param error An out field for an error which occurred constructing the request. - @return Whether the operation was successful or not. - */ + /// Sets the response instance from the decoded JSON response. + /// - Parameter dictionary: The dictionary decoded from HTTP JSON response. + /// - Parameter error: An out field for an error which occurred constructing the request. + /// - Returns: Whether the operation was successful or not. func setFields(dictionary: [String: AnyHashable]) throws - /** @fn clientErrorWithshortErrorMessage:detailErrorMessage - @brief This optional method allows response classes to create client errors given a short error - message and a detail error message from the server. - @param shortErrorMessage The short error message from the server. - @param detailErrorMessage The detailed error message from the server. - @return A client error, if any. - */ + /// This optional method allows response classes to create client errors given a short error + /// message and a detail error message from the server. + /// - Parameter shortErrorMessage: The short error message from the server. + /// - Parameter detailErrorMessage: The detailed error message from the server. + /// - Returns: A client error, if any. func clientError(shortErrorMessage: String, detailedErrorMessage: String?) -> Error? } diff --git a/FirebaseAuth/Sources/Swift/Backend/AuthRequestConfiguration.swift b/FirebaseAuth/Sources/Swift/Backend/AuthRequestConfiguration.swift index 80ce4a79ac7..9aa53d7e68f 100644 --- a/FirebaseAuth/Sources/Swift/Backend/AuthRequestConfiguration.swift +++ b/FirebaseAuth/Sources/Swift/Backend/AuthRequestConfiguration.swift @@ -17,52 +17,34 @@ import Foundation import FirebaseAppCheckInterop import FirebaseCoreExtension -/** @class FIRAuthRequestConfiguration - @brief Defines configurations to be added to a request to Firebase Auth's backend. - */ +/// Defines configurations to be added to a request to Firebase Auth's backend. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) class AuthRequestConfiguration: NSObject { - /** @property APIKey - @brief The Firebase Auth API key used in the request. - */ + /// The Firebase Auth API key used in the request. let apiKey: String - /** @property LanguageCode - @brief The language code used in the request. - */ + /// The language code used in the request. var languageCode: String? - /** @property appID - @brief The Firebase appID used in the request. - */ + /// The Firebase appID used in the request. let appID: String - /** @property auth - @brief The FIRAuth instance used in the request. - */ + /// The `Auth` instance used in the request. weak var auth: Auth? /// The heartbeat logger used to add heartbeats to the corresponding request's header. var heartbeatLogger: FIRHeartbeatLoggerProtocol? - /** @property appCheck - @brief The appCheck is used to generate a token. - */ + /// The appCheck is used to generate a token. var appCheck: AppCheckInterop? - /** @property HTTPMethod - @brief The HTTP method used in the request. - */ + /// The HTTP method used in the request. var httpMethod: String - /** @property additionalFrameworkMarker - @brief Additional framework marker that will be added as part of the header of every request. - */ + /// Additional framework marker that will be added as part of the header of every request. var additionalFrameworkMarker: String? - /** @property emulatorHostAndPort - @brief If set, the local emulator host and port to point to instead of the remote backend. - */ + /// If set, the local emulator host and port to point to instead of the remote backend. var emulatorHostAndPort: String? init(apiKey: String, diff --git a/FirebaseAuth/Sources/Swift/Backend/IdentityToolkitRequest.swift b/FirebaseAuth/Sources/Swift/Backend/IdentityToolkitRequest.swift index b31292c807e..ebc549b3288 100644 --- a/FirebaseAuth/Sources/Swift/Backend/IdentityToolkitRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/IdentityToolkitRequest.swift @@ -40,19 +40,13 @@ class IdentityToolkitRequest { /// The tenant ID of the request. nil if none is available. let tenantID: String? - /** @property useIdentityPlatform - @brief The toggle of using Identity Platform endpoints. - */ + /// The toggle of using Identity Platform endpoints. let useIdentityPlatform: Bool - /** @property useStaging - @brief The toggle of using staging endpoints. - */ + /// The toggle of using staging endpoints. let useStaging: Bool - /** @property clientType - @brief The type of the client that the request sent from, which should be CLIENT_TYPE_IOS; - */ + /// The type of the client that the request sent from, which should be CLIENT_TYPE_IOS; var clientType: String private let _requestConfiguration: AuthRequestConfiguration diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/CreateAuthURIRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/CreateAuthURIRequest.swift index 6fadfcfde04..1a7503ced9a 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/CreateAuthURIRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/CreateAuthURIRequest.swift @@ -14,95 +14,61 @@ import Foundation -/** @var kCreateAuthURIEndpoint - @brief The "createAuthUri" endpoint. - */ +/// The "createAuthUri" endpoint. private let kCreateAuthURIEndpoint = "createAuthUri" -/** @var kProviderIDKey - @brief The key for the "providerId" value in the request. - */ +/// The key for the "providerId" value in the request. private let kProviderIDKey = "providerId" -/** @var kIdentifierKey - @brief The key for the "identifier" value in the request. - */ +/// The key for the "identifier" value in the request. private let kIdentifierKey = "identifier" -/** @var kContinueURIKey - @brief The key for the "continueUri" value in the request. - */ +/// The key for the "continueUri" value in the request. private let kContinueURIKey = "continueUri" -/** @var kOpenIDRealmKey - @brief The key for the "openidRealm" value in the request. - */ +/// The key for the "openidRealm" value in the request. private let kOpenIDRealmKey = "openidRealm" -/** @var kClientIDKey - @brief The key for the "clientId" value in the request. - */ +/// The key for the "clientId" value in the request. private let kClientIDKey = "clientId" -/** @var kContextKey - @brief The key for the "context" value in the request. - */ +/// The key for the "context" value in the request. private let kContextKey = "context" -/** @var kAppIDKey - @brief The key for the "appId" value in the request. - */ +/// The key for the "appId" value in the request. private let kAppIDKey = "appId" -/** @var kTenantIDKey - @brief The key for the tenant id value in the request. - */ +/// The key for the tenant id value in the request. private let kTenantIDKey = "tenantId" -/** @class FIRCreateAuthURIRequest - @brief Represents the parameters for the createAuthUri endpoint. - @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/createAuthUri - */ +/// Represents the parameters for the createAuthUri endpoint. +/// See https://developers.google.com/identity/toolkit/web/reference/relyingparty/createAuthUri @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) class CreateAuthURIRequest: IdentityToolkitRequest, AuthRPCRequest { typealias Response = CreateAuthURIResponse - /** @property identifier - @brief The email or federated ID of the user. - */ + /// The email or federated ID of the user. let identifier: String - /** @property continueURI - @brief The URI to which the IDP redirects the user after the federated login flow. - */ + /// The URI to which the IDP redirects the user after the federated login flow. let continueURI: String - /** @property openIDRealm - @brief Optional realm for OpenID protocol. The sub string "scheme://domain:port" of the param - "continueUri" is used if this is not set. - */ + /// Optional realm for OpenID protocol. The sub string "scheme://domain:port" of the param + /// "continueUri" is used if this is not set. var openIDRealm: String? - /** @property providerID - @brief The IdP ID. For white listed IdPs it's a short domain name e.g. google.com, aol.com, - live.net and yahoo.com. For other OpenID IdPs it's the OP identifier. - */ + /// The IdP ID. For white listed IdPs it's a short domain name e.g. google.com, aol.com, + /// live.net and yahoo.com. For other OpenID IdPs it's the OP identifier. var providerID: String? - /** @property clientID - @brief The relying party OAuth client ID. - */ + /// The relying party OAuth client ID. var clientID: String? - /** @property context - @brief The opaque value used by the client to maintain context info between the authentication - request and the IDP callback. - */ + /// The opaque value used by the client to maintain context info between the authentication + /// request and the IDP callback. var context: String? - /** @property appID - @brief The iOS client application's bundle identifier. - */ + /// The iOS client application's bundle identifier. var appID: String? init(identifier: String, continueURI: String, diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/CreateAuthURIResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/CreateAuthURIResponse.swift index b5cdef16482..d80b09e1aaa 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/CreateAuthURIResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/CreateAuthURIResponse.swift @@ -14,40 +14,26 @@ import Foundation -/** @class FIRCreateAuthURIResponse - @brief Represents the parameters for the createAuthUri endpoint. - @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/createAuthUri - */ +/// Represents the parameters for the createAuthUri endpoint. +/// See https: // developers.google.com/identity/toolkit/web/reference/relyingparty/createAuthUri class CreateAuthURIResponse: AuthRPCResponse { - /** @property authUri - @brief The URI used by the IDP to authenticate the user. - */ + /// The URI used by the IDP to authenticate the user. var authURI: String? - /** @property registered - @brief Whether the user is registered if the identifier is an email. - */ + /// Whether the user is registered if the identifier is an email. var registered: Bool = false - /** @property providerId - @brief The provider ID of the auth URI. - */ + /// The provider ID of the auth URI. var providerID: String? - /** @property forExistingProvider - @brief True if the authUri is for user's existing provider. - */ + /// True if the authUri is for user's existing provider. var forExistingProvider: Bool = false - /** @property allProviders - @brief A list of provider IDs the passed @c identifier could use to sign in with. - */ + /// A list of provider IDs the passed identifier could use to sign in with. var allProviders: [String]? - /** @property signinMethods - @brief A list of sign-in methods available for the passed @c identifier. - */ + /// A list of sign-in methods available for the passed identifier. var signinMethods: [String]? /// Bare initializer. diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/DeleteAccountRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/DeleteAccountRequest.swift index bf059cb7967..430b74a4b76 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/DeleteAccountRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/DeleteAccountRequest.swift @@ -14,34 +14,25 @@ import Foundation -/** @var kCreateAuthURIEndpoint - @brief The "deleteAccount" endpoint. - */ +/// The "deleteAccount" endpoint. + private let kDeleteAccountEndpoint = "deleteAccount" -/** @var kIDTokenKey - @brief The key for the "idToken" value in the request. This is actually the STS Access Token, - despite it's confusing (backwards compatiable) parameter name. - */ +/// The key for the "idToken" value in the request. This is actually the STS Access Token, +/// despite its confusing (backwards compatiable) parameter name. private let kIDTokenKey = "idToken" -/** @var kLocalIDKey - @brief The key for the "localID" value in the request. - */ +/// The key for the "localID" value in the request. private let kLocalIDKey = "localId" @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) class DeleteAccountRequest: IdentityToolkitRequest, AuthRPCRequest { typealias Response = DeleteAccountResponse - /** @var _accessToken - @brief The STS Access Token of the authenticated user. - */ + /// The STS Access Token of the authenticated user. let accessToken: String - /** @var _localID - @brief The localID of the user. - */ + /// The localID of the user. let localID: String init(localID: String, accessToken: String, requestConfiguration: AuthRequestConfiguration) { diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/DeleteAccountResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/DeleteAccountResponse.swift index c5d80c863a2..92208e7581b 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/DeleteAccountResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/DeleteAccountResponse.swift @@ -14,10 +14,9 @@ import Foundation -/** @class FIRDeleteAccountResponse - @brief Represents the response from the deleteAccount endpoint. - @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/deleteAccount - */ +/// Represents the response from the deleteAccount endpoint. +/// +/// See https://developers.google.com/identity/toolkit/web/reference/relyingparty/deleteAccount class DeleteAccountResponse: NSObject, AuthRPCResponse { override required init() {} diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/EmailLinkSignInRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/EmailLinkSignInRequest.swift index cfbf1247a25..b57c2170450 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/EmailLinkSignInRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/EmailLinkSignInRequest.swift @@ -14,34 +14,22 @@ import Foundation -/** @var kEmailLinkSigninEndpoint - @brief The "EmailLinkSignin" endpoint. - */ +/// The "EmailLinkSignin" endpoint. private let kEmailLinkSigninEndpoint = "emailLinkSignin" -/** @var kEmailKey - @brief The key for the "identifier" value in the request. - */ +/// The key for the "identifier" value in the request. private let kEmailKey = "email" -/** @var kEmailLinkKey - @brief The key for the "emailLink" value in the request. - */ +/// The key for the "emailLink" value in the request. private let kOOBCodeKey = "oobCode" -/** @var kIDTokenKey - @brief The key for the "IDToken" value in the request. - */ +/// The key for the "IDToken" value in the request. private let kIDTokenKey = "idToken" -/** @var kPostBodyKey - @brief The key for the "postBody" value in the request. - */ +/// The key for the "postBody" value in the request. private let kPostBodyKey = "postBody" -/** @var kTenantIDKey - @brief The key for the tenant id value in the request. - */ +/// The key for the tenant id value in the request. private let kTenantIDKey = "tenantId" @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @@ -50,15 +38,10 @@ class EmailLinkSignInRequest: IdentityToolkitRequest, AuthRPCRequest { let email: String - /** @property oobCode - @brief The OOB code used to complete the email link sign-in flow. - */ + /// The OOB code used to complete the email link sign-in flow. let oobCode: String - /** @property IDToken - @brief The ID Token code potentially used to complete the email link sign-in flow. - */ - + /// The ID Token code potentially used to complete the email link sign-in flow. var idToken: String? init(email: String, oobCode: String, diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/EmailLinkSignInResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/EmailLinkSignInResponse.swift index f41d0bde732..4147c4962e0 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/EmailLinkSignInResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/EmailLinkSignInResponse.swift @@ -14,48 +14,32 @@ import Foundation -/** @class FIRVerifyAssertionResponse - @brief Represents the response from the emailLinkSignin endpoint. - */ +/// Represents the response from the emailLinkSignin endpoint. class EmailLinkSignInResponse: NSObject, AuthRPCResponse, AuthMFAResponse { override required init() {} - /** @property IDToken - @brief The ID token in the email link sign-in response. - */ + /// The ID token in the email link sign-in response. private(set) var idToken: String? - /** @property email - @brief The email returned by the IdP. - */ + /// The email returned by the IdP. var email: String? - /** @property refreshToken - @brief The refreshToken returned by the server. - */ + /// The refreshToken returned by the server. var refreshToken: String? - /** @property approximateExpirationDate - @brief The approximate expiration date of the access token. - */ + /// The approximate expiration date of the access token. var approximateExpirationDate: Date? - /** @property isNewUser - @brief Flag indicating that the user signing in is a new user and not a returning user. - */ + /// Flag indicating that the user signing in is a new user and not a returning user. var isNewUser: Bool = false // MARK: - AuthMFAResponse - /** @property MFAPendingCredential - @brief An opaque string that functions as proof that the user has successfully passed the first - factor check. - */ + /// An opaque string that functions as proof that the user has successfully passed the first + /// factor check. private(set) var mfaPendingCredential: String? - /** @property MFAInfo - @brief Info on which multi-factor authentication providers are enabled. - */ + /// Info on which multi-factor authentication providers are enabled. private(set) var mfaInfo: [AuthProtoMFAEnrollment]? func setFields(dictionary: [String: AnyHashable]) throws { diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/GetAccountInfoRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/GetAccountInfoRequest.swift index 9a0e19af0be..ce94425d311 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/GetAccountInfoRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/GetAccountInfoRequest.swift @@ -14,35 +14,26 @@ import Foundation -/** @var kGetAccountInfoEndpoint - @brief The "getAccountInfo" endpoint. - */ +/// The "getAccountInfo" endpoint. private let kGetAccountInfoEndpoint = "getAccountInfo" -/** @var kIDTokenKey - @brief The key for the "idToken" value in the request. This is actually the STS Access Token, - despite it's confusing (backwards compatiable) parameter name. - */ +/// The key for the "idToken" value in the request. This is actually the STS Access Token, +/// despite its confusing (backwards compatiable) parameter name. private let kIDTokenKey = "idToken" -/** @class FIRGetAccountInfoRequest - @brief Represents the parameters for the getAccountInfo endpoint. - @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/getAccountInfo - */ +/// Represents the parameters for the getAccountInfo endpoint. +/// See https://developers.google.com/identity/toolkit/web/reference/relyingparty/getAccountInfo @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) class GetAccountInfoRequest: IdentityToolkitRequest, AuthRPCRequest { typealias Response = GetAccountInfoResponse - /** @property accessToken - @brief The STS Access Token for the authenticated user. - */ + /// The STS Access Token for the authenticated user. let accessToken: String - /** @fn initWithAccessToken:requestConfiguration - @brief Designated initializer. - @param accessToken The Access Token of the authenticated user. - @param requestConfiguration An object containing configurations to be added to the request. - */ + /// Designated initializer. + /// - Parameter accessToken: The Access Token of the authenticated user. + /// - Parameter requestConfiguration: An object containing configurations to be added to the + /// request. init(accessToken: String, requestConfiguration: AuthRequestConfiguration) { self.accessToken = accessToken super.init(endpoint: kGetAccountInfoEndpoint, requestConfiguration: requestConfiguration) diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/GetAccountInfoResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/GetAccountInfoResponse.swift index 07413e94c84..2f39fdfe68d 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/GetAccountInfoResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/GetAccountInfoResponse.swift @@ -14,50 +14,32 @@ import Foundation -/** @var kErrorKey - @brief The key for the "error" value in JSON responses from the server. - */ +/// The key for the "error" value in JSON responses from the server. private let kErrorKey = "error" -/** @class FIRGetAccountInfoResponseProviderUserInfo - @brief Represents the provider user info part of the response from the getAccountInfo endpoint. - @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/getAccountInfo - */ +/// Represents the provider user info part of the response from the getAccountInfo endpoint. +/// See https://developers.google.com/identity/toolkit/web/reference/relyingparty/getAccountInfo class GetAccountInfoResponseProviderUserInfo: NSObject { - /** @property providerID - @brief The ID of the identity provider. - */ + /// The ID of the identity provider. let providerID: String? - /** @property displayName - @brief The user's display name at the identity provider. - */ + /// The user's display name at the identity provider. let displayName: String? - /** @property photoURL - @brief The user's photo URL at the identity provider. - */ + /// The user's photo URL at the identity provider. let photoURL: URL? - /** @property federatedID - @brief The user's identifier at the identity provider. - */ + /// The user's identifier at the identity provider. let federatedID: String? - /** @property email - @brief The user's email at the identity provider. - */ + /// The user's email at the identity provider. let email: String? - /** @property phoneNumber - @brief A phone number associated with the user. - */ + /// A phone number associated with the user. let phoneNumber: String? - /** @fn initWithAPIKey: - @brief Designated initializer. - @param dictionary The provider user info data from endpoint. - */ + /// Designated initializer. + /// - Parameter dictionary: The provider user info data from endpoint. init(dictionary: [String: Any]) { providerID = dictionary["providerId"] as? String displayName = dictionary["displayName"] as? String @@ -73,69 +55,44 @@ class GetAccountInfoResponseProviderUserInfo: NSObject { } } -/** @class FIRGetAccountInfoResponseUser - @brief Represents the firebase user info part of the response from the getAccountInfo endpoint. - @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/getAccountInfo - */ +/// Represents the firebase user info part of the response from the getAccountInfo endpoint. +/// See https://developers.google.com/identity/toolkit/web/reference/relyingparty/getAccountInfo class GetAccountInfoResponseUser: NSObject { - /** @property localID - @brief The ID of the user. - */ + /// The ID of the user. let localID: String? - /** @property email - @brief The email or the user. - */ + /// The email or the user. let email: String? - /** @property emailVerified - @brief Whether the email has been verified. - */ + /// Whether the email has been verified. let emailVerified: Bool - /** @property displayName - @brief The display name of the user. - */ + /// The display name of the user. let displayName: String? - /** @property photoURL - @brief The user's photo URL. - */ + /// The user's photo URL. let photoURL: URL? - /** @property creationDate - @brief The user's creation date. - */ + /// The user's creation date. let creationDate: Date? - /** @property lastSignInDate - @brief The user's last login date. - */ + /// The user's last login date. let lastLoginDate: Date? - /** @property providerUserInfo - @brief The user's profiles at the associated identity providers. - */ + /// The user's profiles at the associated identity providers. let providerUserInfo: [GetAccountInfoResponseProviderUserInfo]? - /** @property passwordHash - @brief Information about user's password. - @remarks This is not necessarily the hash of user's actual password. - */ - + /// Information about user's password. + /// This is not necessarily the hash of user's actual password. let passwordHash: String? - /** @property phoneNumber - @brief A phone number associated with the user. - */ + /// A phone number associated with the user. let phoneNumber: String? let mfaEnrollments: [AuthProtoMFAEnrollment]? - /** @fn initWithAPIKey: - @brief Designated initializer. - @param dictionary The provider user info data from endpoint. - */ + /// Designated initializer. + /// - Parameter dictionary: The provider user info data from endpoint. init(dictionary: [String: Any]) { if let providerUserInfoData = dictionary["providerUserInfo"] as? [[String: Any]] { providerUserInfo = providerUserInfoData.map { @@ -179,17 +136,13 @@ class GetAccountInfoResponseUser: NSObject { } } -/** @class FIRGetAccountInfoResponse - @brief Represents the response from the setAccountInfo endpoint. - @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/getAccountInfo - */ +/// Represents the response from the setAccountInfo endpoint. +/// See https://developers.google.com/identity/toolkit/web/reference/relyingparty/getAccountInfo @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) class GetAccountInfoResponse: AuthRPCResponse { required init() {} - /** @property providerUserInfo - @brief The requested users' profiles. - */ + /// The requested users' profiles. var users: [GetAccountInfoResponseUser]? func setFields(dictionary: [String: AnyHashable]) throws { guard let usersData = dictionary["users"] as? [[String: AnyHashable]] else { diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/GetOOBConfirmationCodeRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/GetOOBConfirmationCodeRequest.swift index 7b33352e102..bd3925d1f67 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/GetOOBConfirmationCodeRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/GetOOBConfirmationCodeRequest.swift @@ -15,24 +15,16 @@ import Foundation enum GetOOBConfirmationCodeRequestType: Int { - /** @var FIRGetOOBConfirmationCodeRequestTypePasswordReset - @brief Requests a password reset code. - */ + /// Requests a password reset code. case passwordReset - /** @var FIRGetOOBConfirmationCodeRequestTypeVerifyEmail - @brief Requests an email verification code. - */ + /// Requests an email verification code. case verifyEmail - /** @var FIRGetOOBConfirmationCodeRequestTypeEmailLink - @brief Requests an email sign-in link. - */ + /// Requests an email sign-in link. case emailLink - /** @var FIRGetOOBConfirmationCodeRequestTypeVerifyBeforeUpdateEmail - @brief Requests an verify before update email. - */ + /// Requests an verify before update email. case verifyBeforeUpdateEmail var value: String { @@ -51,187 +43,118 @@ enum GetOOBConfirmationCodeRequestType: Int { private let kGetOobConfirmationCodeEndpoint = "getOobConfirmationCode" -/** @var kRequestTypeKey - @brief The name of the required "requestType" property in the request. - */ +/// The name of the required "requestType" property in the request. private let kRequestTypeKey = "requestType" -/** @var kEmailKey - @brief The name of the "email" property in the request. - */ +/// The name of the "email" property in the request. private let kEmailKey = "email" -/** @var kNewEmailKey - @brief The name of the "newEmail" property in the request. - */ +/// The name of the "newEmail" property in the request. private let kNewEmailKey = "newEmail" -/** @var kIDTokenKey - @brief The key for the "idToken" value in the request. This is actually the STS Access Token, - despite it's confusing (backwards compatiable) parameter name. - */ +/// The key for the "idToken" value in the request. This is actually the STS Access Token, +/// despite its confusing (backwards compatiable) parameter name. private let kIDTokenKey = "idToken" -/** @var kContinueURLKey - @brief The key for the "continue URL" value in the request. - */ +/// The key for the "continue URL" value in the request. private let kContinueURLKey = "continueUrl" -/** @var kIosBundeIDKey - @brief The key for the "iOS Bundle Identifier" value in the request. - */ +/// The key for the "iOS Bundle Identifier" value in the request. private let kIosBundleIDKey = "iOSBundleId" -/** @var kAndroidPackageNameKey - @brief The key for the "Android Package Name" value in the request. - */ +/// The key for the "Android Package Name" value in the request. private let kAndroidPackageNameKey = "androidPackageName" -/** @var kAndroidInstallAppKey - @brief The key for the request parameter indicating whether the android app should be installed - or not. - */ +/// The key for the request parameter indicating whether the android app should be installed or not. private let kAndroidInstallAppKey = "androidInstallApp" -/** @var kAndroidMinimumVersionKey - @brief The key for the "minimum Android version supported" value in the request. - */ +/// The key for the "minimum Android version supported" value in the request. private let kAndroidMinimumVersionKey = "androidMinimumVersion" -/** @var kCanHandleCodeInAppKey - @brief The key for the request parameter indicating whether the action code can be handled in - the app or not. - */ +/// The key for the request parameter indicating whether the action code can be handled in the app +/// or not. private let kCanHandleCodeInAppKey = "canHandleCodeInApp" -/** @var kDynamicLinkDomainKey - @brief The key for the "dynamic link domain" value in the request. - */ +/// The key for the "dynamic link domain" value in the request. private let kDynamicLinkDomainKey = "dynamicLinkDomain" -/** @var kPasswordResetRequestTypeValue - @brief The value for the "PASSWORD_RESET" request type. - */ +/// The value for the "PASSWORD_RESET" request type. private let kPasswordResetRequestTypeValue = "PASSWORD_RESET" -/** @var kEmailLinkSignInTypeValue - @brief The value for the "EMAIL_SIGNIN" request type. - */ +/// The value for the "EMAIL_SIGNIN" request type. private let kEmailLinkSignInTypeValue = "EMAIL_SIGNIN" -/** @var kVerifyEmailRequestTypeValue - @brief The value for the "VERIFY_EMAIL" request type. - */ +/// The value for the "VERIFY_EMAIL" request type. private let kVerifyEmailRequestTypeValue = "VERIFY_EMAIL" -/** @var kVerifyBeforeUpdateEmailRequestTypeValue - @brief The value for the "VERIFY_AND_CHANGE_EMAIL" request type. - */ +/// The value for the "VERIFY_AND_CHANGE_EMAIL" request type. private let kVerifyBeforeUpdateEmailRequestTypeValue = "VERIFY_AND_CHANGE_EMAIL" -/** @var kTenantIDKey - @brief The key for the tenant id value in the request. - */ +/// The key for the tenant id value in the request. private let kTenantIDKey = "tenantId" -/** @var kCaptchaResponseKey - @brief The key for the "captchaResponse" value in the request. - */ +/// The key for the "captchaResponse" value in the request. private let kCaptchaResponseKey = "captchaResp" -/** @var kClientType - @brief The key for the "clientType" value in the request. - */ +/// The key for the "clientType" value in the request. private let kClientType = "clientType" -/** @var kRecaptchaVersion - @brief The key for the "recaptchaVersion" value in the request. - */ +/// The key for the "recaptchaVersion" value in the request. private let kRecaptchaVersion = "recaptchaVersion" @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) class GetOOBConfirmationCodeRequest: IdentityToolkitRequest, AuthRPCRequest { typealias Response = GetOOBConfirmationCodeResponse - /** @property requestType - @brief The types of OOB Confirmation Code to request. - */ + /// The types of OOB Confirmation Code to request. let requestType: GetOOBConfirmationCodeRequestType - /** @property email - @brief The email of the user. - @remarks For password reset. - */ + /// The email of the user for password reset. private(set) var email: String? - /** @property updatedEmail - @brief The new email to be updated. - @remarks For verifyBeforeUpdateEmail. - */ + /// The new email to be updated for verifyBeforeUpdateEmail. private(set) var updatedEmail: String? - /** @property accessToken - @brief The STS Access Token of the authenticated user. - @remarks For email change. - */ + /// The STS Access Token of the authenticated user for email change. private(set) var accessToken: String? - /** @property continueURL - @brief This URL represents the state/Continue URL in the form of a universal link. - */ + /// This URL represents the state/Continue URL in the form of a universal link. private(set) var continueURL: String? - /** @property iOSBundleID - @brief The iOS bundle Identifier, if available. - */ + /// The iOS bundle Identifier, if available. private(set) var iOSBundleID: String? - /** @property androidPackageName - @brief The Android package name, if available. - */ + /// The Android package name, if available. private(set) var androidPackageName: String? - /** @property androidMinimumVersion - @brief The minimum Android version supported, if available. - */ + /// The minimum Android version supported, if available. private(set) var androidMinimumVersion: String? - /** @property androidInstallIfNotAvailable - @brief Indicates whether or not the Android app should be installed if not already available. - */ + /// Indicates whether or not the Android app should be installed if not already available. private(set) var androidInstallApp: Bool - /** @property handleCodeInApp - @brief Indicates whether the action code link will open the app directly or after being - redirected from a Firebase owned web widget. - */ + /// Indicates whether the action code link will open the app directly or after being + /// redirected from a Firebase owned web widget. private(set) var handleCodeInApp: Bool - /** @property dynamicLinkDomain - @brief The Firebase Dynamic Link domain used for out of band code flow. - */ + /// The Firebase Dynamic Link domain used for out of band code flow. private(set) var dynamicLinkDomain: String? - /** @property captchaResponse - @brief Response to the captcha. - */ + /// Response to the captcha. var captchaResponse: String? - /** @property captchaResponse - @brief The reCAPTCHA version. - */ + /// The reCAPTCHA version. var recaptchaVersion: String? - /** @fn initWithRequestType:email:APIKey: - @brief Designated initializer. - @param requestType The types of OOB Confirmation Code to request. - @param email The email of the user. - @param newEmail The email of the user to be updated. - @param accessToken The STS Access Token of the currently signed in user. - @param actionCodeSettings An object of FIRActionCodeSettings which specifies action code - settings to be applied to the OOB code request. - @param requestConfiguration An object containing configurations to be added to the request. - */ + /// Designated initializer. + /// - Parameter requestType: The types of OOB Confirmation Code to request. + /// - Parameter email: The email of the user. + /// - Parameter newEmail: The email of the user to be updated. + /// - Parameter accessToken: The STS Access Token of the currently signed in user. + /// - Parameter actionCodeSettings: An object of FIRActionCodeSettings which specifies action code + /// settings to be applied to the OOB code request. + /// - Parameter requestConfiguration: An object containing configurations to be added to the + /// request. required init(requestType: GetOOBConfirmationCodeRequestType, email: String?, newEmail: String?, diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/GetProjectConfigRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/GetProjectConfigRequest.swift index 33c9b442d21..f731e6df8fc 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/GetProjectConfigRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/GetProjectConfigRequest.swift @@ -14,9 +14,8 @@ import Foundation -/** @var kGetProjectConfigEndPoint - @brief The "getProjectConfig" endpoint. - */ +/// The "getProjectConfig" endpoint. + private let kGetProjectConfigEndPoint = "getProjectConfig" @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/GetRecaptchaConfigRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/GetRecaptchaConfigRequest.swift index 4b57f74d315..885f407a052 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/GetRecaptchaConfigRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/GetRecaptchaConfigRequest.swift @@ -18,45 +18,29 @@ private let kRecaptchaVersion = "RECAPTCHA_ENTERPRISE" private let kGetOobConfirmationCodeEndpoint = "getOobConfirmationCode" -/** @var kRequestTypeKey - @brief The name of the required "requestType" property in the request. - */ +/// The name of the required "requestType" property in the request. private let kRequestTypeKey = "requestType" -/** @var kEmailKey - @brief The name of the "email" property in the request. - */ +/// The name of the "email" property in the request. private let kEmailKey = "email" -/** @var kNewEmailKey - @brief The name of the "newEmail" property in the request. - */ +/// The name of the "newEmail" property in the request. private let kNewEmailKey = "newEmail" -/** @var kIDTokenKey - @brief The key for the "idToken" value in the request. This is actually the STS Access Token, - despite it's confusing (backwards compatiable) parameter name. - */ +/// The key for the "idToken" value in the request. This is actually the STS Access Token, +/// despite its confusing (backwards compatiable) parameter name. private let kIDTokenKey = "idToken" -/** @var kGetRecaptchaConfigEndpoint - @brief The "getRecaptchaConfig" endpoint. - */ +/// The "getRecaptchaConfig" endpoint. private let kGetRecaptchaConfigEndpoint = "recaptchaConfig" -/** @var kClientType - @brief The key for the "clientType" value in the request. - */ +/// The key for the "clientType" value in the request. private let kClientTypeKey = "clientType" -/** @var kVersionKey - @brief The key for the "version" value in the request. - */ +/// The key for the "version" value in the request. private let kVersionKey = "version" -/** @var kTenantIDKey - @brief The key for the tenant id value in the request. - */ +/// The key for the tenant id value in the request. private let kTenantIDKey = "tenantId" @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/FinalizeMFAEnrollmentRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/FinalizeMFAEnrollmentRequest.swift index d2adb4d9cdb..773b448a136 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/FinalizeMFAEnrollmentRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/FinalizeMFAEnrollmentRequest.swift @@ -16,9 +16,7 @@ import Foundation private let kFinalizeMFAEnrollmentEndPoint = "accounts/mfaEnrollment:finalize" -/** @var kTenantIDKey - @brief The key for the tenant id value in the request. - */ +/// The key for the tenant id value in the request. private let kTenantIDKey = "tenantId" @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/StartMFAEnrollmentRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/StartMFAEnrollmentRequest.swift index ffaf18f6e97..2ae904eac46 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/StartMFAEnrollmentRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/StartMFAEnrollmentRequest.swift @@ -16,9 +16,7 @@ import Foundation private let kStartMFAEnrollmentEndPoint = "accounts/mfaEnrollment:start" -/** @var kTenantIDKey - @brief The key for the tenant id value in the request. - */ +/// The key for the tenant id value in the request. private let kTenantIDKey = "tenantId" @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/FinalizeMFASignInRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/FinalizeMFASignInRequest.swift index 8c94ea5dc9d..53f8a783b67 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/FinalizeMFASignInRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/FinalizeMFASignInRequest.swift @@ -16,9 +16,7 @@ import Foundation private let kFinalizeMFASignInEndPoint = "accounts/mfaSignIn:finalize" -/** @var kTenantIDKey - @brief The key for the tenant id value in the request. - */ +/// The key for the tenant id value in the request. private let kTenantIDKey = "tenantId" @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/StartMFASignInRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/StartMFASignInRequest.swift index 977901fc563..413245776a7 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/StartMFASignInRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/StartMFASignInRequest.swift @@ -16,9 +16,8 @@ import Foundation private let kStartMFASignInEndPoint = "accounts/mfaSignIn:start" -/** @var kTenantIDKey - @brief The key for the tenant id value in the request. - */ +/// The key for the tenant id value in the request. + private let kTenantIDKey = "tenantId" @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Unenroll/WithdrawMFARequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Unenroll/WithdrawMFARequest.swift index 240f909e262..5f8156a5191 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Unenroll/WithdrawMFARequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Unenroll/WithdrawMFARequest.swift @@ -16,9 +16,7 @@ import Foundation private let kWithdrawMFAEndPoint = "accounts/mfaEnrollment:withdraw" -/** @var kTenantIDKey - @brief The key for the tenant id value in the request. - */ +/// The key for the tenant id value in the request. private let kTenantIDKey = "tenantId" @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/Phone/AuthProtoStartMFAPhoneRequestInfo.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/Phone/AuthProtoStartMFAPhoneRequestInfo.swift index fc52c4cfb94..274a7b97883 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/Phone/AuthProtoStartMFAPhoneRequestInfo.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/Phone/AuthProtoStartMFAPhoneRequestInfo.swift @@ -14,24 +14,16 @@ import Foundation -/** @var kPhoneNumberKey - @brief The key for the Phone Number parameter in the request. - */ +/// The key for the Phone Number parameter in the request. private let kPhoneNumberKey = "phoneNumber" -/** @var kReceiptKey - @brief The key for the receipt parameter in the request. - */ +/// The key for the receipt parameter in the request. private let kReceiptKey = "iosReceipt" -/** @var kSecretKey - @brief The key for the Secret parameter in the request. - */ +/// The key for the Secret parameter in the request. private let kSecretKey = "iosSecret" -/** @var kreCAPTCHATokenKey - @brief The key for the reCAPTCHAToken parameter in the request. - */ +/// The key for the reCAPTCHAToken parameter in the request. private let kreCAPTCHATokenKey = "recaptchaToken" class AuthProtoStartMFAPhoneRequestInfo: NSObject, AuthProto { diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/TOTP/AuthProtoStartMFATOTPEnrollmentRequestInfo.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/TOTP/AuthProtoStartMFATOTPEnrollmentRequestInfo.swift index f6c0af69381..69f65349aa1 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/TOTP/AuthProtoStartMFATOTPEnrollmentRequestInfo.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/TOTP/AuthProtoStartMFATOTPEnrollmentRequestInfo.swift @@ -14,10 +14,8 @@ import Foundation -/** - @brief AuthProtoFinalizeMFATOTPSignInRequestInfo class. This class is used to compose - finalizeMFASignInRequest for TOTP case. - */ +/// AuthProtoFinalizeMFATOTPSignInRequestInfo class. This class is used to compose +/// finalizeMFASignInRequest for TOTP case . class AuthProtoFinalizeMFATOTPSignInRequestInfo: NSObject, AuthProto { required init(dictionary: [String: AnyHashable]) { fatalError() diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/ResetPasswordRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/ResetPasswordRequest.swift index 69b6c085624..aa39c927b83 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/ResetPasswordRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/ResetPasswordRequest.swift @@ -14,46 +14,33 @@ import Foundation -/** @var kResetPasswordEndpoint - @brief The "resetPassword" endpoint. - */ +/// The "resetPassword" endpoint. private let kResetPasswordEndpoint = "resetPassword" -/** @var kOOBCodeKey - @brief The "resetPassword" key. - */ +/// The "resetPassword" key. private let kOOBCodeKey = "oobCode" -/** @var kCurrentPasswordKey - @brief The "newPassword" key. - */ +/// The "newPassword" key. private let kCurrentPasswordKey = "newPassword" -/** @var kTenantIDKey - @brief The key for the tenant id value in the request. - */ +/// The key for the tenant id value in the request. private let kTenantIDKey = "tenantId" @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) class ResetPasswordRequest: IdentityToolkitRequest, AuthRPCRequest { typealias Response = ResetPasswordResponse - /** @property oobCode - @brief The oobCode sent in the request. - */ + /// The oobCode sent in the request. let oobCode: String - /** @property updatedPassword - @brief The new password sent in the request. - */ + /// The new password sent in the request. let updatedPassword: String? - /** @fn initWithOobCode:newPassword:requestConfiguration: - @brief Designated initializer. - @param oobCode The OOB Code. - @param newPassword The new password. - @param requestConfiguration An object containing configurations to be added to the request. - */ + /// Designated initializer. + /// - Parameter oobCode: The OOB Code. + /// - Parameter newPassword: The new password. + /// - Parameter requestConfiguration: An object containing configurations to be added to the + /// request. init(oobCode: String, newPassword: String?, requestConfiguration: AuthRequestConfiguration) { self.oobCode = oobCode diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/ResetPasswordResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/ResetPasswordResponse.swift index 8cb85aaffd6..fcfb619732e 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/ResetPasswordResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/ResetPasswordResponse.swift @@ -14,32 +14,26 @@ import Foundation -/** @class FIRAuthResetPasswordResponse - @brief Represents the response from the resetPassword endpoint. - @remarks Possible error codes: - - FIRAuthErrorCodeWeakPassword - - FIRAuthErrorCodeUserDisabled - - FIRAuthErrorCodeOperationNotAllowed - - FIRAuthErrorCodeExpiredActionCode - - FIRAuthErrorCodeInvalidActionCode - @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/resetPassword - */ +/// Represents the response from the resetPassword endpoint. +/// +/// Possible error codes: +/// * FIRAuthErrorCodeWeakPassword +/// * FIRAuthErrorCodeUserDisabled +/// * FIRAuthErrorCodeOperationNotAllowed +/// * FIRAuthErrorCodeExpiredActionCode +/// * FIRAuthErrorCodeInvalidActionCode +/// +/// See https: // developers.google.com/identity/toolkit/web/reference/relyingparty/resetPassword class ResetPasswordResponse: AuthRPCResponse { required init() {} - /** @property email - @brief The email address corresponding to the reset password request. - */ + /// The email address corresponding to the reset password request. var email: String? - /** @property verifiedEmail - @brief The verified email returned from the backend. - */ + /// The verified email returned from the backend. var verifiedEmail: String? - /** @property requestType - @brief The type of request as returned by the backend. - */ + /// The type of request as returned by the backend. var requestType: String? func setFields(dictionary: [String: AnyHashable]) throws { diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/RevokeTokenRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/RevokeTokenRequest.swift index c50151fc93c..c69ab2b60b0 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/RevokeTokenRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/RevokeTokenRequest.swift @@ -14,57 +14,38 @@ import Foundation -/** @var kRevokeTokenEndpoint - @brief The endpoint for the revokeToken request. - */ +/// The endpoint for the revokeToken request. private let kRevokeTokenEndpoint = "accounts:revokeToken" -/** @var kProviderIDKey - @brief The key for the provider that issued the token to revoke. - */ +/// The key for the provider that issued the token to revoke. private let kProviderIDKey = "providerId" -/** @var kTokenTypeKey - @brief The key for the type of the token to revoke. - */ +/// The key for the type of the token to revoke. private let kTokenTypeKey = "tokenType" -/** @var kTokenKey - @brief The key for the token to be revoked. - */ +/// The key for the token to be revoked. private let kTokenKey = "token" -/** @var kIDTokenKey - @brief The key for the ID Token associated with this credential. - */ +/// The key for the ID Token associated with this credential. private let kIDTokenKey = "idToken" -/** @class FIRVerifyPasswordRequest - @brief Represents the parameters for the verifyPassword endpoint. - @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/verifyPassword - */ +/// Represents the parameters for the verifyPassword endpoint. +/// +/// See https: // developers.google.com/identity/toolkit/web/reference/relyingparty/verifyPassword @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) class RevokeTokenRequest: IdentityToolkitRequest, AuthRPCRequest { typealias Response = RevokeTokenResponse - /** @property providerID - @brief The provider that issued the token to revoke. - */ + /// The provider that issued the token to revoke. private(set) var providerID: String - /** @property tokenType - @brief The type of the token to revoke. - */ + /// The type of the token to revoke. private(set) var tokenType: TokenType - /** @property token - @brief The token to be revoked. - */ + /// The token to be revoked. private(set) var token: String - /** @property idToken - @brief The ID Token associated with this credential. - */ + /// The ID Token associated with this credential. private(set) var idToken: String enum TokenType: Int { diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenRequest.swift index e0f0829fe95..60a504a367a 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenRequest.swift @@ -28,84 +28,54 @@ enum SecureTokenRequestGrantType: Int { } } -/** @var kFIRSecureTokenServiceGetTokenURLFormat - @brief The format of the secure token service URLs. Requires string format substitution with - the client's API Key. - */ +/// The format of the secure token service URLs. Requires string format substitution with +/// the client's API Key. private let kFIRSecureTokenServiceGetTokenURLFormat = "https://%@/v1/token?key=%@" -/** @var kFIREmulatorURLFormat - @brief The format of the emulated secure token service URLs. Requires string format substitution - with the emulator host, the gAPIHost, and the client's API Key. - */ +/// The format of the emulated secure token service URLs. Requires string format substitution +/// with the emulator host, the gAPIHost, and the client's API Key. private let kFIREmulatorURLFormat = "http://%@/%@/v1/token?key=%@" -/** @var kFIRSecureTokenServiceGrantTypeRefreshToken - @brief The string value of the @c FIRSecureTokenRequestGrantTypeRefreshToken request type. - */ +/// The string value of the `SecureTokenRequestGrantTypeRefreshToken` request type. private let kFIRSecureTokenServiceGrantTypeRefreshToken = "refresh_token" -/** @var kFIRSecureTokenServiceGrantTypeAuthorizationCode - @brief The string value of the @c FIRSecureTokenRequestGrantTypeAuthorizationCode request type. - */ +/// The string value of the `SecureTokenRequestGrantTypeAuthorizationCode` request type. private let kFIRSecureTokenServiceGrantTypeAuthorizationCode = "authorization_code" -/** @var kGrantTypeKey - @brief The key for the "grantType" parameter in the request. - */ +/// The key for the "grantType" parameter in the request. private let kGrantTypeKey = "grantType" -/** @var kScopeKey - @brief The key for the "scope" parameter in the request. - */ +/// The key for the "scope" parameter in the request. private let kScopeKey = "scope" -/** @var kRefreshTokenKey - @brief The key for the "refreshToken" parameter in the request. - */ +/// The key for the "refreshToken" parameter in the request. private let kRefreshTokenKey = "refreshToken" -/** @var kCodeKey - @brief The key for the "code" parameter in the request. - */ +/// The key for the "code" parameter in the request. private let kCodeKey = "code" -/** @var gAPIHost - @brief Host for server API calls. - */ +/// Host for server API calls. private var gAPIHost = "securetoken.googleapis.com" -/** @class FIRSecureTokenRequest - @brief Represents the parameters for the token endpoint. - */ +/// Represents the parameters for the token endpoint. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) class SecureTokenRequest: AuthRPCRequest { typealias Response = SecureTokenResponse - /** @property grantType - @brief The type of grant requested. - @see FIRSecureTokenRequestGrantType - */ + /// The type of grant requested. + /// See FIRSecureTokenRequestGrantType var grantType: SecureTokenRequestGrantType - /** @property scope - @brief The scopes requested (a comma-delimited list of scope strings.) - */ + /// The scopes requested (a comma-delimited list of scope strings). var scope: String? - /** @property refreshToken - @brief The client's refresh token. - */ + /// The client's refresh token. var refreshToken: String? - /** @property code - @brief The client's authorization code (legacy Gitkit "ID Token"). - */ + /// The client's authorization code (legacy Gitkit "ID Token"). var code: String? - /** @property APIKey - @brief The client's API Key. - */ + /// The client's API Key. let apiKey: String let _requestConfiguration: AuthRequestConfiguration diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenResponse.swift index d2331b44bf5..f16bd573da0 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenResponse.swift @@ -16,19 +16,16 @@ import Foundation private let kExpiresInKey = "expires_in" -/** @var kRefreshTokenKey - @brief The key for the refresh token. - */ +/// The key for the refresh token. + private let kRefreshTokenKey = "refresh_token" -/** @var kAccessTokenKey - @brief The key for the access token. - */ +/// The key for the access token. + private let kAccessTokenKey = "access_token" -/** @var kIDTokenKey - @brief The key for the "id_token" value in the response. - */ +/// The key for the "id_token" value in the response. + private let kIDTokenKey = "id_token" @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SendVerificationTokenRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SendVerificationTokenRequest.swift index a3ae44f95ac..af090fdd3b3 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SendVerificationTokenRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SendVerificationTokenRequest.swift @@ -14,34 +14,22 @@ import Foundation -/** @var kSendVerificationCodeEndPoint - @brief The "sendVerificationCodeEnd" endpoint. - */ +/// The "sendVerificationCodeEnd" endpoint. private let kSendVerificationCodeEndPoint = "sendVerificationCode" -/** @var kPhoneNumberKey - @brief The key for the Phone Number parameter in the request. - */ +/// The key for the Phone Number parameter in the request. private let kPhoneNumberKey = "phoneNumber" -/** @var kReceiptKey - @brief The key for the receipt parameter in the request. - */ +/// The key for the receipt parameter in the request. private let kReceiptKey = "iosReceipt" -/** @var kSecretKey - @brief The key for the Secret parameter in the request. - */ +/// The key for the Secret parameter in the request. private let kSecretKey = "iosSecret" -/** @var kreCAPTCHATokenKey - @brief The key for the reCAPTCHAToken parameter in the request. - */ +/// The key for the reCAPTCHAToken parameter in the request. private let kreCAPTCHATokenKey = "recaptchaToken" -/** @var kTenantIDKey - @brief The key for the tenant id value in the request. - */ +/// The key for the tenant id value in the request. private let kTenantIDKey = "tenantId" /// A verification code can be an appCredential or a reCaptcha Token @@ -55,14 +43,11 @@ enum CodeIdentity { class SendVerificationCodeRequest: IdentityToolkitRequest, AuthRPCRequest { typealias Response = SendVerificationCodeResponse - /** @property phoneNumber - @brief The phone number to which the verification code should be sent. - */ + /// The phone number to which the verification code should be sent. let phoneNumber: String - /** @property verificationCode - @brief The credential or reCAPTCHA token to prove the identity of the app in order to send the verification code. - */ + /// The credential or reCAPTCHA token to prove the identity of the app in order to send the + /// verification code. let codeIdentity: CodeIdentity init(phoneNumber: String, codeIdentity: CodeIdentity, diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SetAccountInfoRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SetAccountInfoRequest.swift index a6f242ab92d..2680d41e16e 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SetAccountInfoRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SetAccountInfoRequest.swift @@ -24,179 +24,114 @@ private let FIRSetAccountInfoUserAttributePhotoURL = "PHOTO_URL" private let FIRSetAccountInfoUserAttributePassword = "PASSWORD" -/** @var kCreateAuthURIEndpoint - @brief The "setAccountInfo" endpoint. - */ +/// The "setAccountInfo" endpoint. private let kSetAccountInfoEndpoint = "setAccountInfo" -/** @var kIDTokenKey - @brief The key for the "idToken" value in the request. This is actually the STS Access Token, - despite it's confusing (backwards compatiable) parameter name. - */ +/// The key for the "idToken" value in the request. This is actually the STS Access Token, +/// despite its confusing (backwards compatiable) parameter name. private let kIDTokenKey = "idToken" -/** @var kDisplayNameKey - @brief The key for the "displayName" value in the request. - */ +/// The key for the "displayName" value in the request. private let kDisplayNameKey = "displayName" -/** @var kLocalIDKey - @brief The key for the "localID" value in the request. - */ +/// The key for the "localID" value in the request. private let kLocalIDKey = "localId" -/** @var kEmailKey - @brief The key for the "email" value in the request. - */ +/// The key for the "email" value in the request. private let kEmailKey = "email" -/** @var kPasswordKey - @brief The key for the "password" value in the request. - */ +/// The key for the "password" value in the request. private let kPasswordKey = "password" -/** @var kPhotoURLKey - @brief The key for the "photoURL" value in the request. - */ +/// The key for the "photoURL" value in the request. private let kPhotoURLKey = "photoUrl" -/** @var kProvidersKey - @brief The key for the "providers" value in the request. - */ +/// The key for the "providers" value in the request. private let kProvidersKey = "provider" -/** @var kOOBCodeKey - @brief The key for the "OOBCode" value in the request. - */ +/// The key for the "OOBCode" value in the request. private let kOOBCodeKey = "oobCode" -/** @var kEmailVerifiedKey - @brief The key for the "emailVerified" value in the request. - */ +/// The key for the "emailVerified" value in the request. private let kEmailVerifiedKey = "emailVerified" -/** @var kUpgradeToFederatedLoginKey - @brief The key for the "upgradeToFederatedLogin" value in the request. - */ +/// The key for the "upgradeToFederatedLogin" value in the request. private let kUpgradeToFederatedLoginKey = "upgradeToFederatedLogin" -/** @var kCaptchaChallengeKey - @brief The key for the "captchaChallenge" value in the request. - */ +/// The key for the "captchaChallenge" value in the request. private let kCaptchaChallengeKey = "captchaChallenge" -/** @var kCaptchaResponseKey - @brief The key for the "captchaResponse" value in the request. - */ +/// The key for the "captchaResponse" value in the request. private let kCaptchaResponseKey = "captchaResponse" -/** @var kDeleteAttributesKey - @brief The key for the "deleteAttribute" value in the request. - */ +/// The key for the "deleteAttribute" value in the request. private let kDeleteAttributesKey = "deleteAttribute" -/** @var kDeleteProvidersKey - @brief The key for the "deleteProvider" value in the request. - */ +/// The key for the "deleteProvider" value in the request. private let kDeleteProvidersKey = "deleteProvider" -/** @var kReturnSecureTokenKey - @brief The key for the "returnSecureToken" value in the request. - */ +/// The key for the "returnSecureToken" value in the request. private let kReturnSecureTokenKey = "returnSecureToken" -/** @var kTenantIDKey - @brief The key for the tenant id value in the request. - */ +/// The key for the tenant id value in the request. private let kTenantIDKey = "tenantId" -/** @class FIRSetAccountInfoRequest - @brief Represents the parameters for the setAccountInfo endpoint. - @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/setAccountInfo - */ +/// Represents the parameters for the setAccountInfo endpoint. +/// See https://developers.google.com/identity/toolkit/web/reference/relyingparty/setAccountInfo @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) class SetAccountInfoRequest: IdentityToolkitRequest, AuthRPCRequest { typealias Response = SetAccountInfoResponse - /** @property accessToken - @brief The STS Access Token of the authenticated user. - */ + + /// The STS Access Token of the authenticated user. var accessToken: String? - /** @property displayName - @brief The name of the user. - */ + /// The name of the user. var displayName: String? - /** @property localID - @brief The local ID of the user. - */ + /// The local ID of the user. var localID: String? - /** @property email - @brief The email of the user. - */ + /// The email of the user. var email: String? - /** @property photoURL - @brief The photoURL of the user. - */ + /// The photoURL of the user. var photoURL: URL? - /** @property password - @brief The new password of the user. - */ + /// The new password of the user. var password: String? - /** @property providers - @brief The associated identity providers of the user. - */ + /// The associated identity providers of the user. var providers: [String]? - /** @property OOBCode - @brief The out-of-band code of the change email request. - */ + /// The out-of-band code of the change email request. var oobCode: String? - /** @property emailVerified - @brief Whether to mark the email as verified or not. - */ + /// Whether to mark the email as verified or not. var emailVerified: Bool = false - /** @property upgradeToFederatedLogin - @brief Whether to mark the user to upgrade to federated login. - */ + /// Whether to mark the user to upgrade to federated login. var upgradeToFederatedLogin: Bool = false - /** @property captchaChallenge - @brief The captcha challenge. - */ + /// The captcha challenge. var captchaChallenge: String? - /** @property captchaResponse - @brief Response to the captcha. - */ + /// Response to the captcha. var captchaResponse: String? - /** @property deleteAttributes - @brief The list of user attributes to delete. - @remarks Every element of the list must be one of the predefined constant starts with - "FIRSetAccountInfoUserAttribute". - */ + /// The list of user attributes to delete. + /// + /// Every element of the list must be one of the predefined constant starts with + /// `SetAccountInfoUserAttribute`. var deleteAttributes: [String]? - /** @property deleteProviders - @brief The list of identity providers to delete. - */ + /// The list of identity providers to delete. var deleteProviders: [String]? - /** @property returnSecureToken - @brief Whether the response should return access token and refresh token directly. - @remarks The default value is @c YES . - */ - var returnSecureToken: Bool = false + /// Whether the response should return access token and refresh token directly. + /// The default value is `true` . + var returnSecureToken: Bool = true init(requestConfiguration: AuthRequestConfiguration) { - returnSecureToken = true super.init(endpoint: kSetAccountInfoEndpoint, requestConfiguration: requestConfiguration) } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SetAccountInfoResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SetAccountInfoResponse.swift index ac498ce458c..d6bd97ccd70 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SetAccountInfoResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SetAccountInfoResponse.swift @@ -14,30 +14,20 @@ import Foundation -/** @class FIRSetAccountInfoResponseProviderUserInfo - @brief Represents the provider user info part of the response from the setAccountInfo endpoint. - @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/setAccountInfo - */ +/// Represents the provider user info part of the response from the setAccountInfo endpoint. +/// See https: // developers.google.com/identity/toolkit/web/reference/relyingparty/setAccountInfo class SetAccountInfoResponseProviderUserInfo: NSObject { - /** @property providerID - @brief The ID of the identity provider. - */ + /// The ID of the identity provider. var providerID: String? - /** @property displayName - @brief The user's display name at the identity provider. - */ + /// The user's display name at the identity provider. var displayName: String? - /** @property photoURL - @brief The user's photo URL at the identity provider. - */ + /// The user's photo URL at the identity provider. var photoURL: URL? - /** @fn initWithAPIKey: - @brief Designated initializer. - @param dictionary The provider user info data from endpoint. - */ + /// Designated initializer. + /// - Parameter dictionary: The provider user info data from endpoint. init(dictionary: [String: Any]) { providerID = dictionary["providerId"] as? String displayName = dictionary["displayName"] as? String @@ -47,43 +37,29 @@ class SetAccountInfoResponseProviderUserInfo: NSObject { } } -/** @class FIRSetAccountInfoResponse - @brief Represents the response from the setAccountInfo endpoint. - @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/setAccountInfo - */ +/// Represents the response from the setAccountInfo endpoint. +/// See https: // developers.google.com/identity/toolkit/web/reference/relyingparty/setAccountInfo class SetAccountInfoResponse: AuthRPCResponse { required init() {} - /** @property email - @brief The email or the user. - */ + /// The email or the user. var email: String? - /** @property displayName - @brief The display name of the user. - */ + /// The display name of the user. var displayName: String? - /** @property providerUserInfo - @brief The user's profiles at the associated identity providers. - */ + /// The user's profiles at the associated identity providers. var providerUserInfo: [SetAccountInfoResponseProviderUserInfo]? - /** @property idToken - @brief Either an authorization code suitable for performing an STS token exchange, or the - access token from Secure Token Service, depending on whether @c returnSecureToken is set - on the request. - */ + /// Either an authorization code suitable for performing an STS token exchange, or the + /// access token from Secure Token Service, depending on whether `returnSecureToken` is set + /// on the request. var idToken: String? - /** @property approximateExpirationDate - @brief The approximate expiration date of the access token. - */ + /// The approximate expiration date of the access token. var approximateExpirationDate: Date? - /** @property refreshToken - @brief The refresh token from Secure Token Service. - */ + /// The refresh token from Secure Token Service. var refreshToken: String? func setFields(dictionary: [String: AnyHashable]) throws { diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SignInWithGameCenterRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SignInWithGameCenterRequest.swift index 39e346f6325..adab76dcd34 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SignInWithGameCenterRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SignInWithGameCenterRequest.swift @@ -16,69 +16,47 @@ import Foundation private let kSignInWithGameCenterEndPoint = "signInWithGameCenter" -/** @class FIRSignInWithGameCenterRequest - @brief The request to sign in with Game Center account - */ +/// The request to sign in with Game Center account @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) class SignInWithGameCenterRequest: IdentityToolkitRequest, AuthRPCRequest { typealias Response = SignInWithGameCenterResponse - /** @property playerID - @brief The playerID to verify. - */ + /// The playerID to verify. var playerID: String - /** @property teamPlayerID - @brief The team player ID of the Game Center local player. - */ + /// The team player ID of the Game Center local player. var teamPlayerID: String? - /** @property gamePlayerID - @brief The game player ID of the Game Center local player. - */ + /// The game player ID of the Game Center local player. var gamePlayerID: String? - /** @property publicKeyURL - @brief The URL for the encryption key. - */ + /// The URL for the encryption key. var publicKeyURL: URL - /** @property signature - @brief The verification signature data generated by Game Center. - */ + /// The verification signature data generated by Game Center. var signature: Data - /** @property salt - @brief A random strong used to compute the hash and keep it randomized. - */ + /// A random strong used to compute the hash and keep it randomized. var salt: Data - /** @property timestamp - @brief The date and time that the signature was created. - */ + /// The date and time that the signature was created. var timestamp: UInt64 - /** @property accessToken - @brief The STS Access Token for the authenticated user, only needed for linking the user. - */ + /// The STS Access Token for the authenticated user, only needed for linking the user. var accessToken: String? - /** @property displayName - @brief The display name of the local Game Center player. - */ + /// The display name of the local Game Center player. var displayName: String? - /** @fn initWithPlayerID:publicKeyURL:signature:salt:timestamp:displayName:requestConfiguration: - @brief Designated initializer. - @param playerID The ID of the Game Center player. - @param teamPlayerID The teamPlayerID of the Game Center local player. - @param gamePlayerID The gamePlayerID of the Game Center local player. - @param publicKeyURL The URL for the encryption key. - @param signature The verification signature generated. - @param salt A random string used to compute the hash and keep it randomized. - @param timestamp The date and time that the signature was created. - @param displayName The display name of the Game Center player. - */ + /// Designated initializer. + /// - Parameter playerID: The ID of the Game Center player. + /// - Parameter teamPlayerID: The teamPlayerID of the Game Center local player. + /// - Parameter gamePlayerID: The gamePlayerID of the Game Center local player. + /// - Parameter publicKeyURL: The URL for the encryption key. + /// - Parameter signature: The verification signature generated. + /// - Parameter salt: A random string used to compute the hash and keep it randomized. + /// - Parameter timestamp: The date and time that the signature was created. + /// - Parameter displayName: The display name of the Game Center player. init(playerID: String, teamPlayerID: String?, gamePlayerID: String?, publicKeyURL: URL, signature: Data, salt: Data, diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SignUpNewUserRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SignUpNewUserRequest.swift index f96703fff35..9a2d7096418 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SignUpNewUserRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SignUpNewUserRequest.swift @@ -14,105 +14,69 @@ import Foundation -/** @var kSignupNewUserEndpoint - @brief The "SingupNewUserEndpoint" endpoint. - */ +/// The "SignupNewUserEndpoint" endpoint. private let kSignupNewUserEndpoint = "signupNewUser" -/** @var kEmailKey - @brief The key for the "email" value in the request. - */ +/// The key for the "email" value in the request. private let kEmailKey = "email" -/** @var kPasswordKey - @brief The key for the "password" value in the request. - */ +/// The key for the "password" value in the request. private let kPasswordKey = "password" -/** @var kDisplayNameKey - @brief The key for the "kDisplayName" value in the request. - */ +/// The key for the "kDisplayName" value in the request. private let kDisplayNameKey = "displayName" -/** @var kIDToken - @brief The key for the "kIDToken" value in the request. - */ +/// The key for the "kIDToken" value in the request. private let kIDToken = "idToken" -/** @var kCaptchaResponseKey - @brief The key for the "captchaResponse" value in the request. - */ +/// The key for the "captchaResponse" value in the request. private let kCaptchaResponseKey = "captchaResponse" -/** @var kClientType - @brief The key for the "clientType" value in the request. - */ +/// The key for the "clientType" value in the request. private let kClientType = "clientType" -/** @var kRecaptchaVersion - @brief The key for the "recaptchaVersion" value in the request. - */ +/// The key for the "recaptchaVersion" value in the request. private let kRecaptchaVersion = "recaptchaVersion" -/** @var kReturnSecureTokenKey - @brief The key for the "returnSecureToken" value in the request. - */ +/// The key for the "returnSecureToken" value in the request. private let kReturnSecureTokenKey = "returnSecureToken" -/** @var kTenantIDKey - @brief The key for the tenant id value in the request. - */ +/// The key for the tenant id value in the request. private let kTenantIDKey = "tenantId" @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) class SignUpNewUserRequest: IdentityToolkitRequest, AuthRPCRequest { typealias Response = SignUpNewUserResponse - /** @property email - @brief The email of the user. - */ + /// The email of the user. private(set) var email: String? - /** @property password - @brief The password inputed by the user. - */ + /// The password inputed by the user. private(set) var password: String? - /** @property displayName - @brief The password inputed by the user. - */ + /// The password inputed by the user. private(set) var displayName: String? - /** @property idToken - @brief The idToken of the user. - */ + /// The idToken of the user. private(set) var idToken: String? - /** @property captchaResponse - @brief Response to the captcha. - */ - + /// Response to the captcha. var captchaResponse: String? - /** @property captchaResponse - @brief The reCAPTCHA version. - */ + /// The reCAPTCHA version. var recaptchaVersion: String? - /** @property returnSecureToken - @brief Whether the response should return access token and refresh token directly. - @remarks The default value is @c YES . - */ + /// Whether the response should return access token and refresh token directly. + /// The default value is `true`. var returnSecureToken: Bool = true init(requestConfiguration: AuthRequestConfiguration) { super.init(endpoint: kSignupNewUserEndpoint, requestConfiguration: requestConfiguration) } - /** @fn initWithAPIKey:email:password:displayName:requestConfiguration - @brief Designated initializer. - @param requestConfiguration An object containing configurations to be added to the request. - */ + /// Designated initializer. + /// - Parameter requestConfiguration: An object containing configurations to be added to the + /// request. init(email: String?, password: String?, displayName: String?, diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SignUpNewUserResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SignUpNewUserResponse.swift index d46ae7398dd..fe6c8d9556e 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SignUpNewUserResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SignUpNewUserResponse.swift @@ -17,21 +17,15 @@ import Foundation class SignUpNewUserResponse: AuthRPCResponse { required init() {} - /** @property IDToken - @brief Either an authorization code suitable for performing an STS token exchange, or the - access token from Secure Token Service, depending on whether @c returnSecureToken is set - on the request. - */ + /// Either an authorization code suitable for performing an STS token exchange, or the + /// access token from Secure Token Service, depending on whether `returnSecureToken` is set + /// on the request. var idToken: String? - /** @property approximateExpirationDate - @brief The approximate expiration date of the access token. - */ + /// The approximate expiration date of the access token. var approximateExpirationDate: Date? - /** @property refreshToken - @brief The refresh token from Secure Token Service. - */ + /// The refresh token from Secure Token Service. var refreshToken: String? func setFields(dictionary: [String: AnyHashable]) throws { diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyAssertionRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyAssertionRequest.swift index fd97de6c282..59f6f52f84f 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyAssertionRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyAssertionRequest.swift @@ -14,197 +14,124 @@ import Foundation -/** @var kVerifyAssertionEndpoint - @brief The "verifyAssertion" endpoint. - */ +/// The "verifyAssertion" endpoint. private let kVerifyAssertionEndpoint = "verifyAssertion" -/** @var kProviderIDKey - @brief The key for the "providerId" value in the request. - */ +/// The key for the "providerId" value in the request. private let kProviderIDKey = "providerId" -/** @var kProviderIDTokenKey - @brief The key for the "id_token" value in the request. - */ +/// The key for the "id_token" value in the request. private let kProviderIDTokenKey = "id_token" -/** @var kProviderNonceKey - @brief The key for the "nonce" value in the request. - */ +/// The key for the "nonce" value in the request. private let kProviderNonceKey = "nonce" -/** @var kProviderAccessTokenKey - @brief The key for the "access_token" value in the request. - */ +/// The key for the "access_token" value in the request. private let kProviderAccessTokenKey = "access_token" -/** @var kProviderOAuthTokenSecretKey - @brief The key for the "oauth_token_secret" value in the request. - */ +/// The key for the "oauth_token_secret" value in the request. private let kProviderOAuthTokenSecretKey = "oauth_token_secret" -/** @var kIdentifierKey - @brief The key for the "identifier" value in the request. - */ +/// The key for the "identifier" value in the request. private let kIdentifierKey = "identifier" -/** @var kRequestURIKey - @brief The key for the "requestUri" value in the request. - */ +/// The key for the "requestUri" value in the request. private let kRequestURIKey = "requestUri" -/** @var kPostBodyKey - @brief The key for the "postBody" value in the request. - */ +/// The key for the "postBody" value in the request. private let kPostBodyKey = "postBody" -/** @var kPendingTokenKey - @brief The key for the "pendingToken" value in the request. - */ +/// The key for the "pendingToken" value in the request. private let kPendingTokenKey = "pendingToken" -/** @var kAutoCreateKey - @brief The key for the "autoCreate" value in the request. - */ +/// The key for the "autoCreate" value in the request. private let kAutoCreateKey = "autoCreate" -/** @var kIDTokenKey - @brief The key for the "idToken" value in the request. This is actually the STS Access Token, - despite it's confusing (backwards compatiable) parameter name. - */ +/// The key for the "idToken" value in the request. This is actually the STS Access Token, +/// despite its confusing (backwards compatiable) parameter name. private let kIDTokenKey = "idToken" -/** @var kReturnSecureTokenKey - @brief The key for the "returnSecureToken" value in the request. - */ +/// The key for the "returnSecureToken" value in the request. private let kReturnSecureTokenKey = "returnSecureToken" -/** @var kReturnIDPCredentialKey - @brief The key for the "returnIdpCredential" value in the request. - */ +/// The key for the "returnIdpCredential" value in the request. private let kReturnIDPCredentialKey = "returnIdpCredential" -/** @var kSessionIDKey - @brief The key for the "sessionID" value in the request. - */ +/// The key for the "sessionID" value in the request. private let kSessionIDKey = "sessionId" -/** @var kTenantIDKey - @brief The key for the tenant id value in the request. - */ +/// The key for the tenant id value in the request. private let kTenantIDKey = "tenantId" -/** @var kUserKey - @brief The key for the "user" value in the request. The value is a JSON object that contains the - name of the user. - */ +/// The key for the "user" value in the request. The value is a JSON object that contains the +/// name of the user. private let kUserKey = "user" -/** @var kNameKey - @brief The key for the "name" value in the request. The value is a JSON object that contains the - first and/or last name of the user. - */ +/// The key for the "name" value in the request. The value is a JSON object that contains the +/// first and/or last name of the user. private let kNameKey = "name" -/** @var kFirstNameKey - @brief The key for the "firstName" value in the request. - */ +/// The key for the "firstName" value in the request. private let kFirstNameKey = "firstName" -/** @var kLastNameKey - @brief The key for the "lastName" value in the request. - */ +/// The key for the "lastName" value in the request. private let kLastNameKey = "lastName" -/** @class FIRVerifyAssertionRequest - @brief Represents the parameters for the verifyAssertion endpoint. - @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/verifyAssertion - */ +/// Represents the parameters for the verifyAssertion endpoint. +/// See https://developers.google.com/identity/toolkit/web/reference/relyingparty/verifyAssertion @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) class VerifyAssertionRequest: IdentityToolkitRequest, AuthRPCRequest { typealias Response = VerifyAssertionResponse - /** @property requestURI - @brief The URI to which the IDP redirects the user back. It may contain federated login result - params added by the IDP. - */ + /// The URI to which the IDP redirects the user back. It may contain federated login result + /// params added by the IDP. var requestURI: String? - /** @property pendingToken - @brief The Firebase ID Token for the IDP pending to be confirmed by the user. - */ + /// The Firebase ID Token for the IDP pending to be confirmed by the user. var pendingToken: String? - /** @property accessToken - @brief The STS Access Token for the authenticated user, only needed for linking the user. - */ + /// The STS Access Token for the authenticated user, only needed for linking the user. var accessToken: String? - /** @property returnSecureToken - @brief Whether the response should return access token and refresh token directly. - @remarks The default value is @c YES . - */ - var returnSecureToken: Bool = false + /// Whether the response should return access token and refresh token directly. + /// The default value is `true` . + + var returnSecureToken: Bool = true // MARK: - Components of "postBody" - /** @property providerID - @brief The ID of the IDP whose credentials are being presented to the endpoint. - */ + /// The ID of the IDP whose credentials are being presented to the endpoint. let providerID: String - /** @property providerAccessToken - @brief An access token from the IDP. - */ + /// An access token from the IDP. var providerAccessToken: String? - /** @property providerIDToken - @brief An ID Token from the IDP. - */ + /// An ID Token from the IDP. var providerIDToken: String? - /** @property providerRawNonce - @brief An raw nonce from the IDP. - */ + /// An raw nonce from the IDP. var providerRawNonce: String? - /** @property returnIDPCredential - @brief Whether the response should return the IDP credential directly. - */ - var returnIDPCredential: Bool = false + /// Whether the response should return the IDP credential directly. + var returnIDPCredential: Bool = true - /** @property providerOAuthTokenSecret - @brief A session ID used to map this request to a headful-lite flow. - */ + /// A session ID used to map this request to a headful-lite flow. var sessionID: String? - /** @property providerOAuthTokenSecret - @brief An OAuth client secret from the IDP. - */ + /// An OAuth client secret from the IDP. var providerOAuthTokenSecret: String? - /** @property inputEmail - @brief The originally entered email in the UI. - */ + /// The originally entered email in the UI. var inputEmail: String? - /** @property autoCreate - @brief A flag that indicates whether or not the user should be automatically created. - */ - var autoCreate: Bool = false + /// A flag that indicates whether or not the user should be automatically created. + var autoCreate: Bool = true - /** @property fullName - @brief A full name from the IdP. - */ + /// A full name from the IdP. var fullName: PersonNameComponents? init(providerID: String, requestConfiguration: AuthRequestConfiguration) { self.providerID = providerID - returnSecureToken = true - autoCreate = true - returnIDPCredential = true - super.init(endpoint: kVerifyAssertionEndpoint, requestConfiguration: requestConfiguration) } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyAssertionResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyAssertionResponse.swift index b8610028df5..a2077ba8bb9 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyAssertionResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyAssertionResponse.swift @@ -14,191 +14,119 @@ import Foundation -/** @class FIRVerifyAssertionResponse - @brief Represents the response from the verifyAssertion endpoint. - @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/verifyAssertion - */ +/// Represents the response from the verifyAssertion endpoint. +/// See https: // developers.google.com/identity/toolkit/web/reference/relyingparty/verifyAssertion class VerifyAssertionResponse: AuthRPCResponse, AuthMFAResponse { required init() {} - /** @property federatedID - @brief The unique ID identifies the IdP account. - */ + /// The unique ID identifies the IdP account. var federatedID: String? - /** @property providerID - @brief The IdP ID. For white listed IdPs it's a short domain name e.g. google.com, aol.com, - live.net and yahoo.com. If the "providerId" param is set to OpenID OP identifer other than - the whilte listed IdPs the OP identifier is returned. If the "identifier" param is federated - ID in the createAuthUri request. The domain part of the federated ID is returned. - */ + /// The IdP ID. For white listed IdPs it's a short domain name e.g. google.com, aol.com, + /// live.net and yahoo.com.If the "providerId" param is set to OpenID OP identifer other than + /// the white listed IdPs the OP identifier is returned.If the "identifier" param is federated + /// ID in the createAuthUri request.The domain part of the federated ID is returned. var providerID: String? - /** @property localID - @brief The RP local ID if it's already been mapped to the IdP account identified by the - federated ID. - */ + /// The RP local ID if it's already been mapped to the IdP account identified by the federated ID. var localID: String? - /** @property email - @brief The email returned by the IdP. NOTE: The federated login user may not own the email. - */ + /// The email returned by the IdP. NOTE: The federated login user may not own the email. var email: String? - /** @property inputEmail - @brief It's the identifier param in the createAuthUri request if the identifier is an email. It - can be used to check whether the user input email is different from the asserted email. - */ + /// It's the identifier param in the createAuthUri request if the identifier is an email. It + /// can be used to check whether the user input email is different from the asserted email. var inputEmail: String? - /** @property originalEmail - @brief The original email stored in the mapping storage. It's returned when the federated ID is - associated to a different email. - */ + /// The original email stored in the mapping storage. It's returned when the federated ID is + /// associated to a different email. var originalEmail: String? - /** @property oauthRequestToken - @brief The user approved request token for the OpenID OAuth extension. - */ + /// The user approved request token for the OpenID OAuth extension. var oauthRequestToken: String? - /** @property oauthScope - @brief The scope for the OpenID OAuth extension. - */ + /// The scope for the OpenID OAuth extension. var oauthScope: String? - /** @property firstName - @brief The first name of the user. - */ + /// The first name of the user. var firstName: String? - /** @property lastName - @brief The last name of the user. - */ + /// The last name of the user. var lastName: String? - /** @property fullName - @brief The full name of the user. - */ + /// The full name of the user. var fullName: String? - /** @property nickName - @brief The nick name of the user. - */ + /// The nickname of the user. var nickName: String? - /** @property displayName - @brief The display name of the user. - */ + /// The display name of the user. var displayName: String? - /** @property idToken - @brief Either an authorization code suitable for performing an STS token exchange, or the - access token from Secure Token Service, depending on whether @c returnSecureToken is set - on the request. - */ + /// Either an authorization code suitable for performing an STS token exchange, or the + /// access token from Secure Token Service, depending on whether `returnSecureToken` is set + /// on the request. private(set) var idToken: String? - /** @property approximateExpirationDate - @brief The approximate expiration date of the access token. - */ + /// The approximate expiration date of the access token. var approximateExpirationDate: Date? - /** @property refreshToken - @brief The refresh token from Secure Token Service. - */ + /// The refresh token from Secure Token Service. var refreshToken: String? - /** @property action - @brief The action code. - */ + /// The action code. var action: String? - /** @property language - @brief The language preference of the user. - */ + /// The language preference of the user. var language: String? - /** @property timeZone - @brief The timezone of the user. - */ + /// The timezone of the user. var timeZone: String? - /** @property photoURL - @brief The URI of the accessible profile picture. - */ + /// The URI of the accessible profile picture. var photoURL: URL? - /** @property dateOfBirth - @brief The birth date of the IdP account. - */ + /// The birth date of the IdP account. var dateOfBirth: String? - /** @property context - @brief The opaque value used by the client to maintain context info between the authentication - request and the IDP callback. - */ + /// The opaque value used by the client to maintain context info between the authentication + /// request and the IDP callback. var context: String? - /** @property verifiedProvider - @brief When action is 'map', contains the idps which can be used for confirmation. - */ + /// When action is 'map', contains the idps which can be used for confirmation. var verifiedProvider: [String]? - /** @property needConfirmation - @brief Whether the assertion is from a non-trusted IDP and need account linking confirmation. - */ + /// Whether the assertion is from a non-trusted IDP and need account linking confirmation. var needConfirmation: Bool = false - /** @property emailRecycled - @brief It's true if the email is recycled. - */ + /// It's true if the email is recycled. var emailRecycled: Bool = false - /** @property emailVerified - @brief The value is true if the IDP is also the email provider. It means the user owns the - email. - */ + /// The value is true if the IDP is also the email provider. It means the user owns the email. var emailVerified: Bool = false - /** @property isNewUser - @brief Flag indicating that the user signing in is a new user and not a returning user. - */ + /// Flag indicating that the user signing in is a new user and not a returning user. var isNewUser: Bool = false - /** @property profile - @brief Dictionary containing the additional IdP specific information. - */ + /// Dictionary containing the additional IdP specific information. var profile: [String: Any]? - /** @property username - @brief The name of the user. - */ + /// The name of the user. var username: String? - /** @property oauthIDToken - @brief The ID token for the OpenID OAuth extension. - */ + /// The ID token for the OpenID OAuth extension. var oauthIDToken: String? - /** @property oauthExpirationDate - @brief The approximate expiration date of the oauth access token. - */ + /// The approximate expiration date of the oauth access token. var oauthExpirationDate: Date? - /** @property oauthAccessToken - @brief The access token for the OpenID OAuth extension. - */ + /// The access token for the OpenID OAuth extension. var oauthAccessToken: String? - /** @property oauthSecretToken - @brief The secret for the OpenID OAuth extention. - */ + /// The secret for the OpenID OAuth extention. var oauthSecretToken: String? - /** @property pendingToken - @brief The pending ID Token string. - */ + /// The pending ID Token string. var pendingToken: String? // MARK: - AuthMFAResponse diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyCustomTokenRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyCustomTokenRequest.swift index e23143c1026..af1518ee653 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyCustomTokenRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyCustomTokenRequest.swift @@ -14,24 +14,16 @@ import Foundation -/** @var kVerifyCustomTokenEndpoint - @brief The "verifyPassword" endpoint. - */ +/// The "verifyPassword" endpoint. private let kVerifyCustomTokenEndpoint = "verifyCustomToken" -/** @var kTokenKey - @brief The key for the "token" value in the request. - */ +/// The key for the "token" value in the request. private let kTokenKey = "token" -/** @var kReturnSecureTokenKey - @brief The key for the "returnSecureToken" value in the request. - */ +/// The key for the "returnSecureToken" value in the request. private let kReturnSecureTokenKey = "returnSecureToken" -/** @var kTenantIDKey - @brief The key for the tenant id value in the request. - */ +/// The key for the tenant id value in the request. private let kTenantIDKey = "tenantId" @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyCustomTokenResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyCustomTokenResponse.swift index a309707b325..74053b5f3b5 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyCustomTokenResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyCustomTokenResponse.swift @@ -14,32 +14,22 @@ import Foundation -/** @class FIRVerifyCustomTokenResponse - @brief Represents the response from the verifyCustomToken endpoint. - */ +/// Represents the response from the verifyCustomToken endpoint. class VerifyCustomTokenResponse: AuthRPCResponse { required init() {} - /** @property idToken - @brief Either an authorization code suitable for performing an STS token exchange, or the - access token from Secure Token Service, depending on whether @c returnSecureToken is set - on the request. - */ + /// Either an authorization code suitable for performing an STS token exchange, or the + /// access token from Secure Token Service, depending on whether `returnSecureToken` is set + /// on the request. var idToken: String? - /** @property approximateExpirationDate - @brief The approximate expiration date of the access token. - */ + /// The approximate expiration date of the access token. var approximateExpirationDate: Date? - /** @property refreshToken - @brief The refresh token from Secure Token Service. - */ + /// The refresh token from Secure Token Service. var refreshToken: String? - /** @property isNewUser - @brief Flag indicating that the user signing in is a new user and not a returning user. - */ + /// Flag indicating that the user signing in is a new user and not a returning user. var isNewUser: Bool = false func setFields(dictionary: [String: AnyHashable]) throws { diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPasswordRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPasswordRequest.swift index de351d79c67..7a95848c985 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPasswordRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPasswordRequest.swift @@ -14,105 +14,68 @@ import Foundation -/** @var kVerifyPasswordEndpoint - @brief The "verifyPassword" endpoint. - */ +/// The "verifyPassword" endpoint. private let kVerifyPasswordEndpoint = "verifyPassword" -/** @var kEmailKey - @brief The key for the "email" value in the request. - */ +/// The key for the "email" value in the request. private let kEmailKey = "email" -/** @var kPasswordKey - @brief The key for the "password" value in the request. - */ +/// The key for the "password" value in the request. private let kPasswordKey = "password" -/** @var kPendingIDTokenKey - @brief The key for the "pendingIdToken" value in the request. - */ +/// The key for the "pendingIdToken" value in the request. private let kPendingIDTokenKey = "pendingIdToken" -/** @var kCaptchaChallengeKey - @brief The key for the "captchaChallenge" value in the request. - */ +/// The key for the "captchaChallenge" value in the request. private let kCaptchaChallengeKey = "captchaChallenge" -/** @var kCaptchaResponseKey - @brief The key for the "captchaResponse" value in the request. - */ +/// The key for the "captchaResponse" value in the request. private let kCaptchaResponseKey = "captchaResponse" -/** @var kClientType - @brief The key for the "clientType" value in the request. - */ +/// The key for the "clientType" value in the request. private let kClientType = "clientType" -/** @var kRecaptchaVersion - @brief The key for the "recaptchaVersion" value in the request. - */ +/// The key for the "recaptchaVersion" value in the request. private let kRecaptchaVersion = "recaptchaVersion" -/** @var kReturnSecureTokenKey - @brief The key for the "returnSecureToken" value in the request. - */ +/// The key for the "returnSecureToken" value in the request. private let kReturnSecureTokenKey = "returnSecureToken" -/** @var kTenantIDKey - @brief The key for the tenant id value in the request. - */ +/// The key for the tenant id value in the request. private let kTenantIDKey = "tenantId" -/** @class FIRVerifyPasswordRequest - @brief Represents the parameters for the verifyPassword endpoint. - @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/verifyPassword - */ +/// Represents the parameters for the verifyPassword endpoint. +/// See https: // developers.google.com/identity/toolkit/web/reference/relyingparty/verifyPassword @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) class VerifyPasswordRequest: IdentityToolkitRequest, AuthRPCRequest { typealias Response = VerifyPasswordResponse - /** @property email - @brief The email of the user. - */ + /// The email of the user. private(set) var email: String - /** @property password - @brief The password inputed by the user. - */ + /// The password inputed by the user. private(set) var password: String - /** @property pendingIDToken - @brief The GITKit token for the non-trusted IDP, which is to be confirmed by the user. - */ + /// The GITKit token for the non-trusted IDP, which is to be confirmed by the user. var pendingIDToken: String? - /** @property captchaChallenge - @brief The captcha challenge. - */ + /// The captcha challenge. var captchaChallenge: String? - /** @property captchaResponse - @brief Response to the captcha. - */ + /// Response to the captcha. var captchaResponse: String? - /** @property captchaResponse - @brief The reCAPTCHA version. - */ + /// The reCAPTCHA version. var recaptchaVersion: String? - /** @property returnSecureToken - @brief Whether the response should return access token and refresh token directly. - @remarks The default value is @c YES . - */ - private(set) var returnSecureToken: Bool + /// Whether the response should return access token and refresh token directly. + /// The default value is `true`. + private(set) var returnSecureToken: Bool = true init(email: String, password: String, requestConfiguration: AuthRequestConfiguration) { self.email = email self.password = password - returnSecureToken = true super.init(endpoint: kVerifyPasswordEndpoint, requestConfiguration: requestConfiguration) } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPasswordResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPasswordResponse.swift index 8a5e03262c6..7735450ed76 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPasswordResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPasswordResponse.swift @@ -14,52 +14,37 @@ import Foundation -/** @class FIRVerifyPasswordResponse - @brief Represents the response from the verifyPassword endpoint. - @remarks Possible error codes: - - FIRAuthInternalErrorCodeUserDisabled - - FIRAuthInternalErrorCodeEmailNotFound - @see https://developers.google.com/identity/toolkit/web/reference/relyingparty/verifyPassword - */ +/// Represents the response from the verifyPassword endpoint. +/// +/// Possible error codes: +/// * FIRAuthInternalErrorCodeUserDisabled +/// * FIRAuthInternalErrorCodeEmailNotFound +/// +/// See https: // developers.google.com/identity/toolkit/web/reference/relyingparty/verifyPassword class VerifyPasswordResponse: AuthRPCResponse, AuthMFAResponse { required init() {} - /** @property localID - @brief The RP local ID if it's already been mapped to the IdP account identified by the - federated ID. - */ + /// The RP local ID if it's already been mapped to the IdP account identified by the federated ID. var localID: String? - /** @property email - @brief The email returned by the IdP. NOTE: The federated login user may not own the email. - */ + /// The email returned by the IdP. NOTE: The federated login user may not own the email. var email: String? - /** @property displayName - @brief The display name of the user. - */ + /// The display name of the user. var displayName: String? - /** @property IDToken - @brief Either an authorization code suitable for performing an STS token exchange, or the - access token from Secure Token Service, depending on whether @c returnSecureToken is set - on the request. - */ + /// Either an authorization code suitable for performing an STS token exchange, or the + /// access token from Secure Token Service, depending on whether `returnSecureToken` is set + /// on the request. private(set) var idToken: String? - /** @property approximateExpirationDate - @brief The approximate expiration date of the access token. - */ + /// The approximate expiration date of the access token. var approximateExpirationDate: Date? - /** @property refreshToken - @brief The refresh token from Secure Token Service. - */ + /// The refresh token from Secure Token Service. var refreshToken: String? - /** @property photoURL - @brief The URI of the accessible profile picture. - */ + /// The URI of the accessible profile picture. var photoURL: URL? // MARK: - AuthMFAResponse diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPhoneNumberRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPhoneNumberRequest.swift index 93389e654b3..2fb0b183709 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPhoneNumberRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPhoneNumberRequest.swift @@ -14,53 +14,32 @@ import Foundation -/** @var kVerifyPhoneNumberEndPoint - @brief The "verifyPhoneNumber" endpoint. - */ +/// The "verifyPhoneNumber" endpoint. private let kVerifyPhoneNumberEndPoint = "verifyPhoneNumber" -/** @var kVerificationIDKey - @brief The key for the verification ID parameter in the request. - */ +/// The key for the verification ID parameter in the request. private let kVerificationIDKey = "sessionInfo" -/** @var kVerificationCodeKey - @brief The key for the verification code parameter in the request. - */ +/// The key for the verification code parameter in the request. private let kVerificationCodeKey = "code" -/** @var kIDTokenKey - @brief The key for the "ID Token" value in the request. - */ +/// The key for the "ID Token" value in the request. private let kIDTokenKey = "idToken" -/** @var kTemporaryProofKey - @brief The key for the temporary proof value in the request. - */ +/// The key for the temporary proof value in the request. private let kTemporaryProofKey = "temporaryProof" -/** @var kPhoneNumberKey - @brief The key for the phone number value in the request. - */ +/// The key for the phone number value in the request. private let kPhoneNumberKey = "phoneNumber" -/** @var kOperationKey - @brief The key for the operation value in the request. - */ +/// The key for the operation value in the request. private let kOperationKey = "operation" -/** @var kTenantIDKey - @brief The key for the tenant id value in the request. - */ +/// The key for the tenant id value in the request. private let kTenantIDKey = "tenantId" extension AuthOperationType { - /** @fn FIRAuthOperationString - @brief Returns a string object corresponding to the provided FIRAuthOperationType value. - @param operationType The value of the FIRAuthOperationType enum which will be translated to its - corresponding string value. - @return The string value corresponding to the FIRAuthOperationType argument. - */ + /// - Returns: The string value corresponding to the AuthOperationType. var operationString: String { switch self { case .unspecified: @@ -81,43 +60,30 @@ extension AuthOperationType { class VerifyPhoneNumberRequest: IdentityToolkitRequest, AuthRPCRequest { typealias Response = VerifyPhoneNumberResponse - /** @property verificationID - @brief The verification ID obtained from the response of @c sendVerificationCode. - */ + /// The verification ID obtained from the response of `sendVerificationCode`. var verificationID: String? - /** @property verificationCode - @brief The verification code provided by the user. - */ + /// The verification code provided by the user. var verificationCode: String? - /** @property accessToken - @brief The STS Access Token for the authenticated user. - */ + /// The STS Access Token for the authenticated user. var accessToken: String? - /** @var temporaryProof - @brief The temporary proof code, previously returned from the backend. - */ + /// The temporary proof code, previously returned from the backend. var temporaryProof: String? - /** @var phoneNumber - @brief The phone number to be verified in the request. - */ + /// The phone number to be verified in the request. var phoneNumber: String? - /** @var operation - @brief The type of operation triggering this verify phone number request. - */ + /// The type of operation triggering this verify phone number request. var operation: AuthOperationType - /** @fn initWithTemporaryProof:phoneNumberAPIKey - @brief Designated initializer. - @param temporaryProof The temporary proof sent by the backed. - @param phoneNumber The phone number associated with the credential to be signed in. - @param operation Indicates what operation triggered the verify phone number request. - @param requestConfiguration An object containing configurations to be added to the request. - */ + /// Designated initializer. + /// - Parameter temporaryProof: The temporary proof sent by the backed. + /// - Parameter phoneNumber: The phone number associated with the credential to be signed in . + /// - Parameter operation: Indicates what operation triggered the verify phone number request. + /// - Parameter requestConfiguration: An object containing configurations to be added to the + /// request. init(temporaryProof: String, phoneNumber: String, operation: AuthOperationType, requestConfiguration: AuthRequestConfiguration) { self.temporaryProof = temporaryProof @@ -126,13 +92,13 @@ class VerifyPhoneNumberRequest: IdentityToolkitRequest, AuthRPCRequest { super.init(endpoint: kVerifyPhoneNumberEndPoint, requestConfiguration: requestConfiguration) } - /** @fn initWithVerificationID:verificationCode:requestConfiguration - @brief Designated initializer. - @param verificationID The verification ID obtained from the response of @c sendVerificationCode. - @param verificationCode The verification code provided by the user. - @param operation Indicates what operation triggered the verify phone number request. - @param requestConfiguration An object containing configurations to be added to the request. - */ + /// Designated initializer. + /// - Parameter verificationID: The verification ID obtained from the response of + /// `sendVerificationCode`. + /// - Parameter verificationCode: The verification code provided by the user. + /// - Parameter operation: Indicates what operation triggered the verify phone number request. + /// - Parameter requestConfiguration: An object containing configurations to be added to the + /// request. init(verificationID: String, verificationCode: String, operation: AuthOperationType, diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPhoneNumberResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPhoneNumberResponse.swift index 9ac4821ed37..82325a25fbe 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPhoneNumberResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPhoneNumberResponse.swift @@ -17,42 +17,27 @@ import Foundation class VerifyPhoneNumberResponse: AuthRPCResponse { required init() {} - /** @property IDToken - @brief Either an authorization code suitable for performing an STS token exchange, or the - access token from Secure Token Service, depending on whether @c returnSecureToken is set - on the request. - */ + /// Either an authorization code suitable for performing an STS token exchange, or the + /// access token from Secure Token Service, depending on whether `returnSecureToken` is set + /// on the request. var idToken: String? - /** @property refreshToken - @brief The refresh token from Secure Token Service. - */ + /// The refresh token from Secure Token Service. var refreshToken: String? - /** @property localID - @brief The Firebase Auth user ID. - */ + /// The Firebase Auth user ID. var localID: String? - /** @property phoneNumber - @brief The verified phone number. - */ + /// The verified phone number. var phoneNumber: String? - /** @property temporaryProof - @brief The temporary proof code returned by the backend. - */ + /// The temporary proof code returned by the backend. var temporaryProof: String? - /** @property isNewUser - @brief Flag indicating that the user signing in is a new user and not a returning user. - */ - + /// Flag indicating that the user signing in is a new user and not a returning user. var isNewUser: Bool = false - /** @property approximateExpirationDate - @brief The approximate expiration date of the access token. - */ + /// The approximate expiration date of the access token. var approximateExpirationDate: Date? // XXX TODO(ObjC): What might this be? diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactor.swift b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactor.swift index dce862d2ed0..8b6cd0b0512 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactor.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactor.swift @@ -18,20 +18,20 @@ import Foundation @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) extension MultiFactor: NSSecureCoding {} - /** @class FIRMultiFactor - @brief The interface defining the multi factor related properties and operations pertaining to a - user. - This class is available on iOS only. - */ + + /// The interface defining the multi factor related properties and operations pertaining to a + /// user. + /// + /// This class is available on iOS only. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @objc(FIRMultiFactor) open class MultiFactor: NSObject { @objc open var enrolledFactors: [MultiFactorInfo] - /** @fn getSessionWithCompletion: - @brief Get a session for a second factor enrollment operation. - @param completion A block with the session identifier for a second factor enrollment operation. - This is used to identify the current user trying to enroll a second factor. - */ + /// Get a session for a second factor enrollment operation. + /// + /// This is used to identify the current user trying to enroll a second factor. + /// - Parameter completion: A block with the session identifier for a second factor enrollment + /// operation. @objc(getSessionWithCompletion:) open func getSessionWithCompletion(_ completion: ((MultiFactorSession?, Error?) -> Void)?) { let session = MultiFactorSession.sessionForCurrentUser @@ -40,11 +40,9 @@ import Foundation } } - /** @fn getSessionWithCompletion: - @brief Get a session for a second factor enrollment operation. - @param completion A block with the session identifier for a second factor enrollment operation. - This is used to identify the current user trying to enroll a second factor. - */ + /// Get a session for a second factor enrollment operation. + /// + /// This is used to identify the current user trying to enroll a second factor. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) open func session() async throws -> MultiFactorSession { return try await withCheckedThrowingContinuation { continuation in @@ -58,12 +56,12 @@ import Foundation } } - /** @fn enrollWithAssertion:displayName:completion: - @brief Enrolls a second factor as identified by the `MultiFactorAssertion` parameter for the - current user. - @param displayName An optional display name associated with the multi factor to enroll. - @param completion The block invoked when the request is complete, or fails. - */ + /// Enrolls a second factor as identified by the `MultiFactorAssertion` parameter for the + /// current user. + /// - Parameter assertion: The `MultiFactorAssertion`. + /// - Parameter displayName: An optional display name associated with the multi factor to + /// enroll. + /// - Parameter completion: The block invoked when the request is complete, or fails. @objc(enrollWithAssertion:displayName:completion:) open func enroll(with assertion: MultiFactorAssertion, displayName: String?, @@ -168,12 +166,12 @@ import Foundation } } - /** @fn enrollWithAssertion:displayName:completion: - @brief Enrolls a second factor as identified by the `MultiFactorAssertion` parameter for the - current user. - @param displayName An optional display name associated with the multi factor to enroll. - @param completion The block invoked when the request is complete, or fails. - */ + /// Enrolls a second factor as identified by the `MultiFactorAssertion` parameter for the + /// current user. + /// - Parameter assertion: The `MultiFactorAssertion`. + /// - Parameter displayName: An optional display name associated with the multi factor to + /// enroll. + /// - Parameter completion: The block invoked when the request is complete, or fails. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) open func enroll(with assertion: MultiFactorAssertion, displayName: String?) async throws { return try await withCheckedThrowingContinuation { continuation in @@ -187,32 +185,24 @@ import Foundation } } - /** @fn unenrollWithInfo:completion: - @brief Unenroll the given multi factor. - @param completion The block invoked when the request to send the verification email is complete, - or fails. - */ + /// Unenroll the given multi factor. + /// - Parameter completion: The block invoked when the request to send the verification email is + /// complete, or fails. @objc(unenrollWithInfo:completion:) open func unenroll(with factorInfo: MultiFactorInfo, completion: ((Error?) -> Void)?) { unenroll(withFactorUID: factorInfo.uid, completion: completion) } - /** @fn unenrollWithInfo:completion: - @brief Unenroll the given multi factor. - @param completion The block invoked when the request to send the verification email is complete, - or fails. - */ + /// Unenroll the given multi factor. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) open func unenroll(with factorInfo: MultiFactorInfo) async throws { try await unenroll(withFactorUID: factorInfo.uid) } - /** @fn unenrollWithFactorUID:completion: - @brief Unenroll the given multi factor. - @param completion The block invoked when the request to send the verification email is complete, - or fails. - */ + /// Unenroll the given multi factor. + /// - Parameter completion: The block invoked when the request to send the verification email is + /// complete, or fails. @objc(unenrollWithFactorUID:completion:) open func unenroll(withFactorUID factorUID: String, completion: ((Error?) -> Void)?) { @@ -252,6 +242,7 @@ import Foundation } } + /// Unenroll the given multi factor. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) open func unenroll(withFactorUID factorUID: String) async throws { return try await withCheckedThrowingContinuation { continuation in diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorAssertion.swift b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorAssertion.swift index 1c313b3a18c..ff3edc94dd0 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorAssertion.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorAssertion.swift @@ -16,15 +16,12 @@ import Foundation #if os(iOS) - /** @class FIRMultiFactorAssertion - @brief The base class for asserting ownership of a second factor. This is equivalent to the - AuthCredential class. - This class is available on iOS only. - */ + /// The base class for asserting ownership of a second factor. This is equivalent to the + /// AuthCredential class. + /// + /// This class is available on iOS only. @objc(FIRMultiFactorAssertion) open class MultiFactorAssertion: NSObject { - /** - @brief The second factor identifier for this opaque object asserting a second factor. - */ + /// The second factor identifier for this opaque object asserting a second factor. @objc open var factorID: String init(factorID: String) { diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorInfo.swift b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorInfo.swift index 60d51cbb5dd..2b19b4963f0 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorInfo.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorInfo.swift @@ -17,29 +17,20 @@ import Foundation #if os(iOS) extension MultiFactorInfo: NSSecureCoding {} - /** @class FIRMultiFactorInfo - @brief Safe public structure used to represent a second factor entity from a client perspective. - This class is available on iOS only. - */ + /// Safe public structure used to represent a second factor entity from a client perspective. + /// + /// This class is available on iOS only. @objc(FIRMultiFactorInfo) open class MultiFactorInfo: NSObject { - /** - @brief The multi-factor enrollment ID. - */ + /// The multi-factor enrollment ID. @objc(UID) public let uid: String - /** - @brief The user friendly name of the current second factor. - */ + /// The user friendly name of the current second factor. @objc public let displayName: String? - /** - @brief The second factor enrollment date. - */ + /// The second factor enrollment date. @objc public let enrollmentDate: Date - /** - @brief The identifier of the second factor. - */ + /// The identifier of the second factor. @objc public let factorID: String init(proto: AuthProtoMFAEnrollment, factorID: String) { diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorResolver.swift b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorResolver.swift index fd0eef4c8d9..a9936928fec 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorResolver.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorResolver.swift @@ -15,36 +15,28 @@ import Foundation #if os(iOS) - /** @class FIRPhoneMultiFactorAssertion - @brief The subclass of base class FIRMultiFactorAssertion, used to assert ownership of a phone - second factor. - This class is available on iOS only. - */ + + /// The subclass of base class `MultiFactorAssertion`, used to assert ownership of a phone + /// second factor. + /// + /// This class is available on iOS only. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @objc(FIRMultiFactorResolver) open class MultiFactorResolver: NSObject { - /** - @brief The opaque session identifier for the current sign-in flow. - */ + /// The opaque session identifier for the current sign-in flow. @objc public let session: MultiFactorSession - /** - @brief The list of hints for the second factors needed to complete the sign-in for the current - session. - */ + /// The list of hints for the second factors needed to complete the sign-in for the current + /// session. @objc public let hints: [MultiFactorInfo] - /** - @brief The Auth reference for the current FIRMultiResolver. - */ + /// The Auth reference for the current `MultiResolver`. @objc public let auth: Auth - /** @fn resolveSignInWithAssertion:completion: - @brief A helper function to help users complete sign in with a second factor using an - FIRMultiFactorAssertion confirming the user successfully completed the second factor - challenge. - @param completion The block invoked when the request is complete, or fails. - */ + /// A helper function to help users complete sign in with a second factor using a + /// `MultiFactorAssertion` confirming the user successfully completed the second factor + /// challenge. + /// - Parameter completion: The block invoked when the request is complete, or fails. @objc(resolveSignInWithAssertion:completion:) open func resolveSignIn(with assertion: MultiFactorAssertion, completion: ((AuthDataResult?, Error?) -> Void)? = nil) { @@ -97,12 +89,9 @@ import Foundation } } - /** @fn resolveSignInWithAssertion:completion: - @brief A helper function to help users complete sign in with a second factor using an - FIRMultiFactorAssertion confirming the user successfully completed the second factor - challenge. - @param completion The block invoked when the request is complete, or fails. - */ + /// A helper function to help users complete sign in with a second factor using a + /// `MultiFactorAssertion` confirming the user successfully completed the second factor + /// challenge. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) open func resolveSignIn(with assertion: MultiFactorAssertion) async throws -> AuthDataResult { return try await withCheckedThrowingContinuation { continuation in diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorSession.swift b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorSession.swift index c1b8979f76e..cc83e3f2f0d 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorSession.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/MultiFactorSession.swift @@ -15,38 +15,31 @@ import Foundation #if os(iOS) - /** @class FIRMultiFactorSession - @brief Opaque object that identifies the current session to enroll a second factor or to - complete sign in when previously enrolled. Identifies the current session to enroll a second factor 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. - */ + + /// Opaque object that identifies the current session to enroll a second factor or to + /// complete sign in when previously enrolled. + /// + /// Identifies the current session to enroll a second factor + /// 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. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @objc(FIRMultiFactorSession) open class MultiFactorSession: NSObject { - /** - @brief The ID token for an enroll flow. This has to be retrieved after recent authentication. - */ + /// The ID token for an enroll flow. This has to be retrieved after recent authentication. var idToken: String? - /** - @brief The pending credential after an enrolled second factor user signs in successfully with the - first factor - */ + /// The pending credential after an enrolled second factor user signs in successfully with the + /// first factor. var mfaPendingCredential: String? - /** - @brief Multi factor info for the current user. - */ + /// Multi factor info for the current user. var multiFactorInfo: MultiFactorInfo? - /** - @brief Current user object - */ + /// Current user object. var currentUser: User? class var sessionForCurrentUser: MultiFactorSession { - // TODO: Fix for the right Auth instance. (broken in ObjC) guard let currentUser = Auth.auth().currentUser else { fatalError("Internal Auth Error: missing user for multifactor auth") } diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/Phone/PhoneMultiFactorAssertion.swift b/FirebaseAuth/Sources/Swift/MultiFactor/Phone/PhoneMultiFactorAssertion.swift index 7271bb1a7ff..999809e4bd3 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/Phone/PhoneMultiFactorAssertion.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/Phone/PhoneMultiFactorAssertion.swift @@ -16,11 +16,10 @@ import Foundation #if os(iOS) - /** @class FIRPhoneMultiFactorAssertion - @brief The subclass of base class FIRMultiFactorAssertion, used to assert ownership of a phone - second factor. - This class is available on iOS only. - */ + /// The subclass of base class FIRMultiFactorAssertion, used to assert ownership of a phone + /// second factor. + /// + /// This class is available on iOS only. @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 9370402de5d..cd213c14196 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/Phone/PhoneMultiFactorGenerator.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/Phone/PhoneMultiFactorGenerator.swift @@ -16,20 +16,19 @@ import Foundation #if os(iOS) - /** @class FIRPhoneMultiFactorGenerator - @brief 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. - */ + /// 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. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @objc(FIRPhoneMultiFactorGenerator) open class PhoneMultiFactorGenerator: NSObject { - /** @fn assertionWithCredential: - @brief Initializes the MFA assertion to confirm ownership of the phone second factor. Note that - this API is used for both enrolling and signing in with a phone second factor. - @param phoneAuthCredential The phone auth credential used for multi factor flows. - */ + /// Initializes the MFA assertion to confirm ownership of the phone second factor. + /// + /// Note that this API is used for both enrolling and signing in with a phone second factor. + /// - Parameter phoneAuthCredential: The phone auth credential used for multi factor flows. @objc(assertionWithCredential:) open class func assertion(with phoneAuthCredential: PhoneAuthCredential) -> PhoneMultiFactorAssertion { diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/Phone/PhoneMultiFactorInfo.swift b/FirebaseAuth/Sources/Swift/MultiFactor/Phone/PhoneMultiFactorInfo.swift index 35be568d8a8..58e5fc5fb8b 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/Phone/PhoneMultiFactorInfo.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/Phone/PhoneMultiFactorInfo.swift @@ -16,27 +16,19 @@ import Foundation #if os(iOS) - /** @class FIRPhoneMultiFactorInfo - @brief Extends the MultiFactorInfo class for phone number second factors. - The identifier of this second factor is "phone". - This class is available on iOS only. - */ + /// Extends the MultiFactorInfo class for phone number second factors. + /// + /// The identifier of this second factor is "phone". + /// + /// This class is available on iOS only. @objc(FIRPhoneMultiFactorInfo) open class PhoneMultiFactorInfo: MultiFactorInfo { - /** - @brief The string identifier for using phone as a second factor. - This constant is available on iOS only. - */ + /// The string identifier for using phone as a second factor. @objc(FIRPhoneMultiFactorID) public static let PhoneMultiFactorID = "phone" - /** - @brief The string identifier for using TOTP as a second factor. - This constant is available on iOS only. - */ + /// The string identifier for using TOTP as a second factor. @objc(FIRTOTPMultiFactorID) public static let TOTPMultiFactorID = "totp" - /** - @brief This is the phone number associated with the current second factor. - */ + /// This is the phone number associated with the current second factor. @objc open var phoneNumber: String init(proto: AuthProtoMFAEnrollment) { diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultFactorAssertion.swift b/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultFactorAssertion.swift index a11eed8074d..b5b1c43f3d6 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultFactorAssertion.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultFactorAssertion.swift @@ -21,11 +21,10 @@ import Foundation case enrollmentID(String) } - /** @class FIRTOTPMultiFactorAssertion - @brief 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. - */ + /// 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. @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 970eb3b1881..4fc574caedb 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultiFactorGenerator.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultiFactorGenerator.swift @@ -15,23 +15,19 @@ import Foundation #if os(iOS) - /** - @class TOTPMultiFactorGenerator - @brief 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. - */ + + /// 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. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @objc(FIRTOTPMultiFactorGenerator) open class TOTPMultiFactorGenerator: NSObject { - /** - @fn generateSecretWithMultiFactorSession - @brief Creates a TOTP secret as part of enrolling a TOTP second factor. Used for generating a - QR code URL or inputting into a TOTP app. This method uses the auth instance corresponding to the - user in the multiFactorSession. - @param session The multiFactorSession instance. - @param completion Completion block - */ + /// Creates a TOTP secret as part of enrolling a TOTP second factor. Used for generating a + /// QR code URL or inputting into a TOTP app. This method uses the auth instance corresponding + /// to the user in the multiFactorSession. + /// - Parameter session: The multiFactorSession instance. + /// - Parameter completion: Completion block @objc(generateSecretWithMultiFactorSession:completion:) open class func generateSecret(with session: MultiFactorSession, completion: @escaping (TOTPSecret?, Error?) -> Void) { @@ -71,9 +67,14 @@ import Foundation } } + /// Creates a TOTP secret as part of enrolling a TOTP second factor. + /// + /// Used for generating a QR code URL or inputting into a TOTP app. This + /// method uses the auth instance correspondingto the user in the multiFactorSession. + /// - Parameter session: The multiFactorSession instance. + /// - Returns: The TOTP secret. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) - open class func generateSecret(with session: MultiFactorSession) async throws - -> TOTPSecret { + open class func generateSecret(with session: MultiFactorSession) async throws -> TOTPSecret { return try await withCheckedThrowingContinuation { continuation in self.generateSecret(with: session) { secret, error in if let secret { @@ -85,13 +86,12 @@ import Foundation } } - /** - @fn assertionForEnrollmentWithSecret: - @brief Initializes the MFA assertion to confirm ownership of the TOTP second factor. This assertion - is used to complete enrollment of TOTP as a second factor. - @param secret The TOTP secret. - @param oneTimePassword one time password string. - */ + /// Initializes the MFA assertion to confirm ownership of the TOTP second factor. + /// + /// This assertion is used to complete enrollment of TOTP as a second factor. + /// - Parameter secret: The TOTP secret. + /// - Parameter oneTimePassword: One time password string. + /// - Returns: The MFA assertion. @objc(assertionForEnrollmentWithSecret:oneTimePassword:) open class func assertionForEnrollment(with secret: TOTPSecret, oneTimePassword: String) -> TOTPMultiFactorAssertion { @@ -99,13 +99,12 @@ import Foundation oneTimePassword: oneTimePassword) } - /** - @fn assertionForSignInWithenrollmentID: - @brief Initializes the MFA assertion to confirm ownership of the TOTP second factor. This - assertion is used to complete signIn with TOTP as a second factor. - @param enrollmentID The ID that identifies the enrolled TOTP second factor. - @param oneTimePassword one time password string. - */ + /// Initializes the MFA assertion to confirm ownership of the TOTP second factor. + /// + /// This assertion is used to complete signIn with TOTP as a second factor. + /// - Parameter enrollmentID: The ID that identifies the enrolled TOTP second factor. + /// - Parameter oneTimePassword: one time password string. + /// - Returns: The MFA assertion. @objc(assertionForSignInWithEnrollmentID:oneTimePassword:) open class func assertionForSignIn(withEnrollmentID enrollmentID: String, oneTimePassword: String) -> TOTPMultiFactorAssertion { @@ -113,5 +112,4 @@ import Foundation oneTimePassword: oneTimePassword) } } - #endif diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultiFactorInfo.swift b/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultiFactorInfo.swift index 7cc3f01ae1c..2273b2aea9e 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultiFactorInfo.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPMultiFactorInfo.swift @@ -16,23 +16,17 @@ import Foundation #if os(iOS) - /** - @class FIRTotpMultiFactorInfo - @brief 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. - */ + /// 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. class TOTPMultiFactorInfo: MultiFactorInfo { - /** - @brief This is the totp info for the second factor. - */ + /// This is the totp info for the second factor. let totpInfo: NSObject? - /** - @fn initWithProto: - @brief Initilize the FIRAuthProtoMFAEnrollment instance with proto. - @param proto FIRAuthProtoMFAEnrollment proto object. - */ + /// Initialize the AuthProtoMFAEnrollment instance with proto. + /// - Parameter proto: AuthProtoMFAEnrollment proto object. init(proto: AuthProtoMFAEnrollment) { totpInfo = proto.totpInfo super.init(proto: proto, factorID: PhoneMultiFactorInfo.TOTPMultiFactorID) @@ -43,5 +37,4 @@ import Foundation fatalError("init(coder:) has not been implemented") } } - #endif diff --git a/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPSecret.swift b/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPSecret.swift index b8d34281a79..5588539a89b 100644 --- a/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPSecret.swift +++ b/FirebaseAuth/Sources/Swift/MultiFactor/TOTP/TOTPSecret.swift @@ -22,28 +22,24 @@ import Foundation #if os(iOS) import UIKit - /** @class FIRTOTPMultiFactorAssertion - @brief 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. - */ + /// 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. @objc(FIRTOTPSecret) open class TOTPSecret: NSObject { - /** - @brief Returns the shared secret key/seed used to generate time-based one-time passwords. - */ + /// Returns the shared secret key/seed used to generate time-based one-time passwords. @objc open func sharedSecretKey() -> String { return secretKey } - /** - @brief Returns a QRCode URL as described in - https://github.com/google/google-authenticator/wiki/Key-Uri-Format - This can be displayed to the user as a QRCode to be scanned into a TOTP app like Google - Authenticator. - @param accountName the name of the account/app. - @param issuer issuer of the TOTP(likely the app name). - @returns A QRCode URL string. - */ + /// Returns a QRCode URL as described in + /// https://github.com/google/google-authenticator/wiki/Key-Uri-Format. + /// + /// This can be displayed to the user as a QRCode to be scanned into a TOTP app like Google + /// Authenticator. + /// - Parameter accountName: The name of the account/app. + /// - Parameter issuer: Issuer of the TOTP(likely the app name). + /// - Returns: A QRCode URL string. @objc(generateQRCodeURLWithAccountName:issuer:) open func generateQRCodeURL(withAccountName accountName: String, issuer: String) -> String { @@ -54,11 +50,10 @@ import Foundation "&algorithm=%\(hashingAlgorithm)&digits=\(codeLength)" } - /** - @brief Opens the specified QR Code URL in a password manager like iCloud Keychain. - * See more details here: - https://developer.apple.com/documentation/authenticationservices/securing_logins_with_icloud_keychain_verification_codes - */ + /// Opens the specified QR Code URL in a password manager like iCloud Keychain. + /// + /// See more details + /// [here](https://developer.apple.com/documentation/authenticationservices/securing_logins_with_icloud_keychain_verification_codes) @objc(openInOTPAppWithQRCodeURL:) open func openInOTPApp(withQRCodeURL qrCodeURL: String) { if GULAppEnvironmentUtil.isAppExtension() { @@ -82,35 +77,23 @@ import Foundation } } - /** - @brief Shared secret key/seed used for enrolling in TOTP MFA and generating OTPs. - */ + /// Shared secret key/seed used for enrolling in TOTP MFA and generating OTPs. private let secretKey: String - /** - @brief Hashing algorithm used. - */ + /// Hashing algorithm used. private let hashingAlgorithm: String? - /** - @brief Length of the one-time passwords to be generated. - */ + /// Length of the one-time passwords to be generated. private let codeLength: Int - /** - @brief The interval (in seconds) when the OTP codes should change. - */ + /// The interval (in seconds) when the OTP codes should change. private let codeIntervalSeconds: Int - /** - @brief The timestamp by which TOTP enrollment should be completed. This can be used by callers to - show a countdown of when to enter OTP code by. - */ + /// The timestamp by which TOTP enrollment should be completed. This can be used by callers to + /// show a countdown of when to enter OTP code by. private let enrollmentCompletionDeadline: Date? - /** - @brief Additional session information. - */ + /// Additional session information. let sessionInfo: String? init(secretKey: String, hashingAlgorithm: String?, codeLength: Int, codeIntervalSeconds: Int, diff --git a/FirebaseAuth/Sources/Swift/Storage/AuthKeychainServices.swift b/FirebaseAuth/Sources/Swift/Storage/AuthKeychainServices.swift index 785ea8b85e5..fcf24de126a 100644 --- a/FirebaseAuth/Sources/Swift/Storage/AuthKeychainServices.swift +++ b/FirebaseAuth/Sources/Swift/Storage/AuthKeychainServices.swift @@ -15,20 +15,15 @@ import FirebaseCoreExtension import Foundation -/** @var kAccountPrefix - @brief The prefix string for keychain item account attribute before the key. - @remarks A number "1" is encoded in the prefix in case we need to upgrade the scheme in future. - */ +/// The prefix string for keychain item account attribute before the key. +/// +/// A number "1" is encoded in the prefix in case we need to upgrade the scheme in future. private let kAccountPrefix = "firebase_auth_1_" -/** @class FIRAuthKeychain - @brief The utility class to manipulate data in iOS Keychain. - */ +/// The utility class to manipulate data in iOS Keychain. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) final class AuthKeychainServices { - /** @var _service - @brief The name of the keychain service. - */ + /// The name of the keychain service. let service: String let keychainStorage: AuthKeychainStorage @@ -41,11 +36,9 @@ final class AuthKeychainServices { keychainStorage = storage } - /** @fn getItemWithQuery:error: - @brief Get the item from keychain by given query. - @param query The query to query the keychain. - @return The item of the given query. `nil`` if not exist. - */ + /// Get the item from keychain by given query. + /// - Parameter query: The query to query the keychain. + /// - Returns: The item of the given query. `nil` if it doesn't exist. func getItem(query: [String: Any]) throws -> Data? { var mutableQuery = query mutableQuery[kSecReturnData as String] = true @@ -71,12 +64,10 @@ final class AuthKeychainServices { } } - /** @fn setItem:withQuery:error: - @brief Set the item into keychain with given query. - @param item The item to be added into keychain. - @param query The query to query the keychain. - @return Whether the operation succeed. - */ + /// Set the item into keychain with given query. + /// - Parameter item: The item to be added into keychain. + /// - Parameter query: The query to query the keychain. + /// - Returns: Whether the operation succeed. func setItem(_ item: Data, withQuery query: [String: Any]) throws { let status: OSStatus let function: String @@ -97,11 +88,8 @@ final class AuthKeychainServices { throw AuthErrorUtils.keychainError(function: function, status: status) } - /** @fn removeItemWithQuery:error: - @brief Remove the item with given queryfrom keychain. - @param query The query to query the keychain. - @return Whether the operation succeed. - */ + /// Remove the item with given queryfrom keychain. + /// - Parameter query: The query to query the keychain. func removeItem(query: [String: Any]) throws { let status = keychainStorage.delete(query: query) if status == noErr || status == errSecItemNotFound { @@ -110,12 +98,10 @@ final class AuthKeychainServices { throw AuthErrorUtils.keychainError(function: "SecItemDelete", status: status) } - /** @var _legacyItemDeletedForKey - @brief Indicates whether or not this class knows that the legacy item for a particular key has - been deleted. - @remarks This dictionary is to avoid unecessary keychain operations against legacy items. - */ - + /// Indicates whether or not this class knows that the legacy item for a particular key has + /// been deleted. + /// + /// This dictionary is to avoid unnecessary keychain operations against legacy items. private var legacyEntryDeletedForKey: Set = [] static func storage(identifier: String) -> Self { @@ -225,10 +211,8 @@ final class AuthKeychainServices { throw AuthErrorUtils.keychainError(function: function, status: status) } - /** @fn deleteLegacyItemsWithKey: - @brief Deletes legacy item from the keychain if it is not already known to be deleted. - @param key The key for the item. - */ + /// Deletes legacy item from the keychain if it is not already known to be deleted. + /// - Parameter key: The key for the item. private func deleteLegacyItem(key: String) { if legacyEntryDeletedForKey.contains(key) { return @@ -238,10 +222,9 @@ final class AuthKeychainServices { legacyEntryDeletedForKey.insert(key) } - /** @fn genericPasswordQueryWithKey: - @brief Returns a keychain query of generic password to be used to manipulate key'ed value. - @param key The key for the value being manipulated, used as the account field in the query. - */ + /// Returns a keychain query of generic password to be used to manipulate key'ed value. + /// - Parameter key: The key for the value being manipulated, used as the account field in the + /// query. private func genericPasswordQuery(key: String) -> [String: Any] { if key.isEmpty { fatalError("The key cannot be empty.") @@ -255,11 +238,10 @@ final class AuthKeychainServices { return query } - /** @fn legacyGenericPasswordQueryWithKey: - @brief Returns a keychain query of generic password without service field, which is used by - previous version of this class. - @param key The key for the value being manipulated, used as the account field in the query. - */ + /// Returns a keychain query of generic password without service field, which is used by + /// previous version of this class . + /// - Parameter key: The key for the value being manipulated, used as the account field in the + /// query. private func legacyGenericPasswordQuery(key: String) -> [String: Any] { [ kSecClass as String: kSecClassGenericPassword, diff --git a/FirebaseAuth/Sources/Swift/Storage/AuthKeychainStorage.swift b/FirebaseAuth/Sources/Swift/Storage/AuthKeychainStorage.swift index 41838c6974c..d53bb612402 100644 --- a/FirebaseAuth/Sources/Swift/Storage/AuthKeychainStorage.swift +++ b/FirebaseAuth/Sources/Swift/Storage/AuthKeychainStorage.swift @@ -14,9 +14,8 @@ import Foundation -/** @class AuthKeychainStorage - @brief Protocol to manage keychain updates. Tests can do a fake implementation. - */ +/// Protocol to manage keychain updates. Tests can do a fake implementation. + @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) protocol AuthKeychainStorage { func get(query: [String: Any], result: inout AnyObject?) -> OSStatus diff --git a/FirebaseAuth/Sources/Swift/Storage/AuthKeychainStorageReal.swift b/FirebaseAuth/Sources/Swift/Storage/AuthKeychainStorageReal.swift index 48f3c775df2..777ff056c3b 100644 --- a/FirebaseAuth/Sources/Swift/Storage/AuthKeychainStorageReal.swift +++ b/FirebaseAuth/Sources/Swift/Storage/AuthKeychainStorageReal.swift @@ -14,9 +14,8 @@ import Foundation -/** @class AuthKeychainStorage - @brief The utility class to update the real keychain - */ +/// The utility class to update the real keychain + @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) class AuthKeychainStorageReal: AuthKeychainStorage { func get(query: [String: Any], result: inout AnyObject?) -> OSStatus { diff --git a/FirebaseAuth/Sources/Swift/Storage/AuthUserDefaults.swift b/FirebaseAuth/Sources/Swift/Storage/AuthUserDefaults.swift index fb7d03645ed..54be969dc01 100644 --- a/FirebaseAuth/Sources/Swift/Storage/AuthUserDefaults.swift +++ b/FirebaseAuth/Sources/Swift/Storage/AuthUserDefaults.swift @@ -16,18 +16,14 @@ import Foundation private let kPersistentDomainNamePrefix = "com.google.Firebase.Auth." -/** @class AuthUserDefaults - @brief The utility class to storage data in NSUserDefaults. - */ +/// The utility class to manage data storage in NSUserDefaults. class AuthUserDefaults { - /** @var _persistentDomainName - @brief The name of the persistent domain in user defaults. - */ + /// The name of the persistent domain in user defaults. + private let persistentDomainName: String - /** @var _storage - @brief The backing NSUserDefaults storage for this instance. - */ + /// The backing NSUserDefaults storage for this instance. + private let storage: UserDefaults static func storage(identifier: String) -> Self { @@ -60,10 +56,9 @@ class AuthUserDefaults { storage.setPersistentDomain(allData, forName: persistentDomainName) } - /** @fn clear - @brief Clears all data from the storage. - @remarks This method is only supposed to be called from tests. - */ + /// Clears all data from the storage. + /// + /// This method is only supposed to be called from tests. func clear() { storage.setPersistentDomain([:], forName: persistentDomainName) } diff --git a/FirebaseAuth/Sources/Swift/SystemService/AuthAPNSToken.swift b/FirebaseAuth/Sources/Swift/SystemService/AuthAPNSToken.swift index 72e3d658209..5031a0e212e 100644 --- a/FirebaseAuth/Sources/Swift/SystemService/AuthAPNSToken.swift +++ b/FirebaseAuth/Sources/Swift/SystemService/AuthAPNSToken.swift @@ -15,27 +15,21 @@ #if !os(macOS) import Foundation - /** @class AuthAPNSToken - @brief A data structure for an APNs token. - */ + /// A data structure for an APNs token. class AuthAPNSToken: NSObject { let data: Data let type: AuthAPNSTokenType - /** @fn initWithData:type: - @brief Initializes the instance. - @param data The APNs token data. - @param type The APNs token type. - @return The initialized instance. - */ + /// Initializes the instance. + /// - Parameter data: The APNs token data. + /// - Parameter type: The APNs token type. + /// - Returns: The initialized instance. init(withData data: Data, type: AuthAPNSTokenType) { self.data = data self.type = type } - /** @property string - @brief The uppercase hexadecimal string form of the APNs token data. - */ + /// The uppercase hexadecimal string form of the APNs token data. lazy var string: String = { let byteArray = [UInt8](data) var s = "" diff --git a/FirebaseAuth/Sources/Swift/SystemService/AuthAPNSTokenManager.swift b/FirebaseAuth/Sources/Swift/SystemService/AuthAPNSTokenManager.swift index dc821748ab0..0e4f87d3b42 100644 --- a/FirebaseAuth/Sources/Swift/SystemService/AuthAPNSTokenManager.swift +++ b/FirebaseAuth/Sources/Swift/SystemService/AuthAPNSTokenManager.swift @@ -32,32 +32,26 @@ extension UIApplication: AuthAPNSTokenApplication {} - /** @class AuthAPNSToken - @brief A data structure for an APNs token. - */ + /// A class to manage APNs token in memory. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) class AuthAPNSTokenManager: NSObject { - /** @property timeout - @brief The timeout for registering for remote notification. - @remarks Only tests should access this property. - */ + /// The timeout for registering for remote notification. + /// + /// Only tests should access this property. var timeout: TimeInterval = 5 - /** @fn initWithApplication: - @brief Initializes the instance. - @param application The @c UIApplication to request the token from. - @return The initialized instance. - */ + /// Initializes the instance. + /// - Parameter application: The `UIApplication` to request the token from. + /// - Returns: The initialized instance. init(withApplication application: AuthAPNSTokenApplication) { self.application = application } - // This function is internal to make visible for tests. - /** @fn getTokenWithCallback: - @brief Attempts to get the APNs token. - @param callback The block to be called either immediately or in future, either when a token - becomes available, or when timeout occurs, whichever happens earlier. - */ + /// Attempts to get the APNs token. + /// - Parameter callback: The block to be called either immediately or in future, either when a + /// token becomes available, or when timeout occurs, whichever happens earlier. + /// + /// This function is internal to make visible for tests. func getTokenInternal(callback: @escaping (AuthAPNSToken?, Error?) -> Void) { if let token = tokenStore { callback(token, nil) @@ -94,14 +88,13 @@ } } - /** @property token - @brief The APNs token, if one is available. - @remarks Setting a token with AuthAPNSTokenTypeUnknown will automatically converts it to - a token with the automatically detected type. - */ + /// The APNs token, if one is available. + /// + /// Setting a token with AuthAPNSTokenTypeUnknown will automatically converts it to + /// a token with the automatically detected type. var token: AuthAPNSToken? { get { - return tokenStore + tokenStore } set(setToken) { guard let setToken else { @@ -119,18 +112,16 @@ } } - // Should only be written to in tests + /// Should only be written to in tests var tokenStore: AuthAPNSToken? - /** @fn cancelWithError: - @brief Cancels any pending `getTokenWithCallback:` request. - @param error The error to return. - */ + /// Cancels any pending `getTokenWithCallback:` request. + /// - Parameter error: The error to return . func cancel(withError error: Error) { callback(withToken: nil, error: error) } - // `application` is a var to enable unit test faking. + /// Enable unit test faking. var application: AuthAPNSTokenApplication private var pendingCallbacks: [(AuthAPNSToken?, Error?) -> Void] = [] diff --git a/FirebaseAuth/Sources/Swift/SystemService/AuthAPNSTokenType.swift b/FirebaseAuth/Sources/Swift/SystemService/AuthAPNSTokenType.swift index 35f8e73a6f5..a11ab157bd4 100644 --- a/FirebaseAuth/Sources/Swift/SystemService/AuthAPNSTokenType.swift +++ b/FirebaseAuth/Sources/Swift/SystemService/AuthAPNSTokenType.swift @@ -15,22 +15,20 @@ #if !os(macOS) import Foundation - /** - * @brief The APNs token type for the app. - * This enum is available on iOS, macOS Catalyst, tvOS, and watchOS only. - */ + /// The APNs token type for the app. + /// + /// This enum is available on iOS, macOS Catalyst, tvOS, and watchOS only. + @objc(FIRAuthAPNSTokenType) public enum AuthAPNSTokenType: Int { - /** Unknown token type. - The actual token type will be detected from the provisioning profile in the app's bundle. - */ + /// Unknown token type. + /// + /// The actual token type will be detected from the provisioning profile in the app's bundle. case unknown - /** Sandbox token type. - */ + /// Sandbox token type. case sandbox - /** Production token type. - */ + /// Production token type. case prod } #endif diff --git a/FirebaseAuth/Sources/Swift/SystemService/AuthAppCredential.swift b/FirebaseAuth/Sources/Swift/SystemService/AuthAppCredential.swift index e1218f84678..38fdcb06e34 100644 --- a/FirebaseAuth/Sources/Swift/SystemService/AuthAppCredential.swift +++ b/FirebaseAuth/Sources/Swift/SystemService/AuthAppCredential.swift @@ -14,26 +14,19 @@ import Foundation -/** @class FIRAuthAppCredential - @brief A class represents a credential that proves the identity of the app. - */ +/// A class represents a credential that proves the identity of the app. @objc(FIRAuthAppCredential) class AuthAppCredential: NSObject, NSSecureCoding { - /** @property receipt - @brief The server acknowledgement of receiving client's claim of identity. - */ + /// The server acknowledgement of receiving client's claim of identity. var receipt: String - /** @property secret - @brief The secret that the client received from server via a trusted channel, if ever. - */ + /// The secret that the client received from server via a trusted channel, if ever. var secret: String? - /** @fn initWithReceipt:secret: - @brief Initializes the instance. - @param receipt The server acknowledgement of receiving client's claim of identity. - @param secret The secret that the client received from server via a trusted channel, if ever. - @return The initialized instance. - */ + /// Initializes the instance. + /// - Parameter receipt: The server acknowledgement of receiving client's claim of identity. + /// - Parameter secret: The secret that the client received from server via a trusted channel, if + /// ever. + /// - Returns: The initialized instance. init(receipt: String, secret: String?) { self.secret = secret self.receipt = receipt diff --git a/FirebaseAuth/Sources/Swift/SystemService/AuthAppCredentialManager.swift b/FirebaseAuth/Sources/Swift/SystemService/AuthAppCredentialManager.swift index 094cac5f292..511d354e2fd 100644 --- a/FirebaseAuth/Sources/Swift/SystemService/AuthAppCredentialManager.swift +++ b/FirebaseAuth/Sources/Swift/SystemService/AuthAppCredentialManager.swift @@ -15,24 +15,20 @@ #if !os(macOS) import Foundation - /** @class FIRAuthAppCredentialManager - @brief A class to manage app credentials backed by iOS Keychain. - */ + /// A class to manage app credentials backed by iOS Keychain. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) class AuthAppCredentialManager: NSObject { let kKeychainDataKey = "app_credentials" let kFullCredentialKey = "full_credential" let kPendingReceiptsKey = "pending_receipts" - /** @property credential - @brief The full credential (which has a secret) to be used by the app, if one is available. - */ + /// The full credential (which has a secret) to be used by the app, if one is available. + var credential: AuthAppCredential? - /** @property maximumNumberOfPendingReceipts - @brief The maximum (but not necessarily the minimum) number of pending receipts to be kept. - @remarks Only tests should access this property. - */ + /// The maximum (but not necessarily the minimum) number of pending receipts to be kept. + /// + /// Only tests should access this property. let maximumNumberOfPendingReceipts = 32 init(withKeychain keychain: AuthKeychainServices) { @@ -116,19 +112,13 @@ } } - /** @var _keychainServices - @brief The keychain for app credentials to load from and to save to. - */ + /// The keychain for app credentials to load from and to save to. private let keychainServices: AuthKeychainServices - /** @var pendingReceipts - @brief A list of pending receipts sorted in the order they were recorded. - */ + /// A list of pending receipts sorted in the order they were recorded. private var pendingReceipts: [String] = [] - /** @var callbacksByReceipt - @brief A map from pending receipts to callbacks. - */ + /// A map from pending receipts to callbacks. private var callbacksByReceipt: [String: (AuthAppCredential) -> Void] = [:] // Only for testing. diff --git a/FirebaseAuth/Sources/Swift/SystemService/AuthNotificationManager.swift b/FirebaseAuth/Sources/Swift/SystemService/AuthNotificationManager.swift index d6383ef3d6e..2d78927bec0 100644 --- a/FirebaseAuth/Sources/Swift/SystemService/AuthNotificationManager.swift +++ b/FirebaseAuth/Sources/Swift/SystemService/AuthNotificationManager.swift @@ -16,79 +16,54 @@ import Foundation import UIKit - /** @class FIRAuthAppCredential - @brief A class represents a credential that proves the identity of the app. - */ + /// A class represents a credential that proves the identity of the app. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) class AuthNotificationManager: NSObject { - /** @var kNotificationKey - @brief The key to locate payload data in the remote notification. - */ + /// The key to locate payload data in the remote notification. private let kNotificationDataKey = "com.google.firebase.auth" - /** @var kNotificationReceiptKey - @brief The key for the receipt in the remote notification payload data. - */ + /// The key for the receipt in the remote notification payload data. private let kNotificationReceiptKey = "receipt" - /** @var kNotificationSecretKey - @brief The key for the secret in the remote notification payload data. - */ + /// The key for the secret in the remote notification payload data. private let kNotificationSecretKey = "secret" - /** @var kNotificationProberKey - @brief The key for marking the prober in the remote notification payload data. - */ + /// The key for marking the prober in the remote notification payload data. private let kNotificationProberKey = "warning" - /** @var kProbingTimeout - @brief Timeout for probing whether the app delegate forwards the remote notification to us. - */ + /// Timeout for probing whether the app delegate forwards the remote notification to us. private let kProbingTimeout = 1.0 - /** @var _application - @brief The application. - */ + /// The application. private let application: UIApplication - /** @var _appCredentialManager - @brief The object to handle app credentials delivered via notification. - */ + /// The object to handle app credentials delivered via notification. private let appCredentialManager: AuthAppCredentialManager - /** @var _hasCheckedNotificationForwarding - @brief Whether notification forwarding has been checked or not. - */ + /// Whether notification forwarding has been checked or not. private var hasCheckedNotificationForwarding: Bool = false - /** @var _isNotificationBeingForwarded - @brief Whether or not notification is being forwarded - */ + /// Whether or not notification is being forwarded private var isNotificationBeingForwarded: Bool = false - /** @property timeout - @brief The timeout for checking for notification forwarding. - @remarks Only tests should access this property. - */ + /// The timeout for checking for notification forwarding. + /// + /// Only tests should access this property. let timeout: TimeInterval - /** @property immediateCallbackForTestFaking - @brief Disable callback waiting for tests. - @remarks Only tests should access this property. - */ + /// Disable callback waiting for tests. + /// + /// Only tests should access this property. var immediateCallbackForTestFaking: (() -> Bool)? - /** @var _pendingCallbacks - @brief All pending callbacks while a check is being performed. - */ + /// All pending callbacks while a check is being performed. private var pendingCallbacks: [(Bool) -> Void]? - /** @fn initWithApplication:appCredentialManager: - @brief Initializes the instance. - @param application The application. - @param appCredentialManager The object to handle app credentials delivered via notification. - @return The initialized instance. - */ + /// Initializes the instance. + /// - Parameter application: The application. + /// - Parameter appCredentialManager: The object to handle app credentials delivered via + /// notification. + /// - Returns: The initialized instance. init(withApplication application: UIApplication, appCredentialManager: AuthAppCredentialManager) { self.application = application @@ -96,11 +71,9 @@ timeout = kProbingTimeout } - /** @fn checkNotificationForwardingWithCallback: - @brief Checks whether or not remote notifications are being forwarded to this class. - @param callback The block to be called either immediately or in future once a result - is available. - */ + /// Checks whether or not remote notifications are being forwarded to this class. + /// - Parameter callback: The block to be called either immediately or in future once a result + /// is available. func checkNotificationForwardingInternal(withCallback callback: @escaping (Bool) -> Void) { if pendingCallbacks != nil { pendingCallbacks?.append(callback) @@ -154,11 +127,9 @@ } } - /** @fn canHandleNotification: - @brief Attempts to handle the remote notification. - @param notification The notification in question. - @return Whether or the notification has been handled. - */ + /// Attempts to handle the remote notification. + /// - Parameter notification: The notification in question. + /// - Returns: Whether or the notification has been handled. func canHandle(notification: [AnyHashable: Any]) -> Bool { var stringDictionary: [String: Any]? let data = notification[kNotificationDataKey] diff --git a/FirebaseAuth/Sources/Swift/SystemService/SecureTokenService.swift b/FirebaseAuth/Sources/Swift/SystemService/SecureTokenService.swift index 6e3dabf164c..e7dc9c8d0d6 100644 --- a/FirebaseAuth/Sources/Swift/SystemService/SecureTokenService.swift +++ b/FirebaseAuth/Sources/Swift/SystemService/SecureTokenService.swift @@ -16,43 +16,33 @@ import Foundation private let kFiveMinutes = 5 * 60.0 -/** @class FIRAuthAppCredential - @brief A class represents a credential that proves the identity of the app. - */ +/// A class represents a credential that proves the identity of the app. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @objc(FIRSecureTokenService) // objc Needed for decoding old versions class SecureTokenService: NSObject, NSSecureCoding { - /** @property requestConfiguration - @brief The configuration for making requests to server. - */ + /// The configuration for making requests to server. var requestConfiguration: AuthRequestConfiguration? - /** @property accessToken - @brief The cached access token. - @remarks This method is specifically for providing the access token to internal clients during - deserialization and sign-in events, and should not be used to retrieve the access token by - anyone else. - */ + /// The cached access token. + /// + /// This method is specifically for providing the access token to internal clients during + /// deserialization and sign-in events, and should not be used to retrieve the access token by + /// anyone else. var accessToken: String - /** @property refreshToken - @brief The refresh token for the user, or @c nil if the user has yet completed sign-in flow. - @remarks This property needs to be set manually after the instance is decoded from archive. - */ + /// The refresh token for the user, or `nil` if the user has yet completed sign-in flow. + /// + /// This property needs to be set manually after the instance is decoded from archive. var refreshToken: String? - /** @property accessTokenExpirationDate - @brief The expiration date of the cached access token. - */ + /// The expiration date of the cached access token. var accessTokenExpirationDate: Date? - /** @fn initWithRequestConfiguration:accessToken:accessTokenExpirationDate:refreshToken - @brief Creates a @c FIRSecureTokenService with access and refresh tokens. - @param requestConfiguration The configuration for making requests to server. - @param accessToken The STS access token. - @param accessTokenExpirationDate The approximate expiration date of the access token. - @param refreshToken The STS refresh token. - */ + /// Creates a `SecureTokenService` with access and refresh tokens. + /// - Parameter requestConfiguration: The configuration for making requests to server. + /// - Parameter accessToken: The STS access token. + /// - Parameter accessTokenExpirationDate: The approximate expiration date of the access token. + /// - Parameter refreshToken: The STS refresh token. init(withRequestConfiguration requestConfiguration: AuthRequestConfiguration?, accessToken: String, accessTokenExpirationDate: Date?, @@ -64,13 +54,13 @@ class SecureTokenService: NSObject, NSSecureCoding { taskQueue = AuthSerialTaskQueue() } - /** @fn fetchAccessTokenForcingRefresh:callback: - @brief Fetch a fresh ephemeral access token for the ID associated with this instance. The token - received in the callback should be considered short lived and not cached. - @param forceRefresh Forces the token to be refreshed. - @param callback Callback block that will be called to return either the token or an error. - Invoked asyncronously on the auth global work queue in the future. - */ + /// Fetch a fresh ephemeral access token for the ID associated with this instance. The token + /// received in the callback should be considered short lived and not cached. + /// + /// Invoked asyncronously on the auth global work queue in the future. + /// - Parameter forceRefresh: Forces the token to be refreshed. + /// - Parameter callback: Callback block that will be called to return either the token or an + /// error. func fetchAccessToken(forcingRefresh forceRefresh: Bool, callback: @escaping (String?, Error?, Bool) -> Void) { taskQueue.enqueueTask { complete in @@ -139,17 +129,17 @@ class SecureTokenService: NSObject, NSSecureCoding { // MARK: Private methods - /** @fn requestAccessToken: - @brief Makes a request to STS for an access token. - @details This handles both the case that the token has not been granted yet and that it just - needs to be refreshed. The caller is responsible for making sure that this is occurring in - a @c _taskQueue task. - @Returns token and Bool indicating if update occurred. - @remarks Because this method is guaranteed to only be called from tasks enqueued in - @c _taskQueue, we do not need any @synchronized guards around access to _accessToken/etc. - since only one of those tasks is ever running at a time, and those tasks are the only - access to and mutation of these instance variables. - */ + /// Makes a request to STS for an access token. + /// + /// This handles both the case that the token has not been granted yet and that it just + /// needs to be refreshed. The caller is responsible for making sure that this is occurring in + /// a `_taskQueue` task. + /// + /// Because this method is guaranteed to only be called from tasks enqueued in + /// `_taskQueue`, we do not need any @synchronized guards around access to _accessToken/etc. + /// since only one of those tasks is ever running at a time, and those tasks are the only + /// access to and mutation of these instance variables. + /// - Returns: Token and Bool indicating if update occurred. private func requestAccessToken(retryIfExpired: Bool) async throws -> (String?, Bool) { // TODO: This was a crash in ObjC SDK, should it callback with an error? guard let refreshToken, let requestConfiguration else { diff --git a/FirebaseAuth/Sources/Swift/User/AdditionalUserInfo.swift b/FirebaseAuth/Sources/Swift/User/AdditionalUserInfo.swift index a6b9f101a89..455075903eb 100644 --- a/FirebaseAuth/Sources/Swift/User/AdditionalUserInfo.swift +++ b/FirebaseAuth/Sources/Swift/User/AdditionalUserInfo.swift @@ -16,27 +16,21 @@ import Foundation extension AdditionalUserInfo: NSSecureCoding {} @objc(FIRAdditionalUserInfo) open class AdditionalUserInfo: NSObject { - /** @property providerID - @brief The provider identifier. - */ + /// The provider identifier. @objc public let providerID: String - /** @property profile - @brief Dictionary containing the additional IdP specific information. - */ + /// Dictionary containing the additional IdP specific information. @objc public let profile: [String: Any]? - /** @property username - @brief username The name of the user. - */ + /// The name of the user. @objc public let username: String? - /** @property isMewUser - @brief Indicates whether or not the current user was signed in for the first time. - */ + /// Indicates whether or not the current user was signed in for the first time. @objc public let isNewUser: Bool // Maintain newUser for Objective C API. + + /// Indicates whether or not the current user was signed in for the first time. @objc open func newUser() -> Bool { return isNewUser } diff --git a/FirebaseAuth/Sources/Swift/User/User.swift b/FirebaseAuth/Sources/Swift/User/User.swift index 6e28a54bd14..f0ea312e752 100644 --- a/FirebaseAuth/Sources/Swift/User/User.swift +++ b/FirebaseAuth/Sources/Swift/User/User.swift @@ -17,86 +17,79 @@ import Foundation @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) extension User: NSSecureCoding {} -/** @class User - @brief Represents a user. Firebase Auth does not attempt to validate users - when loading them from the keychain. Invalidated users (such as those - whose passwords have been changed on another client) are automatically - logged out when an auth-dependent operation is attempted or when the - ID token is automatically refreshed. - @remarks This class is thread-safe. - */ +/// Represents a user. +/// +/// Firebase Auth does not attempt to validate users +/// when loading them from the keychain. Invalidated users (such as those +/// whose passwords have been changed on another client) are automatically +/// logged out when an auth-dependent operation is attempted or when the +/// ID token is automatically refreshed. +/// +/// This class is thread-safe. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @objc(FIRUser) open class User: NSObject, UserInfo { - /** @property anonymous - @brief Indicates the user represents an anonymous user. - */ + /// Indicates the user represents an anonymous user. @objc public private(set) var isAnonymous: Bool + + /// Indicates the user represents an anonymous user. @objc open func anonymous() -> Bool { return isAnonymous } - /** @property emailVerified - @brief Indicates the email address associated with this user has been verified. - */ + /// Indicates the email address associated with this user has been verified. @objc public private(set) var isEmailVerified: Bool + + /// Indicates the email address associated with this user has been verified. @objc open func emailVerified() -> Bool { return isEmailVerified } - /** @property providerData - @brief Profile data for each identity provider, if any. - @remarks This data is cached on sign-in and updated when linking or unlinking. - */ + /// Profile data for each identity provider, if any. + /// + /// This data is cached on sign-in and updated when linking or unlinking. @objc open var providerData: [UserInfo] { return Array(providerDataRaw.values) } private var providerDataRaw: [String: UserInfoImpl] - /** @property metadata - @brief Metadata associated with the Firebase user in question. - */ + /// Metadata associated with the Firebase user in question. @objc public private(set) var metadata: UserMetadata - /** @property tenantID - @brief The tenant ID of the current user. nil if none is available. - */ + /// The tenant ID of the current user. `nil` if none is available. @objc public private(set) var tenantID: String? #if os(iOS) - /** @property multiFactor - @brief Multi factor object associated with the user. - This property is available on iOS only. - */ + /// Multi factor object associated with the user. + /// + /// This property is available on iOS only. @objc public private(set) var multiFactor: MultiFactor #endif - /** @fn updateEmail:completion: - @brief [Deprecated] Updates the email address for the user. On success, the cached user - profile data is updated. Returns AuthErrorCodeInvalidCredentials error when - [Email Enumeration Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) - is enabled. - @remarks May fail if there is already an account with this email address that was created using - email and password authentication. - - @param email The email address for the user. - @param completion Optionally; the block invoked when the user profile change has finished. - Invoked asynchronously on the main thread in the future. - - @remarks Possible error codes: - - + `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was - sent in the request. - + `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in - the console for this action. - + `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for - sending update email. - + `AuthErrorCodeEmailAlreadyInUse` - Indicates the email is already in use by another - account. - + `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed. - + `AuthErrorCodeRequiresRecentLogin` - Updating a user’s email is a security - sensitive operation that requires a recent login from the user. This error indicates - the user has not signed in recently enough. To resolve, reauthenticate the user by - calling `reauthenticate(with:)`. - - @remarks See `AuthErrors` for a list of error codes that are common to all `User` methods. - */ + /// [Deprecated] Updates the email address for the user. + /// + /// On success, the cached user profile data is updated. Returns an error when + /// [Email Enumeration Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) + /// is enabled. + /// + /// May fail if there is already an account with this email address that was created using + /// email and password authentication. + /// + /// Invoked asynchronously on the main thread in the future. + /// + /// Possible error codes: + /// * `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was + /// sent in the request. + /// * `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in + /// the console for this action. + /// * `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for + /// sending update email. + /// * `AuthErrorCodeEmailAlreadyInUse` - Indicates the email is already in use by another + /// account. + /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed. + /// * `AuthErrorCodeRequiresRecentLogin` - Updating a user’s email is a security + /// sensitive operation that requires a recent login from the user. This error indicates + /// the user has not signed in recently enough. To resolve, reauthenticate the user by + /// calling `reauthenticate(with:)`. + /// - Parameter email: The email address for the user. + /// - Parameter completion: Optionally; the block invoked when the user profile change has + /// finished. @available( *, deprecated, @@ -111,35 +104,32 @@ extension User: NSSecureCoding {} } } - /** @fn updateEmail - @brief [Deprecated] Updates the email address for the user. On success, the cached user - profile data is updated. Returns AuthErrorCodeInvalidCredentials error when - [Email Enumeration Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) - is enabled. - @remarks May fail if there is already an account with this email address that was created using - email and password authentication. - - @param email The email address for the user. - @throws Error on failure. - - @remarks Possible error codes: - - + `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was - sent in the request. - + `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in - the console for this action. - + `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for - sending update email. - + `AuthErrorCodeEmailAlreadyInUse` - Indicates the email is already in use by another - account. - + `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed. - + `AuthErrorCodeRequiresRecentLogin` - Updating a user’s email is a security - sensitive operation that requires a recent login from the user. This error indicates - the user has not signed in recently enough. To resolve, reauthenticate the user by - calling `reauthenticate(with:)`. - - @remarks See `AuthErrors` for a list of error codes that are common to all `User` methods. - */ + /// [Deprecated] Updates the email address for the user. + /// + /// On success, the cached user profile data is updated. Throws when + /// [Email Enumeration Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) + /// is enabled. + /// + /// May fail if there is already an account with this email address that was created using + /// email and password authentication. + /// + /// Invoked asynchronously on the main thread in the future. + /// + /// Possible error codes: + /// * `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was + /// sent in the request. + /// * `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in + /// the console for this action. + /// * `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for + /// sending update email. + /// * `AuthErrorCodeEmailAlreadyInUse` - Indicates the email is already in use by another + /// account. + /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed. + /// * `AuthErrorCodeRequiresRecentLogin` - Updating a user’s email is a security + /// sensitive operation that requires a recent login from the user. This error indicates + /// the user has not signed in recently enough. To resolve, reauthenticate the user by + /// calling `reauthenticate(with:)`. + /// - Parameter email: The email address for the user. @available( *, deprecated, @@ -158,27 +148,23 @@ extension User: NSSecureCoding {} } } - /** @fn updatePassword:completion: - @brief Updates the password for the user. On success, the cached user profile data is updated. - - @param password The new password for the user. - @param completion Optionally; the block invoked when the user profile change has finished. - Invoked asynchronously on the main thread in the future. - - @remarks Possible error codes: - - + `AuthErrorCodeOperationNotAllowed` - Indicates the administrator disabled - sign in with the specified identity provider. - + `AuthErrorCodeRequiresRecentLogin` - Updating a user’s password is a security - sensitive operation that requires a recent login from the user. This error indicates - the user has not signed in recently enough. To resolve, reauthenticate the user by - calling `reauthenticate(with:)`. - + `AuthErrorCodeWeakPassword` - Indicates an attempt to set a password that is - considered too weak. The `NSLocalizedFailureReasonErrorKey` field in the `userInfo` - dictionary object will contain more detailed explanation that can be shown to the user. - - @remarks See `AuthErrors` for a list of error codes that are common to all `User` methods. - */ + /// Updates the password for the user. On success, the cached user profile data is updated. + /// + /// Invoked asynchronously on the main thread in the future. + /// + /// Possible error codes: + /// * `AuthErrorCodeOperationNotAllowed` - Indicates the administrator disabled + /// sign in with the specified identity provider. + /// * `AuthErrorCodeRequiresRecentLogin` - Updating a user’s password is a security + /// sensitive operation that requires a recent login from the user. This error indicates + /// the user has not signed in recently enough. To resolve, reauthenticate the user by + /// calling `reauthenticate(with:)`. + /// * `AuthErrorCodeWeakPassword` - Indicates an attempt to set a password that is + /// considered too weak. The `NSLocalizedFailureReasonErrorKey` field in the `userInfo` + /// dictionary object will contain more detailed explanation that can be shown to the user. + /// - Parameter password: The new password for the user. + /// - Parameter completion: Optionally; the block invoked when the user profile change has + /// finished. @objc(updatePassword:completion:) open func updatePassword(to password: String, completion: ((Error?) -> Void)? = nil) { guard password.count > 0 else { @@ -194,26 +180,21 @@ extension User: NSSecureCoding {} } } - /** @fn updatePassword - @brief Updates the password for the user. On success, the cached user profile data is updated. - - @param password The new password for the user. - @throws Error on failure. - - @remarks Possible error codes: - - + `AuthErrorCodeOperationNotAllowed` - Indicates the administrator disabled - sign in with the specified identity provider. - + `AuthErrorCodeRequiresRecentLogin` - Updating a user’s password is a security - sensitive operation that requires a recent login from the user. This error indicates - the user has not signed in recently enough. To resolve, reauthenticate the user by - calling `reauthenticate(with:)`. - + `AuthErrorCodeWeakPassword` - Indicates an attempt to set a password that is - considered too weak. The `NSLocalizedFailureReasonErrorKey` field in the `userInfo` - dictionary object will contain more detailed explanation that can be shown to the user. - - @remarks See `AuthErrors` for a list of error codes that are common to all `User` methods. - */ + /// Updates the password for the user. On success, the cached user profile data is updated. + /// + /// Invoked asynchronously on the main thread in the future. + /// + /// Possible error codes: + /// * `AuthErrorCodeOperationNotAllowed` - Indicates the administrator disabled + /// sign in with the specified identity provider. + /// * `AuthErrorCodeRequiresRecentLogin` - Updating a user’s password is a security + /// sensitive operation that requires a recent login from the user. This error indicates + /// the user has not signed in recently enough. To resolve, reauthenticate the user by + /// calling `reauthenticate(with:)`. + /// * `AuthErrorCodeWeakPassword` - Indicates an attempt to set a password that is + /// considered too weak. The `NSLocalizedFailureReasonErrorKey` field in the `userInfo` + /// dictionary object will contain more detailed explanation that can be shown to the user. + /// - Parameter password: The new password for the user. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) open func updatePassword(to password: String) async throws { return try await withCheckedThrowingContinuation { continuation in @@ -228,26 +209,22 @@ extension User: NSSecureCoding {} } #if os(iOS) - /** @fn updatePhoneNumberCredential:completion: - @brief Updates the phone number for the user. On success, the cached user profile data is - updated. - This method is available on iOS only. - - @param phoneNumberCredential The new phone number credential corresponding to the phone number - to be added to the Firebase account, if a phone number is already linked to the account this - new phone number will replace it. - @param completion Optionally; the block invoked when the user profile change has finished. - Invoked asynchronously on the main thread in the future. - - @remarks Possible error codes: - - + `AuthErrorCodeRequiresRecentLogin` - Updating a user’s phone number is a security - sensitive operation that requires a recent login from the user. This error indicates - the user has not signed in recently enough. To resolve, reauthenticate the user by - calling `reauthenticate(with:)`. - - @remarks See `AuthErrors` for a list of error codes that are common to all `User` methods. - */ + /// Updates the phone number for the user. On success, the cached user profile data is updated. + /// + /// Invoked asynchronously on the main thread in the future. + /// + /// This method is available on iOS only. + /// + /// Possible error codes: + /// * `AuthErrorCodeRequiresRecentLogin` - Updating a user’s phone number is a security + /// sensitive operation that requires a recent login from the user. This error indicates + /// the user has not signed in recently enough. To resolve, reauthenticate the user by + /// calling `reauthenticate(with:)`. + /// - Parameter credential: The new phone number credential corresponding to the + /// phone number to be added to the Firebase account, if a phone number is already linked to the + /// account this new phone number will replace it. + /// - Parameter completion: Optionally; the block invoked when the user profile change has + /// finished. @objc(updatePhoneNumberCredential:completion:) open func updatePhoneNumber(_ credential: PhoneAuthCredential, completion: ((Error?) -> Void)? = nil) { @@ -259,25 +236,20 @@ extension User: NSSecureCoding {} } } - /** @fn updatePhoneNumberCredential - @brief Updates the phone number for the user. On success, the cached user profile data is - updated. - This method is available on iOS only. - - @param phoneNumberCredential The new phone number credential corresponding to the phone number - to be added to the Firebase account, if a phone number is already linked to the account this - new phone number will replace it. - @throws an error. - - @remarks Possible error codes: - - + `AuthErrorCodeRequiresRecentLogin` - Updating a user’s phone number is a security - sensitive operation that requires a recent login from the user. This error indicates - the user has not signed in recently enough. To resolve, reauthenticate the user by - calling `reauthenticate(with:)`. - - @remarks See `AuthErrors` for a list of error codes that are common to all `User` methods. - */ + /// Updates the phone number for the user. On success, the cached user profile data is updated. + /// + /// Invoked asynchronously on the main thread in the future. + /// + /// This method is available on iOS only. + /// + /// Possible error codes: + /// * `AuthErrorCodeRequiresRecentLogin` - Updating a user’s phone number is a security + /// sensitive operation that requires a recent login from the user. This error indicates + /// the user has not signed in recently enough. To resolve, reauthenticate the user by + /// calling `reauthenticate(with:)`. + /// - Parameter phoneNumberCredential: The new phone number credential corresponding to the + /// phone number to be added to the Firebase account, if a phone number is already linked to the + /// account this new phone number will replace it. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) open func updatePhoneNumber(_ credential: PhoneAuthCredential) async throws { return try await withCheckedThrowingContinuation { continuation in @@ -292,14 +264,11 @@ extension User: NSSecureCoding {} } #endif - /** @fn profileChangeRequest - @brief Creates an object which may be used to change the user's profile data. - - @remarks Set the properties of the returned object, then call - `UserProfileChangeRequest.commitChanges()` to perform the updates atomically. - - @return An object which may be used to change the user's profile data atomically. - */ + /// Creates an object which may be used to change the user's profile data. + /// + /// Set the properties of the returned object, then call + /// `UserProfileChangeRequest.commitChanges()` to perform the updates atomically. + /// - Returns: An object which may be used to change the user's profile data atomically. @objc(profileChangeRequest) open func createProfileChangeRequest() -> UserProfileChangeRequest { var result: UserProfileChangeRequest! @@ -309,10 +278,9 @@ extension User: NSSecureCoding {} return result } - /** @property refreshToken - @brief A refresh token; useful for obtaining new access tokens independently. - @remarks This property should only be used for advanced scenarios, and is not typically needed. - */ + /// A refresh token; useful for obtaining new access tokens independently. + /// + /// This property should only be used for advanced scenarios, and is not typically needed. @objc open var refreshToken: String? { var result: String? kAuthGlobalWorkQueue.sync { @@ -321,18 +289,13 @@ extension User: NSSecureCoding {} return result } - /** @fn reloadWithCompletion: - @brief Reloads the user's profile data from the server. - - @param completion Optionally; the block invoked when the reload has finished. Invoked - asynchronously on the main thread in the future. - - @remarks May fail with a `AuthErrorCodeRequiresRecentLogin` error code. In this case - you should call `reauthenticate(with:)` before re-invoking - `updateEmail(to:)`. - - @remarks See `AuthErrors` for a list of error codes that are common to all API methods. - */ + /// Reloads the user's profile data from the server. + /// + /// May fail with an `AuthErrorCodeRequiresRecentLogin` error code. In this case + /// you should call `reauthenticate(with:)` before re-invoking + /// `updateEmail(to:)`. + /// - Parameter completion: Optionally; the block invoked when the reload has finished. Invoked + /// asynchronously on the main thread in the future. @objc open func reload(completion: ((Error?) -> Void)? = nil) { kAuthGlobalWorkQueue.async { self.getAccountInfoRefreshingCache { user, error in @@ -341,18 +304,11 @@ extension User: NSSecureCoding {} } } - /** @fn reload - @brief Reloads the user's profile data from the server. - - @param completion Optionally; the block invoked when the reload has finished. Invoked - asynchronously on the main thread in the future. - - @remarks May fail with a `AuthErrorCodeRequiresRecentLogin` error code. In this case - you should call `reauthenticate(with:)` before re-invoking - `updateEmail(to:)`. - - @remarks See `AuthErrors` for a list of error codes that are common to all API methods. - */ + /// Reloads the user's profile data from the server. + /// + /// May fail with an `AuthErrorCodeRequiresRecentLogin` error code. In this case + /// you should call `reauthenticate(with:)` before re-invoking + /// `updateEmail(to:)`. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) open func reload() async throws { return try await withCheckedThrowingContinuation { continuation in @@ -366,41 +322,36 @@ extension User: NSSecureCoding {} } } - /** @fn reauthenticateWithCredential:completion: - @brief Renews the user's authentication tokens by validating a fresh set of credentials supplied - by the user and returns additional identity provider data. - - @param credential A user-supplied credential, which will be validated by the server. This can be - a successful third-party identity provider sign-in, or an email address and password. - @param completion Optionally; the block invoked when the re-authentication operation has - finished. Invoked asynchronously on the main thread in the future. - - @remarks If the user associated with the supplied credential is different from the current user, - or if the validation of the supplied credentials fails; an error is returned and the current - user remains signed in. - - @remarks Possible error codes: - - + `AuthErrorCodeInvalidCredential` - Indicates the supplied credential is invalid. - This could happen if it has expired or it is malformed. - + `AuthErrorCodeOperationNotAllowed` - Indicates that accounts with the - identity provider represented by the credential are not enabled. Enable them in the - Auth section of the Firebase console. - + `AuthErrorCodeEmailAlreadyInUse` - Indicates the email asserted by the credential - (e.g. the email in a Facebook access token) is already in use by an existing account, - that cannot be authenticated with this method. This error will only be thrown if the - "One account per email address" setting is enabled in the Firebase console, under Auth - settings. Please note that the error code raised in this specific situation may not be - the same on Web and Android. - + `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled. - + `AuthErrorCodeWrongPassword` - Indicates the user attempted reauthentication with - an incorrect password, if credential is of the type `EmailPasswordAuthCredential`. - + `AuthErrorCodeUserMismatch` - Indicates that an attempt was made to - reauthenticate with a user which is not the current user. - + `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed. - - @remarks See `AuthErrors` for a list of error codes that are common to all API methods. - */ + /// Renews the user's authentication tokens by validating a fresh set of credentials supplied + /// by the user and returns additional identity provider data. + /// + /// If the user associated with the supplied credential is different from the current user, + /// or if the validation of the supplied credentials fails; an error is returned and the current + /// user remains signed in. + /// + /// Possible error codes: + /// * `AuthErrorCodeInvalidCredential` - Indicates the supplied credential is invalid. + /// This could happen if it has expired or it is malformed. + /// * `AuthErrorCodeOperationNotAllowed` - Indicates that accounts with the + /// identity provider represented by the credential are not enabled. Enable them in the + /// Auth section of the Firebase console. + /// * `AuthErrorCodeEmailAlreadyInUse` - Indicates the email asserted by the credential + /// (e.g. the email in a Facebook access token) is already in use by an existing account, + /// that cannot be authenticated with this method. This error will only be thrown if the + /// "One account per email address" setting is enabled in the Firebase console, under Auth + /// settings. Please note that the error code raised in this specific situation may not be + /// the same on Web and Android. + /// * `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled. + /// * `AuthErrorCodeWrongPassword` - Indicates the user attempted reauthentication with + /// an incorrect password, if credential is of the type `EmailPasswordAuthCredential`. + /// * `AuthErrorCodeUserMismatch` - Indicates that an attempt was made to + /// reauthenticate with a user which is not the current user. + /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed. + /// - Parameter credential: A user-supplied credential, which will be validated by the server. + /// This can be a successful third-party identity provider sign-in, or an email address and + /// password. + /// - Parameter completion: Optionally; the block invoked when the re-authentication operation has + /// finished. Invoked asynchronously on the main thread in the future. @objc(reauthenticateWithCredential:completion:) open func reauthenticate(with credential: AuthCredential, completion: ((AuthDataResult?, Error?) -> Void)? = nil) { @@ -442,40 +393,35 @@ extension User: NSSecureCoding {} } } - /** @fn reauthenticateWithCredential - @brief Renews the user's authentication tokens by validating a fresh set of credentials supplied - by the user and returns additional identity provider data. - - @param credential A user-supplied credential, which will be validated by the server. This can be - a successful third-party identity provider sign-in, or an email address and password. - @returns An AuthDataResult. - - @remarks If the user associated with the supplied credential is different from the current user, - or if the validation of the supplied credentials fails; an error is returned and the current - user remains signed in. - - @remarks Possible error codes: - - + `AuthErrorCodeInvalidCredential` - Indicates the supplied credential is invalid. - This could happen if it has expired or it is malformed. - + `AuthErrorCodeOperationNotAllowed` - Indicates that accounts with the - identity provider represented by the credential are not enabled. Enable them in the - Auth section of the Firebase console. - + `AuthErrorCodeEmailAlreadyInUse` - Indicates the email asserted by the credential - (e.g. the email in a Facebook access token) is already in use by an existing account, - that cannot be authenticated with this method. This error will only be thrown if the - "One account per email address" setting is enabled in the Firebase console, under Auth - settings. Please note that the error code raised in this specific situation may not be - the same on Web and Android. - + `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled. - + `AuthErrorCodeWrongPassword` - Indicates the user attempted reauthentication with - an incorrect password, if credential is of the type `EmailPasswordAuthCredential`. - + `AuthErrorCodeUserMismatch` - Indicates that an attempt was made to - reauthenticate with a user which is not the current user. - + `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed. - - @remarks See `AuthErrors` for a list of error codes that are common to all API methods. - */ + /// Renews the user's authentication tokens by validating a fresh set of credentials supplied + /// by the user and returns additional identity provider data. + /// + /// If the user associated with the supplied credential is different from the current user, + /// or if the validation of the supplied credentials fails; an error is returned and the current + /// user remains signed in. + /// + /// Possible error codes: + /// * `AuthErrorCodeInvalidCredential` - Indicates the supplied credential is invalid. + /// This could happen if it has expired or it is malformed. + /// * `AuthErrorCodeOperationNotAllowed` - Indicates that accounts with the + /// identity provider represented by the credential are not enabled. Enable them in the + /// Auth section of the Firebase console. + /// * `AuthErrorCodeEmailAlreadyInUse` - Indicates the email asserted by the credential + /// (e.g. the email in a Facebook access token) is already in use by an existing account, + /// that cannot be authenticated with this method. This error will only be thrown if the + /// "One account per email address" setting is enabled in the Firebase console, under Auth + /// settings. Please note that the error code raised in this specific situation may not be + /// the same on Web and Android. + /// * `AuthErrorCodeUserDisabled` - Indicates the user's account is disabled. + /// * `AuthErrorCodeWrongPassword` - Indicates the user attempted reauthentication with + /// an incorrect password, if credential is of the type `EmailPasswordAuthCredential`. + /// * `AuthErrorCodeUserMismatch` - Indicates that an attempt was made to + /// reauthenticate with a user which is not the current user. + /// * `AuthErrorCodeInvalidEmail` - Indicates the email address is malformed. + /// - Parameter credential: A user-supplied credential, which will be validated by the server. + /// This can be a successful third-party identity provider sign-in, or an email address and + /// password. + /// - Returns: The `AuthDataResult` after the reauthentication. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @discardableResult open func reauthenticate(with credential: AuthCredential) async throws -> AuthDataResult { @@ -491,17 +437,16 @@ extension User: NSSecureCoding {} } #if os(iOS) - /** @fn reauthenticateWithProvider:UIDelegate:completion: - @brief Renews the user's authentication using the provided auth provider instance. - This method is available on iOS only. - - @param provider An instance of an auth provider used to initiate the reauthenticate flow. - @param UIDelegate Optionally an instance of a class conforming to the `AuthUIDelegate` - protocol, used for presenting the web context. If nil, a default `AuthUIDelegate` - will be used. - @param completion Optionally; a block which is invoked when the reauthenticate flow finishes, or - is canceled. Invoked asynchronously on the main thread in the future. - */ + /// Renews the user's authentication using the provided auth provider instance. + /// + /// This method is available on iOS only. + /// - Parameter provider: An instance of an auth provider used to initiate the reauthenticate + /// flow. + /// - Parameter uiDelegate: Optionally an instance of a class conforming to the `AuthUIDelegate` + /// protocol, used for presenting the web context. If nil, a default `AuthUIDelegate` + /// will be used. + /// - Parameter completion: Optionally; a block which is invoked when the reauthenticate flow + /// finishes, or is canceled. Invoked asynchronously on the main thread in the future. @objc(reauthenticateWithProvider:UIDelegate:completion:) open func reauthenticate(with provider: FederatedAuthProvider, uiDelegate: AuthUIDelegate?, @@ -520,16 +465,15 @@ extension User: NSSecureCoding {} } } - /** @fn reauthenticateWithProvider:UIDelegate - @brief Renews the user's authentication using the provided auth provider instance. - This method is available on iOS only. - - @param provider An instance of an auth provider used to initiate the reauthenticate flow. - @param UIDelegate Optionally an instance of a class conforming to the `AuthUIDelegate` - protocol, used for presenting the web context. If nil, a default `AuthUIDelegate` - will be used. - @returns An AuthDataResult. - */ + /// Renews the user's authentication using the provided auth provider instance. + /// + /// This method is available on iOS only. + /// - Parameter provider: An instance of an auth provider used to initiate the reauthenticate + /// flow. + /// - Parameter uiDelegate: Optionally an instance of a class conforming to the `AuthUIDelegate` + /// protocol, used for presenting the web context. If nil, a default `AuthUIDelegate` + /// will be used. + /// - Returns: The `AuthDataResult` after the reauthentication. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @discardableResult open func reauthenticate(with provider: FederatedAuthProvider, @@ -546,14 +490,9 @@ extension User: NSSecureCoding {} } #endif - /** @fn getIDTokenWithCompletion: - @brief Retrieves the Firebase authentication token, possibly refreshing it if it has expired. - - @param completion Optionally; the block invoked when the token is available. Invoked - asynchronously on the main thread in the future. - - @remarks See `AuthErrors` for a list of error codes that are common to all API methods. - */ + /// Retrieves the Firebase authentication token, possibly refreshing it if it has expired. + /// - Parameter completion: Optionally; the block invoked when the token is available. Invoked + /// asynchronously on the main thread in the future. @objc(getIDTokenWithCompletion:) open func getIDToken(completion: ((String?, Error?) -> Void)?) { // |getIDTokenForcingRefresh:completion:| is also a public API so there is no need to dispatch to @@ -561,19 +500,14 @@ extension User: NSSecureCoding {} getIDTokenForcingRefresh(false, completion: completion) } - /** @fn getIDTokenForcingRefresh:completion: - @brief Retrieves the Firebase authentication token, possibly refreshing it if it has expired. - - @param forceRefresh Forces a token refresh. Useful if the token becomes invalid for some reason - other than an expiration. - @param completion Optionally; the block invoked when the token is available. Invoked - asynchronously on the main thread in the future. - - @remarks The authentication token will be refreshed (by making a network request) if it has - expired, or if `forceRefresh` is true. - - @remarks See `AuthErrors` for a list of error codes that are common to all API methods. - */ + /// Retrieves the Firebase authentication token, possibly refreshing it if it has expired. + /// + /// The authentication token will be refreshed (by making a network request) if it has + /// expired, or if `forceRefresh` is `true`. + /// - Parameter forceRefresh: Forces a token refresh. Useful if the token becomes invalid for some + /// reason other than an expiration. + /// - Parameter completion: Optionally; the block invoked when the token is available. Invoked + /// asynchronously on the main thread in the future. @objc(getIDTokenForcingRefresh:completion:) open func getIDTokenForcingRefresh(_ forceRefresh: Bool, completion: ((String?, Error?) -> Void)?) { @@ -586,18 +520,13 @@ extension User: NSSecureCoding {} } } - /** @fn getIDTokenForcingRefresh:completion: - @brief Retrieves the Firebase authentication token, possibly refreshing it if it has expired. - - @param forceRefresh Forces a token refresh. Useful if the token becomes invalid for some reason - other than an expiration. - @returns The Token. - - @remarks The authentication token will be refreshed (by making a network request) if it has - expired, or if `forceRefresh` is true. - - @remarks See `AuthErrors` for a list of error codes that are common to all API methods. - */ + /// Retrieves the Firebase authentication token, possibly refreshing it if it has expired. + /// + /// The authentication token will be refreshed (by making a network request) if it has + /// expired, or if `forceRefresh` is `true`. + /// - Parameter forceRefresh: Forces a token refresh. Useful if the token becomes invalid for some + /// reason other than an expiration. + /// - Returns: The Firebase authentication token. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) open func getIDToken() async throws -> String { return try await withCheckedThrowingContinuation { continuation in @@ -611,14 +540,9 @@ extension User: NSSecureCoding {} } } - /** @fn getIDTokenResultWithCompletion: - @brief Retrieves the Firebase authentication token, possibly refreshing it if it has expired. - - @param completion Optionally; the block invoked when the token is available. Invoked - asynchronously on the main thread in the future. - - @remarks See `AuthErrors` for a list of error codes that are common to all API methods. - */ + /// Retrieves the Firebase authentication token, possibly refreshing it if it has expired. + /// - Parameter completion: Optionally; the block invoked when the token is available. Invoked + /// asynchronously on the main thread in the future. @objc(getIDTokenResultWithCompletion:) open func getIDTokenResult(completion: ((AuthTokenResult?, Error?) -> Void)?) { getIDTokenResult(forcingRefresh: false) { tokenResult, error in @@ -630,19 +554,15 @@ extension User: NSSecureCoding {} } } - /** @fn getIDTokenResultForcingRefresh:completion: - @brief Retrieves the Firebase authentication token, possibly refreshing it if it has expired. - - @param forceRefresh Forces a token refresh. Useful if the token becomes invalid for some reason - other than an expiration. - @param completion Optionally; the block invoked when the token is available. Invoked - asynchronously on the main thread in the future. - - @remarks The authentication token will be refreshed (by making a network request) if it has - expired, or if `forceRefresh` is YES. - - @remarks See `AuthErrors` for a list of error codes that are common to all API methods. - */ + /// Retrieves the Firebase authentication token, possibly refreshing it if it has expired. + /// + /// The authentication token will be refreshed (by making a network request) if it has + /// expired, or if `forcingRefresh` is `true`. + /// - Parameter forcingRefresh: Forces a token refresh. Useful if the token becomes invalid for + /// some + /// reason other than an expiration. + /// - Parameter completion: Optionally; the block invoked when the token is available. Invoked + /// asynchronously on the main thread in the future. @objc(getIDTokenResultForcingRefresh:completion:) open func getIDTokenResult(forcingRefresh: Bool, completion: ((AuthTokenResult?, Error?) -> Void)?) { @@ -664,18 +584,13 @@ extension User: NSSecureCoding {} } } - /** @fn getIDTokenResultForcingRefresh - @brief Retrieves the Firebase authentication token, possibly refreshing it if it has expired. - - @param forceRefresh Forces a token refresh. Useful if the token becomes invalid for some reason - other than an expiration. - @returns The token. - - @remarks The authentication token will be refreshed (by making a network request) if it has - expired, or if `forceRefresh` is YES. - - @remarks See `AuthErrors` for a list of error codes that are common to all API methods. - */ + /// Retrieves the Firebase authentication token, possibly refreshing it if it has expired. + /// + /// The authentication token will be refreshed (by making a network request) if it has + /// expired, or if `forceRefresh` is `true`. + /// - Parameter forceRefresh: Forces a token refresh. Useful if the token becomes invalid for some + /// reason other than an expiration. + /// - Returns: The Firebase authentication token. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) open func getIDTokenResult(forcingRefresh forceRefresh: Bool = false) async throws -> AuthTokenResult { @@ -690,29 +605,25 @@ extension User: NSSecureCoding {} } } - /** @fn linkWithCredential:completion: - @brief Associates a user account from a third-party identity provider with this user and - returns additional identity provider data. - - @param credential The credential for the identity provider. - @param completion Optionally; the block invoked when the unlinking is complete, or fails. - Invoked asynchronously on the main thread in the future. - - @remarks Possible error codes: - - + `AuthErrorCodeProviderAlreadyLinked` - Indicates an attempt to link a provider of a - type already linked to this account. - + `AuthErrorCodeCredentialAlreadyInUse` - Indicates an attempt to link with a - credential that has already been linked with a different Firebase account. - + `AuthErrorCodeOperationNotAllowed` - Indicates that accounts with the identity - provider represented by the credential are not enabled. Enable them in the Auth section - of the Firebase console. - - @remarks This method may also return error codes associated with `updateEmail(to:)` and - `updatePassword(to:)` on `User`. - - @remarks See `AuthErrors` for a list of error codes that are common to all `User` methods. - */ + /// Associates a user account from a third-party identity provider with this user and + /// returns additional identity provider data. + /// + /// Invoked asynchronously on the main thread in the future. + /// + /// Possible error codes: + /// * `AuthErrorCodeProviderAlreadyLinked` - Indicates an attempt to link a provider of a + /// type already linked to this account. + /// * `AuthErrorCodeCredentialAlreadyInUse` - Indicates an attempt to link with a + /// credential that has already been linked with a different Firebase account. + /// * `AuthErrorCodeOperationNotAllowed` - Indicates that accounts with the identity + /// provider represented by the credential are not enabled. Enable them in the Auth section + /// of the Firebase console. + /// + /// This method may also return error codes associated with `updateEmail(to:)` and + /// `updatePassword(to:)` on `User`. + /// - Parameter credential: The credential for the identity provider. + /// - Parameter completion: Optionally; the block invoked when the unlinking is complete, or + /// fails. @objc(linkWithCredential:completion:) open func link(with credential: AuthCredential, completion: ((AuthDataResult?, Error?) -> Void)? = nil) { @@ -794,28 +705,24 @@ extension User: NSSecureCoding {} } } - /** @fn linkWithCredential: - @brief Associates a user account from a third-party identity provider with this user and - returns additional identity provider data. - - @param credential The credential for the identity provider. - @returns The AuthDataResult. - - @remarks Possible error codes: - - + `AuthErrorCodeProviderAlreadyLinked` - Indicates an attempt to link a provider of a - type already linked to this account. - + `AuthErrorCodeCredentialAlreadyInUse` - Indicates an attempt to link with a - credential that has already been linked with a different Firebase account. - + `AuthErrorCodeOperationNotAllowed` - Indicates that accounts with the identity - provider represented by the credential are not enabled. Enable them in the Auth section - of the Firebase console. - - @remarks This method may also return error codes associated with `updateEmail(to:)` and - `updatePassword(to:)` on `User`. - - @remarks See `AuthErrors` for a list of error codes that are common to all `User` methods. - */ + /// Associates a user account from a third-party identity provider with this user and + /// returns additional identity provider data. + /// + /// Invoked asynchronously on the main thread in the future. + /// + /// Possible error codes: + /// * `AuthErrorCodeProviderAlreadyLinked` - Indicates an attempt to link a provider of a + /// type already linked to this account. + /// * `AuthErrorCodeCredentialAlreadyInUse` - Indicates an attempt to link with a + /// credential that has already been linked with a different Firebase account. + /// * `AuthErrorCodeOperationNotAllowed` - Indicates that accounts with the identity + /// provider represented by the credential are not enabled. Enable them in the Auth section + /// of the Firebase console. + /// + /// This method may also return error codes associated with `updateEmail(to:)` and + /// `updatePassword(to:)` on `User`. + /// - Parameter credential: The credential for the identity provider. + /// - Returns: An `AuthDataResult`. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @discardableResult open func link(with credential: AuthCredential) async throws -> AuthDataResult { @@ -831,17 +738,15 @@ extension User: NSSecureCoding {} } #if os(iOS) - /** @fn linkWithProvider:UIDelegate:completion: - @brief link the user with the provided auth provider instance. - This method is available on iOSonly. - - @param provider An instance of an auth provider used to initiate the link flow. - @param UIDelegate Optionally an instance of a class conforming to the `AuthUIDelegate` - protocol used for presenting the web context. If nil, a default `AuthUIDelegate` - will be used. - @param completion Optionally; a block which is invoked when the link flow finishes, or - is canceled. Invoked asynchronously on the main thread in the future. - */ + /// Link the user with the provided auth provider instance. + /// + /// This method is available on iOSonly. + /// - Parameter provider: An instance of an auth provider used to initiate the link flow. + /// - Parameter uiDelegate: Optionally an instance of a class conforming to the `AuthUIDelegate` + /// protocol used for presenting the web context. If nil, a default `AuthUIDelegate` will be + /// used. + /// - Parameter completion: Optionally; a block which is invoked when the link flow finishes, or + /// is canceled. Invoked asynchronously on the main thread in the future. @objc(linkWithProvider:UIDelegate:completion:) open func link(with provider: FederatedAuthProvider, uiDelegate: AuthUIDelegate?, @@ -860,16 +765,16 @@ extension User: NSSecureCoding {} } } - /** @fn linkWithProvider:UIDelegate: - @brief link the user with the provided auth provider instance. - This method is available on iOS, macOS Catalyst, and tvOS only. - - @param provider An instance of an auth provider used to initiate the link flow. - @param UIDelegate Optionally an instance of a class conforming to the `AuthUIDelegate` - protocol used for presenting the web context. If nil, a default `AuthUIDelegate` - will be used. - @returns An AuthDataResult. - */ + /// Link the user with the provided auth provider instance. + /// + /// This method is available on iOSonly. + /// - Parameter provider: An instance of an auth provider used to initiate the link flow. + /// - Parameter uiDelegate: Optionally an instance of a class conforming to the `AuthUIDelegate` + /// protocol used for presenting the web context. If nil, a default `AuthUIDelegate` + /// will be used. + /// - Parameter completion: Optionally; a block which is invoked when the link flow finishes, or + /// is canceled. Invoked asynchronously on the main thread in the future. + /// - Returns: An AuthDataResult. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @discardableResult open func link(with provider: FederatedAuthProvider, @@ -886,24 +791,20 @@ extension User: NSSecureCoding {} } #endif - /** @fn unlinkFromProvider:completion: - @brief Disassociates a user account from a third-party identity provider with this user. - - @param provider The provider ID of the provider to unlink. - @param completion Optionally; the block invoked when the unlinking is complete, or fails. - Invoked asynchronously on the main thread in the future. - - @remarks Possible error codes: - - + `AuthErrorCodeNoSuchProvider` - Indicates an attempt to unlink a provider - that is not linked to the account. - + `AuthErrorCodeRequiresRecentLogin` - Updating email is a security sensitive - operation that requires a recent login from the user. This error indicates the user - has not signed in recently enough. To resolve, reauthenticate the user by calling - `reauthenticate(with:)`. - - @remarks See `AuthErrors` for a list of error codes that are common to all `User` methods. - */ + /// Disassociates a user account from a third-party identity provider with this user. + /// + /// Invoked asynchronously on the main thread in the future. + /// + /// Possible error codes: + /// * `AuthErrorCodeNoSuchProvider` - Indicates an attempt to unlink a provider + /// that is not linked to the account. + /// * `AuthErrorCodeRequiresRecentLogin` - Updating email is a security sensitive + /// operation that requires a recent login from the user. This error indicates the user + /// has not signed in recently enough. To resolve, reauthenticate the user by calling + /// `reauthenticate(with:)`. + /// - Parameter provider: The provider ID of the provider to unlink. + /// - Parameter completion: Optionally; the block invoked when the unlinking is complete, or + /// fails. @objc open func unlink(fromProvider provider: String, completion: ((User?, Error?) -> Void)? = nil) { taskQueue.enqueueTask { complete in @@ -972,23 +873,19 @@ extension User: NSSecureCoding {} } } - /** @fn unlinkFromProvider: - @brief Disassociates a user account from a third-party identity provider with this user. - - @param provider The provider ID of the provider to unlink. - @returns The user. - - @remarks Possible error codes: - - + `AuthErrorCodeNoSuchProvider` - Indicates an attempt to unlink a provider - that is not linked to the account. - + `AuthErrorCodeRequiresRecentLogin` - Updating email is a security sensitive - operation that requires a recent login from the user. This error indicates the user - has not signed in recently enough. To resolve, reauthenticate the user by calling - `reauthenticate(with:)`. - - @remarks See `AuthErrors` for a list of error codes that are common to all `User` methods. - */ + /// Disassociates a user account from a third-party identity provider with this user. + /// + /// Invoked asynchronously on the main thread in the future. + /// + /// Possible error codes: + /// * `AuthErrorCodeNoSuchProvider` - Indicates an attempt to unlink a provider + /// that is not linked to the account. + /// * `AuthErrorCodeRequiresRecentLogin` - Updating email is a security sensitive + /// operation that requires a recent login from the user. This error indicates the user + /// has not signed in recently enough. To resolve, reauthenticate the user by calling + /// `reauthenticate(with:)`. + /// - Parameter provider: The provider ID of the provider to unlink. + /// - Returns: The user. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) open func unlink(fromProvider provider: String) async throws -> User { return try await withCheckedThrowingContinuation { continuation in @@ -1002,53 +899,37 @@ extension User: NSSecureCoding {} } } - /** @fn sendEmailVerificationWithCompletion: - @brief Initiates email verification for the user. - - @param completion Optionally; the block invoked when the request to send an email verification - is complete, or fails. Invoked asynchronously on the main thread in the future. - - @remarks Possible error codes: - - + `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was - sent in the request. - + `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in - the console for this action. - + `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for - sending update email. - + `AuthErrorCodeUserNotFound` - Indicates the user account was not found. - - @remarks See `AuthErrors` for a list of error codes that are common to all `User` methods. - */ + /// Initiates email verification for the user. + /// + /// Possible error codes: + /// * `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was + /// sent in the request. + /// * `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in + /// the console for this action. + /// * `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for + /// sending update email. + /// * `AuthErrorCodeUserNotFound` - Indicates the user account was not found. + /// - Parameter completion: Optionally; the block invoked when the request to send an email + /// verification is complete, or fails. Invoked asynchronously on the main thread in the future. @objc(sendEmailVerificationWithCompletion:) open func __sendEmailVerification(withCompletion completion: ((Error?) -> Void)?) { sendEmailVerification(completion: completion) } - /** @fn sendEmailVerificationWithActionCodeSettings:completion: - @brief Initiates email verification for the user. - - @param actionCodeSettings An `ActionCodeSettings` object containing settings related to - handling action codes. - - @remarks Possible error codes: - - + `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was - sent in the request. - + `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in - the console for this action. - + `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for - sending update email. - + `AuthErrorCodeUserNotFound` - Indicates the user account was not found. - + `AuthErrorCodeMissingIosBundleID` - Indicates that the iOS bundle ID is missing when - a iOS App Store ID is provided. - + `AuthErrorCodeMissingAndroidPackageName` - Indicates that the android package name - is missing when the `androidInstallApp` flag is set to true. - + `AuthErrorCodeUnauthorizedDomain` - Indicates that the domain specified in the - continue URL is not allowlisted in the Firebase console. - + `AuthErrorCodeInvalidContinueURI` - Indicates that the domain specified in the - continue URL is not valid. - */ + /// Initiates email verification for the user. + /// + /// Possible error codes: + /// * `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was + /// sent in the request. + /// * `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in + /// the console for this action. + /// * `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for + /// sending update email. + /// * `AuthErrorCodeUserNotFound` - Indicates the user account was not found. + /// - Parameter actionCodeSettings: An `ActionCodeSettings` object containing settings related to + /// handling action codes. + /// - Parameter completion: Optionally; the block invoked when the request to send an email + /// verification is complete, or fails. Invoked asynchronously on the main thread in the future. @objc(sendEmailVerificationWithActionCodeSettings:completion:) open func sendEmailVerification(with actionCodeSettings: ActionCodeSettings? = nil, completion: ((Error?) -> Void)? = nil) { @@ -1082,30 +963,18 @@ extension User: NSSecureCoding {} } } - /** @fn sendEmailVerificationWithActionCodeSettings: - @brief Initiates email verification for the user. - - @param actionCodeSettings An `ActionCodeSettings` object containing settings related to - handling action codes. - - @remarks Possible error codes: - - + `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was - sent in the request. - + `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in - the console for this action. - + `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for - sending update email. - + `AuthErrorCodeUserNotFound` - Indicates the user account was not found. - + `AuthErrorCodeMissingIosBundleID` - Indicates that the iOS bundle ID is missing when - a iOS App Store ID is provided. - + `AuthErrorCodeMissingAndroidPackageName` - Indicates that the android package name - is missing when the `androidInstallApp` flag is set to true. - + `AuthErrorCodeUnauthorizedDomain` - Indicates that the domain specified in the - continue URL is not allowlisted in the Firebase console. - + `AuthErrorCodeInvalidContinueURI` - Indicates that the domain specified in the - continue URL is not valid. - */ + /// Initiates email verification for the user. + /// + /// Possible error codes: + /// * `AuthErrorCodeInvalidRecipientEmail` - Indicates an invalid recipient email was + /// sent in the request. + /// * `AuthErrorCodeInvalidSender` - Indicates an invalid sender email is set in + /// the console for this action. + /// * `AuthErrorCodeInvalidMessagePayload` - Indicates an invalid email template for + /// sending update email. + /// * `AuthErrorCodeUserNotFound` - Indicates the user account was not found. + /// - Parameter actionCodeSettings: An `ActionCodeSettings` object containing settings related to + /// handling action codes. The default value is `nil`. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) open func sendEmailVerification(with actionCodeSettings: ActionCodeSettings? = nil) async throws { return try await withCheckedThrowingContinuation { continuation in @@ -1119,21 +988,15 @@ extension User: NSSecureCoding {} } } - /** @fn deleteWithCompletion: - @brief Deletes the user account (also signs out the user, if this was the current user). - - @param completion Optionally; the block invoked when the request to delete the account is - complete, or fails. Invoked asynchronously on the main thread in the future. - - @remarks Possible error codes: - - + `AuthErrorCodeRequiresRecentLogin` - Updating email is a security sensitive - operation that requires a recent login from the user. This error indicates the user - has not signed in recently enough. To resolve, reauthenticate the user by calling - `reauthenticate(with:)`. - - @remarks See `AuthErrors` for a list of error codes that are common to all `User` methods. - */ + /// Deletes the user account (also signs out the user, if this was the current user). + /// + /// Possible error codes: + /// * `AuthErrorCodeRequiresRecentLogin` - Updating email is a security sensitive + /// operation that requires a recent login from the user. This error indicates the user + /// has not signed in recently enough. To resolve, reauthenticate the user by calling + /// `reauthenticate(with:)`. + /// - Parameter completion: Optionally; the block invoked when the request to delete the account + /// is complete, or fails. Invoked asynchronously on the main thread in the future. @objc open func delete(completion: ((Error?) -> Void)? = nil) { kAuthGlobalWorkQueue.async { self.internalGetToken { accessToken, error in @@ -1147,15 +1010,12 @@ extension User: NSSecureCoding {} guard let requestConfiguration = self.auth?.requestConfiguration else { fatalError("Auth Internal Error: Unexpected nil requestConfiguration.") } - guard let uid = self.uid else { - fatalError("Auth Internal Error: uid is nil.") - } - let request = DeleteAccountRequest(localID: uid, accessToken: accessToken, + let request = DeleteAccountRequest(localID: self.uid, accessToken: accessToken, requestConfiguration: requestConfiguration) Task { do { let _ = try await AuthBackend.call(with: request) - try self.auth?.signOutByForce(withUserID: uid) + try self.auth?.signOutByForce(withUserID: self.uid) User.callInMainThreadWithError(callback: completion, error: nil) } catch { User.callInMainThreadWithError(callback: completion, error: error) @@ -1165,21 +1025,13 @@ extension User: NSSecureCoding {} } } - /** @fn delete - @brief Deletes the user account (also signs out the user, if this was the current user). - - @param completion Optionally; the block invoked when the request to delete the account is - complete, or fails. Invoked asynchronously on the main thread in the future. - - @remarks Possible error codes: - - + `AuthErrorCodeRequiresRecentLogin` - Updating email is a security sensitive - operation that requires a recent login from the user. This error indicates the user - has not signed in recently enough. To resolve, reauthenticate the user by calling - `reauthenticate(with:)`. - - @remarks See `AuthErrors` for a list of error codes that are common to all `User` methods. - */ + /// Deletes the user account (also signs out the user, if this was the current user). + /// + /// Possible error codes: + /// * `AuthErrorCodeRequiresRecentLogin` - Updating email is a security sensitive + /// operation that requires a recent login from the user. This error indicates the user + /// has not signed in recently enough. To resolve, reauthenticate the user by calling + /// `reauthenticate(with:)`. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) open func delete() async throws { return try await withCheckedThrowingContinuation { continuation in @@ -1193,25 +1045,21 @@ extension User: NSSecureCoding {} } } - /** @fn sendEmailVerificationBeforeUpdatingEmail:completion: - @brief Send an email to verify the ownership of the account then update to the new email. - @param email The email to be updated to. - @param completion Optionally; the block invoked when the request to send the verification - email is complete, or fails. - */ + /// Send an email to verify the ownership of the account then update to the new email. + /// - Parameter email: The email to be updated to. + /// - Parameter completion: Optionally; the block invoked when the request to send the + /// verification email is complete, or fails. @objc(sendEmailVerificationBeforeUpdatingEmail:completion:) open func __sendEmailVerificationBeforeUpdating(email: String, completion: ((Error?) -> Void)?) { sendEmailVerification(beforeUpdatingEmail: email, completion: completion) } - /** @fn sendEmailVerificationBeforeUpdatingEmail:completion: - @brief Send an email to verify the ownership of the account then update to the new email. - @param email The email to be updated to. - @param actionCodeSettings An `ActionCodeSettings` object containing settings related to - handling action codes. - @param completion Optionally; the block invoked when the request to send the verification - email is complete, or fails. - */ + /// Send an email to verify the ownership of the account then update to the new email. + /// - Parameter email: The email to be updated to. + /// - Parameter actionCodeSettings: An `ActionCodeSettings` object containing settings related to + /// handling action codes. + /// - Parameter completion: Optionally; the block invoked when the request to send the + /// verification email is complete, or fails. @objc open func sendEmailVerification(beforeUpdatingEmail email: String, actionCodeSettings: ActionCodeSettings? = nil, completion: ((Error?) -> Void)? = nil) { @@ -1245,13 +1093,10 @@ extension User: NSSecureCoding {} } } - /** @fn sendEmailVerificationBeforeUpdatingEmail:completion: - @brief Send an email to verify the ownership of the account then update to the new email. - @param email The email to be updated to. - @param actionCodeSettings An `ActionCodeSettings` object containing settings related to - handling action codes. - @throws on failure. - */ + /// Send an email to verify the ownership of the account then update to the new email. + /// - Parameter email: The email to be updated to. + /// - Parameter actionCodeSettings: An `ActionCodeSettings` object containing settings related to + /// handling action codes. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) open func sendEmailVerification(beforeUpdatingEmail newEmail: String, actionCodeSettings: ActionCodeSettings? = nil) async throws { @@ -1267,16 +1112,16 @@ extension User: NSSecureCoding {} } } - @objc open func rawAccessToken() -> String { + // MARK: Internal implementations below + + func rawAccessToken() -> String { return tokenService.accessToken } - @objc open func accessTokenExpirationDate() -> Date? { + func accessTokenExpirationDate() -> Date? { return tokenService.accessTokenExpirationDate } - // MARK: Internal implementations below - init(withTokenService tokenService: SecureTokenService) { providerDataRaw = [:] taskQueue = AuthSerialTaskQueue() @@ -1325,57 +1170,39 @@ extension User: NSSecureCoding {} return "Firebase" } - /** @property uid - @brief The provider's user ID for the user. - */ - @objc open var uid: String? + /// The provider's user ID for the user. + @objc open var uid: String - /** @property displayName - @brief The name of the user. - */ + /// The name of the user. @objc open var displayName: String? - /** @property photoURL - @brief The URL of the user's profile photo. - */ + /// The URL of the user's profile photo. @objc open var photoURL: URL? - /** @property email - @brief The user's email address. - */ + /// The user's email address. @objc open var email: String? - /** @property phoneNumber - @brief A phone number associated with the user. - @remarks This property is only available for users authenticated via phone number auth. - */ + /// A phone number associated with the user. + /// + /// This property is only available for users authenticated via phone number auth. @objc open var phoneNumber: String? - /** @var hasEmailPasswordCredential - @brief Whether or not the user can be authenticated by using Firebase email and password. - */ + /// Whether or not the user can be authenticated by using Firebase email and password. private var hasEmailPasswordCredential: Bool - /** @var _taskQueue - @brief Used to serialize the update profile calls. - */ + /// Used to serialize the update profile calls. private var taskQueue: AuthSerialTaskQueue - /** @property requestConfiguration - @brief A strong reference to a requestConfiguration instance associated with this user instance. - */ + /// A strong reference to a requestConfiguration instance associated with this user instance. var requestConfiguration: AuthRequestConfiguration - /** @var _tokenService - @brief A secure token service associated with this user. For performing token exchanges and - refreshing access tokens. - */ + /// A secure token service associated with this user. For performing token exchanges and + /// refreshing access tokens. var tokenService: SecureTokenService private weak var _auth: Auth? - /** @property auth - @brief A weak reference to a FIRAuth instance associated with this instance. - */ + + /// A weak reference to an `Auth` instance associated with this instance. weak var auth: Auth? { set { _auth = newValue @@ -1467,13 +1294,11 @@ extension User: NSSecureCoding {} } } - /** @fn executeUserUpdateWithChanges:callback: - @brief Performs a setAccountInfo request by mutating the results of a getAccountInfo response, - atomically in regards to other calls to this method. - @param changeBlock A block responsible for mutating a template @c FIRSetAccountInfoRequest - @param callback A block to invoke when the change is complete. Invoked asynchronously on the - auth global work queue in the future. - */ + /// Performs a setAccountInfo request by mutating the results of a getAccountInfo response, + /// atomically in regards to other calls to this method. + /// - Parameter changeBlock: A block responsible for mutating a template `SetAccountInfoRequest` + /// - Parameter callback: A block to invoke when the change is complete. Invoked asynchronously on + /// the auth global work queue in the future. func executeUserUpdateWithChanges(changeBlock: @escaping (GetAccountInfoResponseUser, SetAccountInfoRequest) -> Void, callback: @escaping (Error?) -> Void) { @@ -1529,13 +1354,12 @@ extension User: NSSecureCoding {} } } - /** @fn setTokenService:callback: - @brief Sets a new token service for the @c FIRUser instance. - @param tokenService The new token service object. - @param callback The block to be called in the global auth working queue once finished. - @remarks The method makes sure the token service has access and refresh token and the new tokens - are saved in the keychain before calling back. - */ + /// Sets a new token service for the `User` instance. + /// + /// The method makes sure the token service has access and refresh token and the new tokens + /// are saved in the keychain before calling back. + /// - Parameter tokenService: The new token service object. + /// - Parameter callback: The block to be called in the global auth working queue once finished. private func setTokenService(tokenService: SecureTokenService, callback: @escaping (Error?) -> Void) { tokenService.fetchAccessToken(forcingRefresh: false) { token, error, tokenUpdated in @@ -1552,11 +1376,9 @@ extension User: NSSecureCoding {} } } - /** @fn getAccountInfoRefreshingCache: - @brief Gets the users's account data from the server, updating our local values. - @param callback Invoked when the request to getAccountInfo has completed, or when an error has - been detected. Invoked asynchronously on the auth global work queue in the future. - */ + /// Gets the users' account data from the server, updating our local values. + /// - Parameter callback: Invoked when the request to getAccountInfo has completed, or when an + /// error has been detected. Invoked asynchronously on the auth global work queue in the future. private func getAccountInfoRefreshingCache(callback: @escaping (GetAccountInfoResponseUser?, Error?) -> Void) { internalGetToken { token, error in @@ -1624,17 +1446,16 @@ extension User: NSSecureCoding {} } #if os(iOS) - /** @fn internalUpdateOrLinkPhoneNumber - @brief Updates the phone number for the user. On success, the cached user profile data is - updated. - - @param phoneAuthCredential The new phone number credential corresponding to the phone number - to be added to the Firebase account, if a phone number is already linked to the account this - new phone number will replace it. - @param isLinkOperation Boolean value indicating whether or not this is a link operation. - @param completion Optionally; the block invoked when the user profile change has finished. - Invoked asynchronously on the global work queue in the future. - */ + /// Updates the phone number for the user. On success, the cached user profile data is updated. + /// + /// Invoked asynchronously on the global work queue in the future. + /// - Parameter credential: The new phone number credential corresponding to the phone + /// number to be added to the Firebase account. If a phone number is already linked to the + /// account, this new phone number will replace it. + /// - Parameter isLinkOperation: Boolean value indicating whether or not this is a link + /// operation. + /// - Parameter completion: Optionally; the block invoked when the user profile change has + /// finished. private func internalUpdateOrLinkPhoneNumber(credential: PhoneAuthCredential, isLinkOperation: Bool, completion: @escaping (Error?) -> Void) { @@ -1956,14 +1777,9 @@ extension User: NSSecureCoding {} } } - /** @fn signOutIfTokenIsInvalidWithError: - @brief Signs out this user if the user or the token is invalid. - @param error The error from the server. - */ + /// Signs out this user if the user or the token is invalid. + /// - Parameter error: The error from the server. private func signOutIfTokenIsInvalid(withError error: Error) { - guard let uid else { - return - } let code = (error as NSError).code if code == AuthErrorCode.userNotFound.rawValue || code == AuthErrorCode.userDisabled.rawValue || @@ -1975,11 +1791,9 @@ extension User: NSSecureCoding {} } } - /** @fn internalGetToken - @brief Retrieves the Firebase authentication token, possibly refreshing it if it has expired. - @param callback The block to invoke when the token is available. Invoked asynchronously on the - global work thread in the future. - */ + /// Retrieves the Firebase authentication token, possibly refreshing it if it has expired. + /// - Parameter callback: The block to invoke when the token is available. Invoked asynchronously + /// on the global work thread in the future. func internalGetToken(forceRefresh: Bool = false, callback: @escaping (String?, Error?) -> Void) { tokenService.fetchAccessToken(forcingRefresh: forceRefresh) { token, error, tokenUpdated in @@ -2010,20 +1824,16 @@ extension User: NSSecureCoding {} } } - /** @fn updateKeychain: - @brief Updates the keychain for user token or info changes. - @param error The error if NO is returned. - @return Whether the operation is successful. - */ + /// Updates the keychain for user token or info changes. + /// - Returns: An `Error` on failure. func updateKeychain() -> Error? { return auth?.updateKeychain(withUser: self) } - /** @fn callInMainThreadWithError - @brief Calls a callback in main thread with error. - @param callback The callback to be called in main thread. - @param error The error to pass to callback. - */ + /// Calls a callback in main thread with error. + /// - Parameter callback: The callback to be called in main thread. + /// - Parameter error: The error to pass to callback. + class func callInMainThreadWithError(callback: ((Error?) -> Void)?, error: Error?) { if let callback { DispatchQueue.main.async { @@ -2032,12 +1842,10 @@ extension User: NSSecureCoding {} } } - /** @fn callInMainThreadWithUserAndError - @brief Calls a callback in main thread with user and error. - @param callback The callback to be called in main thread. - @param user The user to pass to callback if there is no error. - @param error The error to pass to callback. - */ + /// Calls a callback in main thread with user and error. + /// - Parameter callback: The callback to be called in main thread. + /// - Parameter user: The user to pass to callback if there is no error. + /// - Parameter error: The error to pass to callback. private class func callInMainThreadWithUserAndError(callback: ((User?, Error?) -> Void)?, user: User, error: Error?) { @@ -2048,12 +1856,8 @@ extension User: NSSecureCoding {} } } - /** @fn callInMainThreadWithAuthDataResultAndError - @brief Calls a callback in main thread with user and error. - @param callback The callback to be called in main thread. - @param result The result to pass to callback if there is no error. - @param error The error to pass to callback. - */ + /// Calls a callback in main thread with user and error. + /// - Parameter callback: The callback to be called in main thread. private class func callInMainThreadWithAuthDataResultAndError(callback: ( (AuthDataResult?, Error?) -> Void )?, diff --git a/FirebaseAuth/Sources/Swift/User/UserInfo.swift b/FirebaseAuth/Sources/Swift/User/UserInfo.swift index 65e244ba6c7..93e8066abe4 100644 --- a/FirebaseAuth/Sources/Swift/User/UserInfo.swift +++ b/FirebaseAuth/Sources/Swift/User/UserInfo.swift @@ -14,38 +14,25 @@ import Foundation -/** - @brief Represents user data returned from an identity provider. - */ +/// Represents user data returned from an identity provider. @objc(FIRUserInfo) public protocol UserInfo: NSObjectProtocol { - /** @property providerID - @brief The provider identifier. - */ + /// The provider identifier. var providerID: String { get } - /** @property uid - @brief The provider's user ID for the user. - */ - var uid: String? { get } + /// The provider's user ID for the user. + var uid: String { get } - /** @property displayName - @brief The name of the user. - */ + /// The name of the user. var displayName: String? { get } - /** @property photoURL - @brief The URL of the user's profile photo. - */ + /// The URL of the user's profile photo. var photoURL: URL? { get } - /** @property email - @brief The user's email address. - */ + /// The user's email address. var email: String? { get } - /** @property phoneNumber - @brief A phone number associated with the user. - @remarks This property is only available for users authenticated via phone number auth. - */ + /// A phone number associated with the user. + /// + /// This property is only available for users authenticated via phone number auth. var phoneNumber: String? { get } } diff --git a/FirebaseAuth/Sources/Swift/User/UserInfoImpl.swift b/FirebaseAuth/Sources/Swift/User/UserInfoImpl.swift index ad3e4acca89..97eb927cdaa 100644 --- a/FirebaseAuth/Sources/Swift/User/UserInfoImpl.swift +++ b/FirebaseAuth/Sources/Swift/User/UserInfoImpl.swift @@ -17,37 +17,34 @@ import Foundation extension UserInfoImpl: NSSecureCoding {} @objc(FIRUserInfoImpl) class UserInfoImpl: NSObject, UserInfo { - /** @fn userInfoWithGetAccountInfoResponseProviderUserInfo: - @brief A convenience factory method for constructing a @c FIRUserInfo instance from data - returned by the getAccountInfo endpoint. - @param providerUserInfo Data returned by the getAccountInfo endpoint. - @return A new instance of @c FIRUserInfo using data from the getAccountInfo endpoint. - */ + /// A convenience factory method for constructing a `UserInfo` instance from data + /// returned by the getAccountInfo endpoint. + /// - Parameter providerUserInfo: Data returned by the getAccountInfo endpoint. + /// - Returns: A new instance of `UserInfo` using data from the getAccountInfo endpoint. class func userInfo(withGetAccountInfoResponseProviderUserInfo providerUserInfo: GetAccountInfoResponseProviderUserInfo) -> UserInfoImpl { - guard let providerID = providerUserInfo.providerID else { + guard let providerID = providerUserInfo.providerID, + let uid = providerUserInfo.federatedID else { // This was a crash in ObjC implementation. Should providerID be not nullable? - fatalError("Missing providerID from GetAccountInfoResponseProviderUserInfo") + fatalError("Missing providerID or uid from GetAccountInfoResponseProviderUserInfo") } return UserInfoImpl(withProviderID: providerID, - userID: providerUserInfo.federatedID, + userID: uid, displayName: providerUserInfo.displayName, photoURL: providerUserInfo.photoURL, email: providerUserInfo.email, phoneNumber: providerUserInfo.phoneNumber) } - /** @fn initWithProviderID:userID:displayName:photoURL:email: - @brief Designated initializer. - @param providerID The provider identifier. - @param userID The unique user ID for the user (the value of the @c uid field in the token.) - @param displayName The name of the user. - @param photoURL The URL of the user's profile photo. - @param email The user's email address. - @param phoneNumber The user's phone number. - */ + /// Designated initializer. + /// - Parameter providerID: The provider identifier. + /// - Parameter userID: The unique user ID for the user (the value of the uid field in the token.) + /// - Parameter displayName: The name of the user. + /// - Parameter photoURL: The URL of the user's profile photo. + /// - Parameter email: The user's email address. + /// - Parameter phoneNumber: The user's phone number. private init(withProviderID providerID: String, - userID: String?, + userID: String, displayName: String?, photoURL: URL?, email: String?, @@ -61,7 +58,7 @@ extension UserInfoImpl: NSSecureCoding {} } var providerID: String - var uid: String? + var uid: String var displayName: String? var photoURL: URL? var email: String? @@ -90,15 +87,17 @@ extension UserInfoImpl: NSSecureCoding {} } required convenience init?(coder: NSCoder) { - guard let providerID = coder.decodeObject(of: [NSString.self], - forKey: UserInfoImpl.kProviderIDCodingKey) as? String + guard let providerID = coder.decodeObject( + of: [NSString.self], + forKey: UserInfoImpl.kProviderIDCodingKey + ) as? String, + let userID = coder.decodeObject( + of: [NSString.self], + forKey: UserInfoImpl.kUserIDCodingKey + ) as? String else { return nil } - let uid = coder.decodeObject( - of: [NSString.self], - forKey: UserInfoImpl.kUserIDCodingKey - ) as? String let displayName = coder.decodeObject( of: [NSString.self], forKey: UserInfoImpl.kDisplayNameCodingKey @@ -116,7 +115,7 @@ extension UserInfoImpl: NSSecureCoding {} forKey: UserInfoImpl.kPhoneNumberCodingKey ) as? String self.init(withProviderID: providerID, - userID: uid, + userID: userID, displayName: displayName, photoURL: photoURL, email: email, diff --git a/FirebaseAuth/Sources/Swift/User/UserMetadata.swift b/FirebaseAuth/Sources/Swift/User/UserMetadata.swift index bff7875625c..b126350c449 100644 --- a/FirebaseAuth/Sources/Swift/User/UserMetadata.swift +++ b/FirebaseAuth/Sources/Swift/User/UserMetadata.swift @@ -14,23 +14,16 @@ import Foundation -/** @class UserMetadata - @brief A data class representing the metadata corresponding to a Firebase user. - */ - @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) extension UserMetadata: NSSecureCoding {} +/// A data class representing the metadata corresponding to a Firebase user. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @objc(FIRUserMetadata) open class UserMetadata: NSObject { - /** @property lastSignInDate - @brief Stores the last sign in date for the corresponding Firebase user. - */ + /// Stores the last sign in date for the corresponding Firebase user. @objc public let lastSignInDate: Date? - /** @property creationDate - @brief Stores the creation date for the corresponding Firebase user. - */ + /// Stores the creation date for the corresponding Firebase user. @objc public let creationDate: Date? init(withCreationDate creationDate: Date?, lastSignInDate: Date?) { diff --git a/FirebaseAuth/Sources/Swift/User/UserProfileChangeRequest.swift b/FirebaseAuth/Sources/Swift/User/UserProfileChangeRequest.swift index d73da417a1c..493f3d80f92 100644 --- a/FirebaseAuth/Sources/Swift/User/UserProfileChangeRequest.swift +++ b/FirebaseAuth/Sources/Swift/User/UserProfileChangeRequest.swift @@ -14,16 +14,13 @@ import Foundation -/** @class UserProfileChangeRequest - @brief Represents an object capable of updating a user's profile data. - @remarks Properties are marked as being part of a profile update when they are set. Setting a - property value to nil is not the same as leaving the property unassigned. - */ +/// Represents an object capable of updating a user's profile data. +/// +/// Properties are marked as being part of a profile update when they are set. Setting a +/// property value to nil is not the same as leaving the property unassigned. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) @objc(FIRUserProfileChangeRequest) open class UserProfileChangeRequest: NSObject { - /** @property displayName - @brief The name of the user. - */ + /// The name of the user. @objc open var displayName: String? { get { return _displayName } set(newDisplayName) { @@ -39,9 +36,7 @@ import Foundation private var _displayName: String? - /** @property photoURL - @brief The URL of the user's profile photo. - */ + /// The URL of the user's profile photo. @objc open var photoURL: URL? { get { return _photoURL } set(newPhotoURL) { @@ -57,14 +52,13 @@ import Foundation private var _photoURL: URL? - /** @fn commitChangesWithCompletion: - @brief Commits any pending changes. - @remarks This method should only be called once. Once called, property values should not be - changed. - - @param completion Optionally; the block invoked when the user profile change has been applied. - Invoked asynchronously on the main thread in the future. - */ + /// Commits any pending changes. + /// + /// Invoked asynchronously on the main thread in the future. + /// + /// This method should only be called once.Once called, property values should not be changed. + /// - Parameter completion: Optionally; the block invoked when the user profile change has been + /// applied. @objc open func commitChanges(completion: ((Error?) -> Void)? = nil) { kAuthGlobalWorkQueue.async { if self.consumed { @@ -107,13 +101,9 @@ import Foundation } } - /** @fn commitChanges - @brief Commits any pending changes. - @remarks This method should only be called once. Once called, property values should not be - changed. - - @throws on error. - */ + /// Commits any pending changes. + /// + /// This method should only be called once. Once called, property values should not be changed. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) open func commitChanges() async throws { return try await withCheckedThrowingContinuation { continuation in diff --git a/FirebaseAuth/Sources/Swift/Utilities/AuthDefaultUIDelegate.swift b/FirebaseAuth/Sources/Swift/Utilities/AuthDefaultUIDelegate.swift index 55d8a880801..6e7a5b30a57 100644 --- a/FirebaseAuth/Sources/Swift/Utilities/AuthDefaultUIDelegate.swift +++ b/FirebaseAuth/Sources/Swift/Utilities/AuthDefaultUIDelegate.swift @@ -22,16 +22,13 @@ @_implementationOnly import GoogleUtilities_Environment #endif - /** @class AuthDefaultUIDelegate - @brief Class responsible for providing a default FIRAuthUIDelegate. - @remarks This class should be used in the case that a UIDelegate was expected and necessary to - continue a given flow, but none was provided. - */ + /// Class responsible for providing a default AuthUIDelegate. + /// + /// This class should be used in the case that a UIDelegate was expected and necessary to + /// continue a given flow, but none was provided. class AuthDefaultUIDelegate: NSObject, AuthUIDelegate { - /** @fn defaultUIDelegate - @brief Returns a default FIRAuthUIDelegate object. - @return The default FIRAuthUIDelegate object. - */ + /// Returns a default AuthUIDelegate object. + /// - Returns: The default AuthUIDelegate object. class func defaultUIDelegate() -> AuthUIDelegate? { if GULAppEnvironmentUtil.isAppExtension() { // iOS App extensions should not call [UIApplication sharedApplication], even if diff --git a/FirebaseAuth/Sources/Swift/Utilities/AuthErrorUtils.swift b/FirebaseAuth/Sources/Swift/Utilities/AuthErrorUtils.swift index 0496997fc33..353e21aa67a 100644 --- a/FirebaseAuth/Sources/Swift/Utilities/AuthErrorUtils.swift +++ b/FirebaseAuth/Sources/Swift/Utilities/AuthErrorUtils.swift @@ -16,21 +16,15 @@ import Foundation // MARK: - URL response error codes -/** @var kURLResponseErrorCodeInvalidClientID - @brief Error code that indicates that the client ID provided was invalid. - */ +/// Error code that indicates that the client ID provided was invalid. private let kURLResponseErrorCodeInvalidClientID = "auth/invalid-oauth-client-id" -/** @var kURLResponseErrorCodeNetworkRequestFailed - @brief Error code that indicates that a network request within the SFSafariViewController or - WKWebView failed. - */ +/// Error code that indicates that a network request within the SFSafariViewController or WKWebView +/// failed. private let kURLResponseErrorCodeNetworkRequestFailed = "auth/network-request-failed" -/** @var kURLResponseErrorCodeInternalError - @brief Error code that indicates that an internal error occurred within the - SFSafariViewController or WKWebView failed. - */ +/// Error code that indicates that an internal error occurred within the +/// SFSafariViewController or WKWebView failed. private let kURLResponseErrorCodeInternalError = "auth/internal-error" private let kFIRAuthErrorMessageMalformedJWT = @@ -42,10 +36,8 @@ class AuthErrorUtils: NSObject { static let userInfoDeserializedResponseKey = "FIRAuthErrorUserInfoDeserializedResponseKey" static let userInfoDataKey = "FIRAuthErrorUserInfoDataKey" - /** @var kServerErrorDetailMarker - @brief This marker indicates that the server error message contains a detail error message which - should be used instead of the hardcoded client error message. - */ + /// This marker indicates that the server error message contains a detail error message which + /// should be used instead of the hardcoded client error message. private static let kServerErrorDetailMarker = " : " static func error(code: SharedErrorCode, userInfo: [String: Any]? = nil) -> Error { diff --git a/FirebaseAuth/Sources/Swift/Utilities/AuthErrors.swift b/FirebaseAuth/Sources/Swift/Utilities/AuthErrors.swift index ac12c7e459e..d0ab6604c2a 100644 --- a/FirebaseAuth/Sources/Swift/Utilities/AuthErrors.swift +++ b/FirebaseAuth/Sources/Swift/Utilities/AuthErrors.swift @@ -14,575 +14,466 @@ import Foundation -/* - @remarks Error Codes common to all API Methods: - - + `FIRAuthErrorCodeNetworkError` - + `FIRAuthErrorCodeUserNotFound` - + `FIRAuthErrorCodeUserTokenExpired` - + `FIRAuthErrorCodeTooManyRequests` - + `FIRAuthErrorCodeInvalidAPIKey` - + `FIRAuthErrorCodeAppNotAuthorized` - + `FIRAuthErrorCodeKeychainError` - + `FIRAuthErrorCodeInternalError` - - @remarks Common error codes for `FIRUser` operations: - - + `FIRAuthErrorCodeInvalidUserToken` - + `FIRAuthErrorCodeUserDisabled` - */ +/// Error Codes common to all API Methods: @objc(FIRAuthErrors) open class AuthErrors: NSObject { + /// The Firebase Auth error domain. @objc public static let domain: String = "FIRAuthErrorDomain" + /// The name of the key for the error short string of an error code. @objc public static let userInfoNameKey: String = "FIRAuthErrorUserInfoNameKey" - /** - @brief Errors with one of the following three codes: - - `FIRAuthErrorCodeAccountExistsWithDifferentCredential` - - `FIRAuthErrorCodeCredentialAlreadyInUse` - - `FIRAuthErrorCodeEmailAlreadyInUse` - may contain an `NSError.userInfo` dictinary object which contains this key. The value - associated with this key is an NSString of the email address of the account that already - exists. - */ + /// Error codes for Email operations + /// + /// Errors with one of the following three codes: + /// * `accountExistsWithDifferentCredential` + /// * `credentialAlreadyInUse` + /// * emailAlreadyInUse` + /// + /// may contain an `NSError.userInfo` dictionary object which contains this key. The value + /// associated with this key is an NSString of the email address of the account that already + /// exists. @objc public static let userInfoEmailKey: String = "FIRAuthErrorUserInfoEmailKey" - /** - @brief The key used to read the updated Auth credential from the userInfo dictionary of the - NSError object returned. This is the updated auth credential the developer should use for - recovery if applicable. - */ + /// The key used to read the updated Auth credential from the userInfo dictionary of the + /// NSError object returned. This is the updated auth credential the developer should use for + /// recovery if applicable. @objc public static let userInfoUpdatedCredentialKey: String = "FIRAuthErrorUserInfoUpdatedCredentialKey" - /** - @brief The key used to read the MFA resolver from the userInfo dictionary of the NSError object - returned when 2FA is required for sign-incompletion. - */ + /// The key used to read the MFA resolver from the userInfo dictionary of the NSError object + /// returned when 2FA is required for sign-incompletion. @objc(FIRAuthErrorUserInfoMultiFactorResolverKey) public static let userInfoMultiFactorResolverKey: String = "FIRAuthErrorUserInfoMultiFactorResolverKey" } +/// Error codes used by Firebase Auth. @objc(FIRAuthErrorCode) public enum AuthErrorCode: Int { - /** Indicates a validation error with the custom token. - */ + /// Indicates a validation error with the custom token. case invalidCustomToken = 17000 - /** Indicates the service account and the API key belong to different projects. - */ + + /// Indicates the service account and the API key belong to different projects. case customTokenMismatch = 17002 - /** Indicates the IDP token or requestUri is invalid. - */ + /// Indicates the IDP token or requestUri is invalid. case invalidCredential = 17004 - /** Indicates the user's account is disabled on the server. - */ + /// Indicates the user's account is disabled on the server. case userDisabled = 17005 - /** Indicates the administrator disabled sign in with the specified identity provider. - */ + /// Indicates the administrator disabled sign in with the specified identity provider. case operationNotAllowed = 17006 - /** Indicates the email used to attempt a sign up is already in use. - */ + /// Indicates the email used to attempt a sign up is already in use. case emailAlreadyInUse = 17007 - /** Indicates the email is invalid. - */ + /// Indicates the email is invalid. case invalidEmail = 17008 - /** Indicates the user attempted sign in with a wrong password. - */ + /// Indicates the user attempted sign in with a wrong password. case wrongPassword = 17009 - /** Indicates that too many requests were made to a server method. - */ + /// Indicates that too many requests were made to a server method. case tooManyRequests = 17010 - /** Indicates the user account was not found. - */ + /// Indicates the user account was not found. case userNotFound = 17011 - /** Indicates account linking is required. - */ + /// Indicates account linking is required. case accountExistsWithDifferentCredential = 17012 - /** Indicates the user has attemped to change email or password more than 5 minutes after - signing in. - */ + /// Indicates the user has attemped to change email or password more than 5 minutes after + /// signing in. case requiresRecentLogin = 17014 - /** Indicates an attempt to link a provider to which the account is already linked. - */ + /// Indicates an attempt to link a provider to which the account is already linked. case providerAlreadyLinked = 17015 - /** Indicates an attempt to unlink a provider that is not linked. - */ + /// Indicates an attempt to unlink a provider that is not linked. case noSuchProvider = 17016 - /** Indicates user's saved auth credential is invalid the user needs to sign in again. - */ + /// Indicates user's saved auth credential is invalid the user needs to sign in again. case invalidUserToken = 17017 - /** Indicates a network error occurred (such as a timeout interrupted connection or - unreachable host). These types of errors are often recoverable with a retry. The - `NSUnderlyingError` field in the `NSError.userInfo` dictionary will contain the error - encountered. - */ + /// Indicates a network error occurred (such as a timeout interrupted connection or + /// unreachable host). These types of errors are often recoverable with a retry. The + /// `NSUnderlyingError` field in the `NSError.userInfo` dictionary will contain the error + /// encountered. case networkError = 17020 - /** Indicates the saved token has expired for example the user may have changed account - password on another device. The user needs to sign in again on the device that made this - request. - */ + /// Indicates the saved token has expired for example the user may have changed account + /// password on another device. The user needs to sign in again on the device that made this + /// request. case userTokenExpired = 17021 - /** Indicates an invalid API key was supplied in the request. - */ + /// Indicates an invalid API key was supplied in the request. case invalidAPIKey = 17023 - /** Indicates that an attempt was made to reauthenticate with a user which is not the current - user. - */ + /// Indicates that an attempt was made to reauthenticate with a user which is not the current + /// user. case userMismatch = 17024 - /** Indicates an attempt to link with a credential that has already been linked with a - different Firebase account - */ + /// Indicates an attempt to link with a credential that has already been linked with a + /// different Firebase account. case credentialAlreadyInUse = 17025 - /** Indicates an attempt to set a password that is considered too weak. - */ + /// Indicates an attempt to set a password that is considered too weak. case weakPassword = 17026 - /** Indicates the App is not authorized to use Firebase Authentication with the - provided API Key. - */ + /// Indicates the App is not authorized to use Firebase Authentication with the + /// provided API Key. case appNotAuthorized = 17028 - /** Indicates the OOB code is expired. - */ + /// Indicates the OOB code is expired. case expiredActionCode = 17029 - /** Indicates the OOB code is invalid. - */ + /// Indicates the OOB code is invalid. case invalidActionCode = 17030 - /** Indicates that there are invalid parameters in the payload during a "send password reset - * email" attempt. - */ + /// Indicates that there are invalid parameters in the payload during a + /// "send password reset email" attempt. case invalidMessagePayload = 17031 - /** Indicates that the sender email is invalid during a "send password reset email" attempt. - */ + /// Indicates that the sender email is invalid during a "send password reset email" attempt. case invalidSender = 17032 - /** Indicates that the recipient email is invalid. - */ + /// Indicates that the recipient email is invalid. case invalidRecipientEmail = 17033 - /** Indicates that an email address was expected but one was not provided. - */ + /// Indicates that an email address was expected but one was not provided. case missingEmail = 17034 // The enum values 17035 is reserved and should NOT be used for new error codes. - /** Indicates that the iOS bundle ID is missing when a iOS App Store ID is provided. - */ + /// Indicates that the iOS bundle ID is missing when a iOS App Store ID is provided. case missingIosBundleID = 17036 - /** Indicates that the android package name is missing when the `androidInstallApp` flag is set - to true. - */ + /// Indicates that the android package name is missing when the `androidInstallApp` flag is set + /// to `true`. case missingAndroidPackageName = 17037 - /** Indicates that the domain specified in the continue URL is not allowlisted in the Firebase - console. - */ + /// Indicates that the domain specified in the continue URL is not allowlisted in the Firebase + /// console. case unauthorizedDomain = 17038 - /** Indicates that the domain specified in the continue URI is not valid. - */ + /// Indicates that the domain specified in the continue URI is not valid. case invalidContinueURI = 17039 - /** Indicates that a continue URI was not provided in a request to the backend which requires - one. - */ + /// Indicates that a continue URI was not provided in a request to the backend which requires one. case missingContinueURI = 17040 - /** Indicates that a phone number was not provided in a call to - `verifyPhoneNumber:completion:`. - */ + /// Indicates that a phone number was not provided in a call to + /// `verifyPhoneNumber:completion:`. case missingPhoneNumber = 17041 - /** Indicates that an invalid phone number was provided in a call to - `verifyPhoneNumber:completion:`. - */ + /// Indicates that an invalid phone number was provided in a call to + /// `verifyPhoneNumber:completion:`. case invalidPhoneNumber = 17042 - /** Indicates that the phone auth credential was created with an empty verification code. - */ + /// Indicates that the phone auth credential was created with an empty verification code. case missingVerificationCode = 17043 - /** Indicates that an invalid verification code was used in the verifyPhoneNumber request. - */ + /// Indicates that an invalid verification code was used in the verifyPhoneNumber request. case invalidVerificationCode = 17044 - /** Indicates that the phone auth credential was created with an empty verification ID. - */ + /// Indicates that the phone auth credential was created with an empty verification ID. case missingVerificationID = 17045 - /** Indicates that an invalid verification ID was used in the verifyPhoneNumber request. - */ + /// Indicates that an invalid verification ID was used in the verifyPhoneNumber request. case invalidVerificationID = 17046 - /** Indicates that the APNS device token is missing in the verifyClient request. - */ + /// Indicates that the APNS device token is missing in the verifyClient request. case missingAppCredential = 17047 - /** Indicates that an invalid APNS device token was used in the verifyClient request. - */ + /// Indicates that an invalid APNS device token was used in the verifyClient request. case invalidAppCredential = 17048 // The enum values between 17048 and 17051 are reserved and should NOT be used for new error // codes. - /** Indicates that the SMS code has expired. - */ + /// Indicates that the SMS code has expired. case sessionExpired = 17051 - /** Indicates that the quota of SMS messages for a given project has been exceeded. - */ + /// Indicates that the quota of SMS messages for a given project has been exceeded. case quotaExceeded = 17052 - /** Indicates that the APNs device token could not be obtained. The app may not have set up - remote notification correctly or may fail to forward the APNs device token to FIRAuth - if app delegate swizzling is disabled. - */ + /// Indicates that the APNs device token could not be obtained. The app may not have set up + /// remote notification correctly or may fail to forward the APNs device token to Auth + /// if app delegate swizzling is disabled. case missingAppToken = 17053 - /** Indicates that the app fails to forward remote notification to FIRAuth. - */ + /// Indicates that the app fails to forward remote notification to FIRAuth. case notificationNotForwarded = 17054 - /** Indicates that the app could not be verified by Firebase during phone number authentication. - */ + /// Indicates that the app could not be verified by Firebase during phone number authentication. case appNotVerified = 17055 - /** Indicates that the reCAPTCHA token is not valid. - */ + /// Indicates that the reCAPTCHA token is not valid. case captchaCheckFailed = 17056 - /** Indicates that an attempt was made to present a new web context while one was already being - presented. - */ + /// Indicates that an attempt was made to present a new web context while one was already being + /// presented. case webContextAlreadyPresented = 17057 - /** Indicates that the URL presentation was cancelled prematurely by the user. - */ + /// Indicates that the URL presentation was cancelled prematurely by the user. case webContextCancelled = 17058 - /** Indicates a general failure during the app verification flow. - */ + /// Indicates a general failure during the app verification flow. case appVerificationUserInteractionFailure = 17059 - /** Indicates that the clientID used to invoke a web flow is invalid. - */ + /// Indicates that the clientID used to invoke a web flow is invalid. case invalidClientID = 17060 - /** Indicates that a network request within a SFSafariViewController or WKWebView failed. - */ + /// Indicates that a network request within a SFSafariViewController or WKWebView failed. case webNetworkRequestFailed = 17061 - /** Indicates that an internal error occurred within a SFSafariViewController or WKWebView. - */ + /// Indicates that an internal error occurred within a SFSafariViewController or WKWebView. case webInternalError = 17062 - /** Indicates a general failure during a web sign-in flow. - */ + /// Indicates a general failure during a web sign-in flow. case webSignInUserInteractionFailure = 17063 - /** Indicates that the local player was not authenticated prior to attempting Game Center - signin. - */ + /// Indicates that the local player was not authenticated prior to attempting Game Center signin. case localPlayerNotAuthenticated = 17066 - /** Indicates that a non-null user was expected as an argmument to the operation but a null - user was provided. - */ + /// Indicates that a non-null user was expected as an argmument to the operation but a null + /// user was provided. case nullUser = 17067 - /** Indicates that a Firebase Dynamic Link is not activated. - */ + /// Indicates that a Firebase Dynamic Link is not activated. case dynamicLinkNotActivated = 17068 - /** - * Represents the error code for when the given provider id for a web operation is invalid. - */ + /// Represents the error code for when the given provider id for a web operation is invalid. case invalidProviderID = 17071 - /** - * Represents the error code for when an attempt is made to update the current user with a - * tenantId that differs from the current FirebaseAuth instance's tenantId. - */ + /// Represents the error code for when an attempt is made to update the current user with a + /// tenantId that differs from the current FirebaseAuth instance's tenantId. case tenantIDMismatch = 17072 - /** - * Represents the error code for when a request is made to the backend with an associated tenant - * ID for an operation that does not support multi-tenancy. - */ + /// Represents the error code for when a request is made to the backend with an associated tenant + /// ID for an operation that does not support multi-tenancy. case unsupportedTenantOperation = 17073 - /** Indicates that the Firebase Dynamic Link domain used is either not configured or is - unauthorized for the current project. - */ + /// Indicates that the Firebase Dynamic Link domain used is either not configured or is + /// unauthorized for the current project. case invalidDynamicLinkDomain = 17074 - /** Indicates that the credential is rejected because it's misformed or mismatching. - */ + /// Indicates that the credential is rejected because it's misformed or mismatching. case rejectedCredential = 17075 - /** Indicates that the GameKit framework is not linked prior to attempting Game Center signin. - */ + /// Indicates that the GameKit framework is not linked prior to attempting Game Center signin. case gameKitNotLinked = 17076 - /** Indicates that the second factor is required for signin. - */ + /// Indicates that the second factor is required for signin. case secondFactorRequired = 17078 - /** Indicates that the multi factor session is missing. - */ + /// Indicates that the multi factor session is missing. case missingMultiFactorSession = 17081 - /** Indicates that the multi factor info is missing. - */ + /// Indicates that the multi factor info is missing. case missingMultiFactorInfo = 17082 - /** Indicates that the multi factor session is invalid. - */ + /// Indicates that the multi factor session is invalid. case invalidMultiFactorSession = 17083 - /** Indicates that the multi factor info is not found. - */ + /// Indicates that the multi factor info is not found. case multiFactorInfoNotFound = 17084 - /** Indicates that the operation is admin restricted. - */ + /// Indicates that the operation is admin restricted. case adminRestrictedOperation = 17085 - /** Indicates that the email is required for verification. - */ + /// Indicates that the email is required for verification. case unverifiedEmail = 17086 - /** Indicates that the second factor is already enrolled. - */ + /// Indicates that the second factor is already enrolled. case secondFactorAlreadyEnrolled = 17087 - /** Indicates that the maximum second factor count is exceeded. - */ + /// Indicates that the maximum second factor count is exceeded. case maximumSecondFactorCountExceeded = 17088 - /** Indicates that the first factor is not supported. - */ + /// Indicates that the first factor is not supported. case unsupportedFirstFactor = 17089 - /** Indicates that the a verifed email is required to changed to. - */ + /// Indicates that the a verifed email is required to changed to. case emailChangeNeedsVerification = 17090 - /** Indicates that the request does not contain a client identifier. - */ + /// Indicates that the request does not contain a client identifier. case missingClientIdentifier = 17093 - /** Indicates that the nonce is missing or invalid. - */ + /// Indicates that the nonce is missing or invalid. case missingOrInvalidNonce = 17094 - /** Raised when n Cloud Function returns a blocking error. Will include a message returned from - * the function. - */ + /// Raised when n Cloud Function returns a blocking error. Will include a message returned from + /// the function. case blockingCloudFunctionError = 17105 - /** Indicates that reCAPTCHA Enterprise integration is not enabled for this project. - */ + /// Indicates that reCAPTCHA Enterprise integration is not enabled for this project. case recaptchaNotEnabled = 17200 - /** Indicates that the reCAPTCHA token is missing from the backend request. - */ + /// Indicates that the reCAPTCHA token is missing from the backend request. case missingRecaptchaToken = 17201 - /** Indicates that the reCAPTCHA token sent with the backend request is invalid. - */ + /// Indicates that the reCAPTCHA token sent with the backend request is invalid. case invalidRecaptchaToken = 17202 - /** Indicates that the requested reCAPTCHA action is invalid. - */ + /// Indicates that the requested reCAPTCHA action is invalid. case invalidRecaptchaAction = 17203 - /** Indicates that the client type is missing from the request. - */ + /// Indicates that the client type is missing from the request. case missingClientType = 17204 - /** Indicates that the reCAPTCHA version is missing from the request. - */ + /// Indicates that the reCAPTCHA version is missing from the request. case missingRecaptchaVersion = 17205 - /** Indicates that the reCAPTCHA version sent to the backend is invalid. - */ + /// Indicates that the reCAPTCHA version sent to the backend is invalid. case invalidRecaptchaVersion = 17206 - /** Indicates that the request type sent to the backend is invalid. - */ + /// Indicates that the request type sent to the backend is invalid. case invalidReqType = 17207 - /** Indicates that the reCAPTCHA SDK is not linked to the app. - */ + /// Indicates that the reCAPTCHA SDK is not linked to the app. case recaptchaSDKNotLinked = 17208 - /** Indicates an error occurred while attempting to access the keychain. - */ + /// Indicates an error occurred while attempting to access the keychain. case keychainError = 17995 - /** Indicates an internal error occurred. - */ + /// Indicates an internal error occurred. case internalError = 17999 - /** Raised when a JWT fails to parse correctly. May be accompanied by an underlying error - describing which step of the JWT parsing process failed. - */ + /// Raised when a JWT fails to parse correctly. May be accompanied by an underlying error + /// describing which step of the JWT parsing process failed. case malformedJWT = 18000 var errorDescription: String { switch self { case .invalidCustomToken: - return kFIRAuthErrorMessageInvalidCustomToken + return kErrorInvalidCustomToken case .customTokenMismatch: - return kFIRAuthErrorMessageCustomTokenMismatch + return kErrorCustomTokenMismatch case .invalidEmail: - return kFIRAuthErrorMessageInvalidEmail + return kErrorInvalidEmail case .invalidCredential: - return kFIRAuthErrorMessageInvalidCredential + return kErrorInvalidCredential case .userDisabled: - return kFIRAuthErrorMessageUserDisabled + return kErrorUserDisabled case .emailAlreadyInUse: - return kFIRAuthErrorMessageEmailAlreadyInUse + return kErrorEmailAlreadyInUse case .wrongPassword: - return kFIRAuthErrorMessageWrongPassword + return kErrorWrongPassword case .tooManyRequests: - return kFIRAuthErrorMessageTooManyRequests + return kErrorTooManyRequests case .accountExistsWithDifferentCredential: - return kFIRAuthErrorMessageAccountExistsWithDifferentCredential + return kErrorAccountExistsWithDifferentCredential case .requiresRecentLogin: - return kFIRAuthErrorMessageRequiresRecentLogin + return kErrorRequiresRecentLogin case .providerAlreadyLinked: - return kFIRAuthErrorMessageProviderAlreadyLinked + return kErrorProviderAlreadyLinked case .noSuchProvider: - return kFIRAuthErrorMessageNoSuchProvider + return kErrorNoSuchProvider case .invalidUserToken: - return kFIRAuthErrorMessageInvalidUserToken + return kErrorInvalidUserToken case .networkError: - return kFIRAuthErrorMessageNetworkError + return kErrorNetworkError case .keychainError: - return kFIRAuthErrorMessageKeychainError + return kErrorKeychainError case .missingClientIdentifier: - return kFIRAuthErrorMessageMissingClientIdentifier + return kErrorMissingClientIdentifier case .userTokenExpired: - return kFIRAuthErrorMessageUserTokenExpired + return kErrorUserTokenExpired case .userNotFound: - return kFIRAuthErrorMessageUserNotFound + return kErrorUserNotFound case .invalidAPIKey: - return kFIRAuthErrorMessageInvalidAPIKey + return kErrorInvalidAPIKey case .credentialAlreadyInUse: - return kFIRAuthErrorMessageCredentialAlreadyInUse + return kErrorCredentialAlreadyInUse case .internalError: - return kFIRAuthErrorMessageInternalError + return kErrorInternalError case .userMismatch: return FIRAuthErrorMessageUserMismatch case .operationNotAllowed: - return kFIRAuthErrorMessageOperationNotAllowed + return kErrorOperationNotAllowed case .weakPassword: - return kFIRAuthErrorMessageWeakPassword + return kErrorWeakPassword case .appNotAuthorized: - return kFIRAuthErrorMessageAppNotAuthorized + return kErrorAppNotAuthorized case .expiredActionCode: - return kFIRAuthErrorMessageExpiredActionCode + return kErrorExpiredActionCode case .invalidActionCode: - return kFIRAuthErrorMessageInvalidActionCode + return kErrorInvalidActionCode case .invalidSender: - return kFIRAuthErrorMessageInvalidSender + return kErrorInvalidSender case .invalidMessagePayload: - return kFIRAuthErrorMessageInvalidMessagePayload + return kErrorInvalidMessagePayload case .invalidRecipientEmail: - return kFIRAuthErrorMessageInvalidRecipientEmail + return kErrorInvalidRecipientEmail case .missingIosBundleID: - return kFIRAuthErrorMessageMissingIosBundleID + return kErrorMissingIosBundleID case .missingAndroidPackageName: - return kFIRAuthErrorMessageMissingAndroidPackageName + return kErrorMissingAndroidPackageName case .unauthorizedDomain: - return kFIRAuthErrorMessageUnauthorizedDomain + return kErrorUnauthorizedDomain case .invalidContinueURI: - return kFIRAuthErrorMessageInvalidContinueURI + return kErrorInvalidContinueURI case .missingContinueURI: - return kFIRAuthErrorMessageMissingContinueURI + return kErrorMissingContinueURI case .missingEmail: - return kFIRAuthErrorMessageMissingEmail + return kErrorMissingEmail case .missingPhoneNumber: - return kFIRAuthErrorMessageMissingPhoneNumber + return kErrorMissingPhoneNumber case .invalidPhoneNumber: - return kFIRAuthErrorMessageInvalidPhoneNumber + return kErrorInvalidPhoneNumber case .missingVerificationCode: - return kFIRAuthErrorMessageMissingVerificationCode + return kErrorMissingVerificationCode case .invalidVerificationCode: - return kFIRAuthErrorMessageInvalidVerificationCode + return kErrorInvalidVerificationCode case .missingVerificationID: - return kFIRAuthErrorMessageMissingVerificationID + return kErrorMissingVerificationID case .invalidVerificationID: - return kFIRAuthErrorMessageInvalidVerificationID + return kErrorInvalidVerificationID case .sessionExpired: - return kFIRAuthErrorMessageSessionExpired + return kErrorSessionExpired case .missingAppCredential: - return kFIRAuthErrorMessageMissingAppCredential + return kErrorMissingAppCredential case .invalidAppCredential: - return kFIRAuthErrorMessageInvalidAppCredential + return kErrorInvalidAppCredential case .quotaExceeded: - return kFIRAuthErrorMessageQuotaExceeded + return kErrorQuotaExceeded case .missingAppToken: - return kFIRAuthErrorMessageMissingAppToken + return kErrorMissingAppToken case .notificationNotForwarded: - return kFIRAuthErrorMessageNotificationNotForwarded + return kErrorNotificationNotForwarded case .appNotVerified: - return kFIRAuthErrorMessageAppNotVerified + return kErrorAppNotVerified case .captchaCheckFailed: - return kFIRAuthErrorMessageCaptchaCheckFailed + return kErrorCaptchaCheckFailed case .webContextAlreadyPresented: - return kFIRAuthErrorMessageWebContextAlreadyPresented + return kErrorWebContextAlreadyPresented case .webContextCancelled: - return kFIRAuthErrorMessageWebContextCancelled + return kErrorWebContextCancelled case .invalidClientID: - return kFIRAuthErrorMessageInvalidClientID + return kErrorInvalidClientID case .appVerificationUserInteractionFailure: - return kFIRAuthErrorMessageAppVerificationUserInteractionFailure + return kErrorAppVerificationUserInteractionFailure case .webNetworkRequestFailed: - return kFIRAuthErrorMessageWebRequestFailed + return kErrorWebRequestFailed case .nullUser: - return kFIRAuthErrorMessageNullUser + return kErrorNullUser case .invalidProviderID: - return kFIRAuthErrorMessageInvalidProviderID + return kErrorInvalidProviderID case .invalidDynamicLinkDomain: - return kFIRAuthErrorMessageInvalidDynamicLinkDomain + return kErrorInvalidDynamicLinkDomain case .webInternalError: - return kFIRAuthErrorMessageWebInternalError + return kErrorWebInternalError case .webSignInUserInteractionFailure: - return kFIRAuthErrorMessageAppVerificationUserInteractionFailure + return kErrorAppVerificationUserInteractionFailure case .malformedJWT: - return kFIRAuthErrorMessageMalformedJWT + return kErrorMalformedJWT case .localPlayerNotAuthenticated: - return kFIRAuthErrorMessageLocalPlayerNotAuthenticated + return kErrorLocalPlayerNotAuthenticated case .gameKitNotLinked: - return kFIRAuthErrorMessageGameKitNotLinked + return kErrorGameKitNotLinked case .secondFactorRequired: - return kFIRAuthErrorMessageSecondFactorRequired + return kErrorSecondFactorRequired case .missingMultiFactorSession: return FIRAuthErrorMessageMissingMultiFactorSession case .missingMultiFactorInfo: @@ -604,35 +495,35 @@ import Foundation case .emailChangeNeedsVerification: return FIRAuthErrorMessageEmailChangeNeedsVerification case .dynamicLinkNotActivated: - return kFIRAuthErrorMessageDynamicLinkNotActivated + return kErrorDynamicLinkNotActivated case .rejectedCredential: - return kFIRAuthErrorMessageRejectedCredential + return kErrorRejectedCredential case .missingOrInvalidNonce: - return kFIRAuthErrorMessageMissingOrInvalidNonce + return kErrorMissingOrInvalidNonce case .tenantIDMismatch: - return kFIRAuthErrorMessageTenantIDMismatch + return kErrorTenantIDMismatch case .unsupportedTenantOperation: - return kFIRAuthErrorMessageUnsupportedTenantOperation + return kErrorUnsupportedTenantOperation case .blockingCloudFunctionError: - return kFIRAuthErrorMessageBlockingCloudFunctionReturnedError + return kErrorBlockingCloudFunctionReturnedError case .recaptchaNotEnabled: - return kFIRAuthErrorMessageRecaptchaNotEnabled + return kErrorRecaptchaNotEnabled case .missingRecaptchaToken: - return kFIRAuthErrorMessageMissingRecaptchaToken + return kErrorMissingRecaptchaToken case .invalidRecaptchaToken: - return kFIRAuthErrorMessageInvalidRecaptchaToken + return kErrorInvalidRecaptchaToken case .invalidRecaptchaAction: - return kFIRAuthErrorMessageInvalidRecaptchaAction + return kErrorInvalidRecaptchaAction case .missingClientType: - return kFIRAuthErrorMessageMissingClientType + return kErrorMissingClientType case .missingRecaptchaVersion: - return kFIRAuthErrorMessageMissingRecaptchaVersion + return kErrorMissingRecaptchaVersion case .invalidRecaptchaVersion: - return kFIRAuthErrorMessageInvalidRecaptchaVersion + return kErrorInvalidRecaptchaVersion case .invalidReqType: - return kFIRAuthErrorMessageInvalidReqType + return kErrorInvalidReqType case .recaptchaSDKNotLinked: - return kFIRAuthErrorMessageRecaptchaSDKNotLinked + return kErrorRecaptchaSDKNotLinked } } @@ -822,501 +713,263 @@ import Foundation // MARK: - Standard Error Messages -/** @var kFIRAuthErrorMessageInvalidCustomToken - @brief Message for @c FIRAuthErrorCodeInvalidCustomToken error code. - */ -private let kFIRAuthErrorMessageInvalidCustomToken = +private let kErrorInvalidCustomToken = "The custom token format is incorrect. Please check the documentation." -/** @var kFIRAuthErrorMessageCustomTokenMismatch - @brief Message for @c FIRAuthErrorCodeCustomTokenMismatch error code. - */ -private let kFIRAuthErrorMessageCustomTokenMismatch = +private let kErrorCustomTokenMismatch = "The custom token corresponds to a different audience." -/** @var kFIRAuthErrorMessageInvalidEmail - @brief Message for @c FIRAuthErrorCodeInvalidEmail error code. - */ -private let kFIRAuthErrorMessageInvalidEmail = "The email address is badly formatted." +private let kErrorInvalidEmail = "The email address is badly formatted." -/** @var kFIRAuthErrorMessageInvalidCredential - @brief Message for @c FIRAuthErrorCodeInvalidCredential error code. - */ -private let kFIRAuthErrorMessageInvalidCredential = +private let kErrorInvalidCredential = "The supplied auth credential is malformed or has expired." -/** @var kFIRAuthErrorMessageUserDisabled - @brief Message for @c FIRAuthErrorCodeUserDisabled error code. - */ -private let kFIRAuthErrorMessageUserDisabled = +private let kErrorUserDisabled = "The user account has been disabled by an administrator." -/** @var kFIRAuthErrorMessageEmailAlreadyInUse - @brief Message for @c FIRAuthErrorCodeEmailAlreadyInUse error code. - */ -private let kFIRAuthErrorMessageEmailAlreadyInUse = +private let kErrorEmailAlreadyInUse = "The email address is already in use by another account." -/** @var kFIRAuthErrorMessageWrongPassword - @brief Message for @c FIRAuthErrorCodeWrongPassword error code. - */ -private let kFIRAuthErrorMessageWrongPassword = +private let kErrorWrongPassword = "The password is invalid or the user does not have a password." -/** @var kFIRAuthErrorMessageTooManyRequests - @brief Message for @c FIRAuthErrorCodeTooManyRequests error code. - */ -private let kFIRAuthErrorMessageTooManyRequests = +private let kErrorTooManyRequests = "We have blocked all requests from this device due to unusual activity. Try again later." -/** @var kFIRAuthErrorMessageAccountExistsWithDifferentCredential - @brief Message for @c FIRAuthErrorCodeAccountExistsWithDifferentCredential error code. - */ -private let kFIRAuthErrorMessageAccountExistsWithDifferentCredential = +private let kErrorAccountExistsWithDifferentCredential = "An account already exists with the same email address but different sign-in credentials. Sign in using a provider associated with this email address." -/** @var kFIRAuthErrorMessageRequiresRecentLogin - @brief Message for @c FIRAuthErrorCodeRequiresRecentLogin error code. - */ -private let kFIRAuthErrorMessageRequiresRecentLogin = +private let kErrorRequiresRecentLogin = "This operation is sensitive and requires recent authentication. Log in again before retrying this request." -/** @var kFIRAuthErrorMessageProviderAlreadyLinked - @brief Message for @c FIRAuthErrorCodeProviderAlreadyExists error code. - */ -private let kFIRAuthErrorMessageProviderAlreadyLinked = +private let kErrorProviderAlreadyLinked = "[ERROR_PROVIDER_ALREADY_LINKED] - User can only be linked to one identity for the given provider." -/** @var kFIRAuthErrorMessageNoSuchProvider - @brief Message for @c FIRAuthErrorCodeNoSuchProvider error code. - */ -private let kFIRAuthErrorMessageNoSuchProvider = +private let kErrorNoSuchProvider = "User was not linked to an account with the given provider." -/** @var kFIRAuthErrorMessageInvalidUserToken - @brief Message for @c FIRAuthErrorCodeInvalidUserToken error code. - */ -private let kFIRAuthErrorMessageInvalidUserToken = +private let kErrorInvalidUserToken = "This user's credential isn't valid for this project. This can happen if the user's token has been tampered with, or if the user doesn’t belong to the project associated with the API key used in your request." -/** @var kFIRAuthErrorMessageNetworkError - @brief Message for @c FIRAuthErrorCodeNetworkError error code. - */ -private let kFIRAuthErrorMessageNetworkError = +private let kErrorNetworkError = "Network error (such as timeout, interrupted connection or unreachable host) has occurred." -/** @var kFIRAuthErrorMessageKeychainError - @brief Message for @c FIRAuthErrorCodeKeychainError error code. - */ -private let kFIRAuthErrorMessageKeychainError = +private let kErrorKeychainError = "An error occurred when accessing the keychain. The NSLocalizedFailureReasonErrorKey field in the NSError.userInfo dictionary will contain more information about the error encountered" -/** @var kFIRAuthErrorMessageUserTokenExpired - @brief Message for @c FIRAuthErrorCodeTokenExpired error code. - */ -private let kFIRAuthErrorMessageUserTokenExpired = +private let kErrorUserTokenExpired = "The user's credential is no longer valid. The user must sign in again." -/** @var kFIRAuthErrorMessageUserNotFound - @brief Message for @c FIRAuthErrorCodeUserNotFound error code. - */ -private let kFIRAuthErrorMessageUserNotFound = +private let kErrorUserNotFound = "There is no user record corresponding to this identifier. The user may have been deleted." -/** @var kFIRAuthErrorMessageInvalidAPIKey - @brief Message for @c FIRAuthErrorCodeInvalidAPIKey error code. - @remarks This error is not thrown by the server. - */ -private let kFIRAuthErrorMessageInvalidAPIKey = "An invalid API Key was supplied in the request." +private let kErrorInvalidAPIKey = "An invalid API Key was supplied in the request." -/** @var kFIRAuthErrorMessageUserMismatch. - @brief Message for @c FIRAuthErrorCodeInvalidAPIKey error code. - */ private let FIRAuthErrorMessageUserMismatch = "The supplied credentials do not correspond to the previously signed in user." -/** @var kFIRAuthErrorMessageCredentialAlreadyInUse - @brief Message for @c FIRAuthErrorCodeCredentialAlreadyInUse error code. - */ -private let kFIRAuthErrorMessageCredentialAlreadyInUse = +private let kErrorCredentialAlreadyInUse = "This credential is already associated with a different user account." -/** @var kFIRAuthErrorMessageOperationNotAllowed - @brief Message for @c FIRAuthErrorCodeOperationNotAllowed error code. - */ -private let kFIRAuthErrorMessageOperationNotAllowed = +private let kErrorOperationNotAllowed = "The given sign-in provider is disabled for this Firebase project. Enable it in the Firebase console, under the sign-in method tab of the Auth section." -/** @var kFIRAuthErrorMessageWeakPassword - @brief Message for @c FIRAuthErrorCodeWeakPassword error code. - */ -private let kFIRAuthErrorMessageWeakPassword = "The password must be 6 characters long or more." +private let kErrorWeakPassword = "The password must be 6 characters long or more." -/** @var kFIRAuthErrorMessageAppNotAuthorized - @brief Message for @c FIRAuthErrorCodeAppNotAuthorized error code. - */ -private let kFIRAuthErrorMessageAppNotAuthorized = +private let kErrorAppNotAuthorized = "This app is not authorized to use Firebase Authentication with the provided API key. Review your key configuration in the Google API console and ensure that it accepts requests from your app's bundle ID." -/** @var kFIRAuthErrorMessageExpiredActionCode - @brief Message for @c FIRAuthErrorCodeExpiredActionCode error code. - */ -private let kFIRAuthErrorMessageExpiredActionCode = "The action code has expired." +private let kErrorExpiredActionCode = "The action code has expired." -/** @var kFIRAuthErrorMessageInvalidActionCode - @brief Message for @c FIRAuthErrorCodeInvalidActionCode error code. - */ -private let kFIRAuthErrorMessageInvalidActionCode = +private let kErrorInvalidActionCode = "The action code is invalid. This can happen if the code is malformed, expired, or has already been used." -/** @var kFIRAuthErrorMessageInvalidMessagePayload - @brief Message for @c FIRAuthErrorCodeInvalidMessagePayload error code. - */ -private let kFIRAuthErrorMessageInvalidMessagePayload = +private let kErrorInvalidMessagePayload = "The action code is invalid. This can happen if the code is malformed, expired, or has already been used." -/** @var kFIRAuthErrorMessageInvalidSender - @brief Message for @c FIRAuthErrorCodeInvalidSender error code. - */ -private let kFIRAuthErrorMessageInvalidSender = +private let kErrorInvalidSender = "The email template corresponding to this action contains invalid characters in its message. Please fix by going to the Auth email templates section in the Firebase Console." -/** @var kFIRAuthErrorMessageInvalidRecipientEmail - @brief Message for @c FIRAuthErrorCodeInvalidRecipient error code. - */ -private let kFIRAuthErrorMessageInvalidRecipientEmail = +private let kErrorInvalidRecipientEmail = "The action code is invalid. This can happen if the code is malformed, expired, or has already been used." -/** @var kFIRAuthErrorMessageMissingIosBundleID - @brief Message for @c FIRAuthErrorCodeMissingIosbundleID error code. - */ -private let kFIRAuthErrorMessageMissingIosBundleID = +private let kErrorMissingIosBundleID = "An iOS Bundle ID must be provided if an App Store ID is provided." -/** @var kFIRAuthErrorMessageMissingAndroidPackageName - @brief Message for @c FIRAuthErrorCodeMissingAndroidPackageName error code. - */ -private let kFIRAuthErrorMessageMissingAndroidPackageName = +private let kErrorMissingAndroidPackageName = "An Android Package Name must be provided if the Android App is required to be installed." -/** @var kFIRAuthErrorMessageUnauthorizedDomain - @brief Message for @c FIRAuthErrorCodeUnauthorizedDomain error code. - */ -private let kFIRAuthErrorMessageUnauthorizedDomain = +private let kErrorUnauthorizedDomain = "The domain of the continue URL is not allowlisted. Please allowlist the domain in the Firebase console." -/** @var kFIRAuthErrorMessageInvalidContinueURI - @brief Message for @c FIRAuthErrorCodeInvalidContinueURI error code. - */ -private let kFIRAuthErrorMessageInvalidContinueURI = +private let kErrorInvalidContinueURI = "The continue URL provided in the request is invalid." -/** @var kFIRAuthErrorMessageMissingEmail - @brief Message for @c FIRAuthErrorCodeMissingEmail error code. - */ -private let kFIRAuthErrorMessageMissingEmail = "An email address must be provided." +private let kErrorMissingEmail = "An email address must be provided." -/** @var kFIRAuthErrorMessageMissingContinueURI - @brief Message for @c FIRAuthErrorCodeMissingContinueURI error code. - */ -private let kFIRAuthErrorMessageMissingContinueURI = +private let kErrorMissingContinueURI = "A continue URL must be provided in the request." -/** @var kFIRAuthErrorMessageMissingPhoneNumber - @brief Message for @c FIRAuthErrorCodeMissingPhoneNumber error code. - */ -private let kFIRAuthErrorMessageMissingPhoneNumber = +private let kErrorMissingPhoneNumber = "To send verification codes, provide a phone number for the recipient." -/** @var kFIRAuthErrorMessageInvalidPhoneNumber - @brief Message for @c FIRAuthErrorCodeInvalidPhoneNumber error code. - */ -private let kFIRAuthErrorMessageInvalidPhoneNumber = +private let kErrorInvalidPhoneNumber = "The format of the phone number provided is incorrect. Please enter the phone number in a format that can be parsed into E.164 format. E.164 phone numbers are written in the format [+][country code][subscriber number including area code]." -/** @var kFIRAuthErrorMessageMissingVerificationCode - @brief Message for @c FIRAuthErrorCodeMissingVerificationCode error code. - */ -private let kFIRAuthErrorMessageMissingVerificationCode = +private let kErrorMissingVerificationCode = "The phone auth credential was created with an empty SMS verification Code." -/** @var kFIRAuthErrorMessageInvalidVerificationCode - @brief Message for @c FIRAuthErrorCodeInvalidVerificationCode error code. - */ -private let kFIRAuthErrorMessageInvalidVerificationCode = +private let kErrorInvalidVerificationCode = "The multifactor verification code used to create the auth credential is invalid. " + "Re-collect the verification code and be sure to use the verification code provided by the user." -/** @var kFIRAuthErrorMessageMissingVerificationID - @brief Message for @c FIRAuthErrorCodeInvalidVerificationID error code. - */ -private let kFIRAuthErrorMessageMissingVerificationID = +private let kErrorMissingVerificationID = "The phone auth credential was created with an empty verification ID." -/** @var kFIRAuthErrorMessageInvalidVerificationID - @brief Message for @c FIRAuthErrorCodeInvalidVerificationID error code. - */ -private let kFIRAuthErrorMessageInvalidVerificationID = +private let kErrorInvalidVerificationID = "The verification ID used to create the phone auth credential is invalid." -/** @var kFIRAuthErrorMessageLocalPlayerNotAuthenticated - @brief Message for @c FIRAuthErrorCodeLocalPlayerNotAuthenticated error code. - */ -private let kFIRAuthErrorMessageLocalPlayerNotAuthenticated = +private let kErrorLocalPlayerNotAuthenticated = "The local player is not authenticated. Please log the local player in to Game Center." -/** @var kFIRAuthErrorMessageGameKitNotLinked - @brief Message for @c kFIRAuthErrorMessageGameKitNotLinked error code. - */ -private let kFIRAuthErrorMessageGameKitNotLinked = +private let kErrorGameKitNotLinked = "The GameKit framework is not linked. Please turn on the Game Center capability." -/** @var kFIRAuthErrorMessageSessionExpired - @brief Message for @c FIRAuthErrorCodeSessionExpired error code. - */ -private let kFIRAuthErrorMessageSessionExpired = +private let kErrorSessionExpired = "The SMS code has expired. Please re-send the verification code to try again." -/** @var kFIRAuthErrorMessageMissingAppCredential - @brief Message for @c FIRAuthErrorCodeMissingAppCredential error code. - */ -private let kFIRAuthErrorMessageMissingAppCredential = +private let kErrorMissingAppCredential = "The phone verification request is missing an APNs Device token. Firebase Auth automatically detects APNs Device Tokens, however, if method swizzling is disabled, the APNs token must be set via the APNSToken property on FIRAuth or by calling setAPNSToken:type on FIRAuth." -/** @var kFIRAuthErrorMessageInvalidAppCredential - @brief Message for @c FIRAuthErrorCodeInvalidAppCredential error code. - */ -private let kFIRAuthErrorMessageInvalidAppCredential = +private let kErrorInvalidAppCredential = "The APNs device token provided is either incorrect or does not match the private certificate uploaded to the Firebase Console." -/** @var kFIRAuthErrorMessageQuotaExceeded - @brief Message for @c FIRAuthErrorCodeQuotaExceeded error code. - */ -private let kFIRAuthErrorMessageQuotaExceeded = "The quota for this operation has been exceeded." +private let kErrorQuotaExceeded = "The quota for this operation has been exceeded." -/** @var kFIRAuthErrorMessageMissingAppToken - @brief Message for @c FIRAuthErrorCodeMissingAppToken error code. - */ -private let kFIRAuthErrorMessageMissingAppToken = +private let kErrorMissingAppToken = "There seems to be a problem with your project's Firebase phone number authentication set-up, please make sure to follow the instructions found at https://firebase.google.com/docs/auth/ios/phone-auth" -/** @var kFIRAuthErrorMessageMissingAppToken - @brief Message for @c FIRAuthErrorCodeMissingAppToken error code. - */ -private let kFIRAuthErrorMessageNotificationNotForwarded = +private let kErrorNotificationNotForwarded = "If app delegate swizzling is disabled, remote notifications received by UIApplicationDelegate need to" + "be forwarded to FirebaseAuth's canHandleNotificaton method." -/** @var kFIRAuthErrorMessageAppNotVerified - @brief Message for @c FIRAuthErrorCodeMissingAppToken error code. - */ -private let kFIRAuthErrorMessageAppNotVerified = +private let kErrorAppNotVerified = "Firebase could not retrieve the silent push notification and therefore could not verify your app. Ensure that you configured your app correctly to receive push notifications." -/** @var kFIRAuthErrorMessageCaptchaCheckFailed - @brief Message for @c FIRAuthErrorCodeCaptchaCheckFailed error code. - */ -private let kFIRAuthErrorMessageCaptchaCheckFailed = +private let kErrorCaptchaCheckFailed = "The reCAPTCHA response token provided is either invalid, expired or already" -/** @var kFIRAuthErrorMessageWebContextAlreadyPresented - @brief Message for @c FIRAuthErrorCodeWebContextAlreadyPresented error code. - */ -private let kFIRAuthErrorMessageWebContextAlreadyPresented = +private let kErrorWebContextAlreadyPresented = "User interaction is still ongoing, another view cannot be presented." -/** @var kFIRAuthErrorMessageWebContextCancelled - @brief Message for @c FIRAuthErrorCodeWebContextCancelled error code. - */ -private let kFIRAuthErrorMessageWebContextCancelled = "The interaction was cancelled by the user." +private let kErrorWebContextCancelled = "The interaction was cancelled by the user." -/** @var kFIRAuthErrorMessageInvalidClientID - @brief Message for @c FIRAuthErrorCodeInvalidClientID error code. - */ -private let kFIRAuthErrorMessageInvalidClientID = +private let kErrorInvalidClientID = "The OAuth client ID provided is either invalid or does not match the specified API key." -/** @var kFIRAuthErrorMessageWebRequestFailed - @brief Message for @c FIRAuthErrorCodeWebRequestFailed error code. - */ -private let kFIRAuthErrorMessageWebRequestFailed = +private let kErrorWebRequestFailed = "A network error (such as timeout, interrupted connection, or unreachable host) has occurred within the web context." -/** @var kFIRAuthErrorMessageWebInternalError - @brief Message for @c FIRAuthErrorCodeWebInternalError error code. - */ -private let kFIRAuthErrorMessageWebInternalError = +private let kErrorWebInternalError = "An internal error has occurred within the SFSafariViewController or WKWebView." -/** @var kFIRAuthErrorMessageAppVerificationUserInteractionFailure - @brief Message for @c FIRAuthErrorCodeInvalidClientID error code. - */ -private let kFIRAuthErrorMessageAppVerificationUserInteractionFailure = +private let kErrorAppVerificationUserInteractionFailure = "The app verification process has failed, print and inspect the error details for more information" -/** @var kFIRAuthErrorMessageNullUser - @brief Message for @c FIRAuthErrorCodeNullUser error code. - */ -private let kFIRAuthErrorMessageNullUser = +private let kErrorNullUser = "A null user object was provided as the argument for an operation which requires a non-null user object." -/** @var kFIRAuthErrorMessageInvalidProviderID - @brief Message for @c FIRAuthErrorCodeInvalidProviderID error code. - */ -private let kFIRAuthErrorMessageInvalidProviderID = +private let kErrorInvalidProviderID = "The provider ID provided for the attempted web operation is invalid." -/** @var kFIRAuthErrorMessageInvalidDynamicLinkDomain - @brief Message for @c kFIRAuthErrorMessageInvalidDynamicLinkDomain error code. - */ -private let kFIRAuthErrorMessageInvalidDynamicLinkDomain = +private let kErrorInvalidDynamicLinkDomain = "The Firebase Dynamic Link domain used is either not configured or is unauthorized for the current project." -/** @var kFIRAuthErrorMessageInternalError - @brief Message for @c FIRAuthErrorCodeInternalError error code. - */ -private let kFIRAuthErrorMessageInternalError = +private let kErrorInternalError = "An internal error has occurred, print and inspect the error details for more information." -/** @var kFIRAuthErrorMessageMalformedJWT - @brief Error message constant describing @c FIRAuthErrorCodeMalformedJWT errors. - */ -private let kFIRAuthErrorMessageMalformedJWT = +private let kErrorMalformedJWT = "Failed to parse JWT. Check the userInfo dictionary for the full token." -/** @var kFIRAuthErrorMessageSecondFactorRequired - @brief Message for @c kFIRAuthErrorMessageSecondFactorRequired error code. - */ -private let kFIRAuthErrorMessageSecondFactorRequired = +private let kErrorSecondFactorRequired = "Please complete a second factor challenge to finish signing into this account." -/** @var kFIRAuthErrorMessageSecondFactorRequired - @brief Message for @c kFIRAuthErrorMessageSecondFactorRequired error code. - */ private let FIRAuthErrorMessageMissingMultiFactorSession = "The request is missing proof of first factor successful sign-in." -/** @var kFIRAuthErrorMessageSecondFactorRequired - @brief Message for @c kFIRAuthErrorMessageSecondFactorRequired error code. - */ private let FIRAuthErrorMessageMissingMultiFactorInfo = "No second factor identifier is provided." -/** @var kFIRAuthErrorMessageSecondFactorRequired - @brief Message for @c kFIRAuthErrorMessageSecondFactorRequired error code. - */ private let FIRAuthErrorMessageInvalidMultiFactorSession = "The request does not contain a valid proof of first factor successful sign-in." -/** @var kFIRAuthErrorMessageSecondFactorRequired - @brief Message for @c kFIRAuthErrorMessageSecondFactorRequired error code. - */ private let FIRAuthErrorMessageMultiFactorInfoNotFound = "The user does not have a second factor matching the identifier provided." -/** @var kFIRAuthErrorMessageSecondFactorRequired - @brief Message for @c kFIRAuthErrorMessageSecondFactorRequired error code. - */ private let FIRAuthErrorMessageAdminRestrictedOperation = "This operation is restricted to administrators only." -/** @var kFIRAuthErrorMessageSecondFactorRequired - @brief Message for @c kFIRAuthErrorMessageSecondFactorRequired error code. - */ private let FIRAuthErrorMessageUnverifiedEmail = "The operation requires a verified email." -/** @var kFIRAuthErrorMessageSecondFactorRequired - @brief Message for @c kFIRAuthErrorMessageSecondFactorRequired error code. - */ private let FIRAuthErrorMessageSecondFactorAlreadyEnrolled = "The second factor is already enrolled on this account." -/** @var kFIRAuthErrorMessageSecondFactorRequired - @brief Message for @c kFIRAuthErrorMessageSecondFactorRequired error code. - */ private let FIRAuthErrorMessageMaximumSecondFactorCountExceeded = "The maximum allowed number of second factors on a user has been exceeded." -/** @var kFIRAuthErrorMessageSecondFactorRequired - @brief Message for @c kFIRAuthErrorMessageSecondFactorRequired error code. - */ private let FIRAuthErrorMessageUnsupportedFirstFactor = "Enrolling a second factor or signing in with a multi-factor account requires sign-in with a supported first factor." -/** @var kFIRAuthErrorMessageSecondFactorRequired - @brief Message for @c kFIRAuthErrorMessageSecondFactorRequired error code. - */ private let FIRAuthErrorMessageEmailChangeNeedsVerification = "Multi-factor users must always have a verified email." -/** @var kFIRAuthErrorMessageDynamicLinkNotActivated - @brief Error message constant describing @c FIRAuthErrorCodeDynamicLinkNotActivated errors. - */ -private let kFIRAuthErrorMessageDynamicLinkNotActivated = +private let kErrorDynamicLinkNotActivated = "Please activate Dynamic Links in the Firebase Console and agree to the terms and conditions." -/** @var kFIRAuthErrorMessageRejectedCredential - @brief Error message constant describing @c FIRAuthErrorCodeRejectedCredential errors. - */ -private let kFIRAuthErrorMessageRejectedCredential = +private let kErrorRejectedCredential = "The request contains malformed or mismatching credentials." -/** @var kFIRAuthErrorMessageMissingClientIdentifier - @brief Error message constant describing @c FIRAuthErrorCodeMissingClientIdentifier errors. - */ -private let kFIRAuthErrorMessageMissingClientIdentifier = +private let kErrorMissingClientIdentifier = "The request does not contain a client identifier." -/** @var kFIRAuthErrorMessageMissingOrInvalidNonce - @brief Error message constant describing @c FIRAuthErrorCodeMissingOrInvalidNonce errors. - */ -private let kFIRAuthErrorMessageMissingOrInvalidNonce = +private let kErrorMissingOrInvalidNonce = "The request contains malformed or mismatched credentials." -/** @var kFIRAuthErrorMessageTenantIDMismatch. - @brief Message for @c FIRAuthErrorCodeTenantIDMismatch error code. - */ -private let kFIRAuthErrorMessageTenantIDMismatch = +private let kErrorTenantIDMismatch = "The provided user's tenant ID does not match the Auth instance's tenant ID." -/** @var kFIRAuthErrorMessageUnsupportedTenantOperation - @brief Message for @c FIRAuthErrorCodeUnsupportedTenantOperation error code. - */ -private let kFIRAuthErrorMessageUnsupportedTenantOperation = +private let kErrorUnsupportedTenantOperation = "This operation is not supported in a multi-tenant context." -/** @var kFIRAuthErrorMessageBlockingCloudFunctionReturnedError - @brief Message for @c FIRAuthErrorCodeBlockingCloudFunctionError error code. - */ -private let kFIRAuthErrorMessageBlockingCloudFunctionReturnedError = +private let kErrorBlockingCloudFunctionReturnedError = "Blocking cloud function returned an error." -private let kFIRAuthErrorMessageRecaptchaNotEnabled = +private let kErrorRecaptchaNotEnabled = "reCAPTCHA Enterprise is not enabled for this project." -private let kFIRAuthErrorMessageMissingRecaptchaToken = +private let kErrorMissingRecaptchaToken = "The backend request is missing the reCAPTCHA verification token." -private let kFIRAuthErrorMessageInvalidRecaptchaToken = +private let kErrorInvalidRecaptchaToken = "The reCAPTCHA verification token is invalid or has expired." -private let kFIRAuthErrorMessageInvalidRecaptchaAction = +private let kErrorInvalidRecaptchaAction = "The reCAPTCHA verification failed due to an invalid action." -private let kFIRAuthErrorMessageMissingClientType = +private let kErrorMissingClientType = "The request is missing a client type or the client type is invalid." -private let kFIRAuthErrorMessageMissingRecaptchaVersion = +private let kErrorMissingRecaptchaVersion = "The request is missing the reCAPTCHA version parameter." -private let kFIRAuthErrorMessageInvalidRecaptchaVersion = +private let kErrorInvalidRecaptchaVersion = "The request specifies an invalid version of reCAPTCHA." -private let kFIRAuthErrorMessageInvalidReqType = +private let kErrorInvalidReqType = "The request is not supported or is invalid." // TODO(chuanr, ObjC): point the link to GCIP doc once available. -private let kFIRAuthErrorMessageRecaptchaSDKNotLinked = +private let kErrorRecaptchaSDKNotLinked = "The reCAPTCHA SDK is not linked to your app. See " + "https://cloud.google.com/recaptcha-enterprise/docs/instrument-ios-apps" diff --git a/FirebaseAuth/Sources/Swift/Utilities/AuthInternalErrors.swift b/FirebaseAuth/Sources/Swift/Utilities/AuthInternalErrors.swift index 19a3b188c0a..51b1c51ba41 100644 --- a/FirebaseAuth/Sources/Swift/Utilities/AuthInternalErrors.swift +++ b/FirebaseAuth/Sources/Swift/Utilities/AuthInternalErrors.swift @@ -14,87 +14,83 @@ import Foundation -/** @var FIRAuthPublicErrorCodeFlag - @brief Bitmask value indicating the error represents a public error code when this bit is - zeroed. Error codes which don't contain this flag will be wrapped in an @c NSError whose - code is @c FIRAuthErrorCodeInternalError. - */ +/// Bitmask value indicating the error represents a public error code when this bit is +/// zeroed. Error codes which don't contain this flag will be wrapped in an `NSError` whose +/// code is `AuthErrorCodeInternalError`. let FIRAuthPublicErrorCodeFlag: Int = 1 << 20 -/** @var FIRAuthInternalErrorCode - @brief Error codes used internally by Firebase Auth. - @remarks All errors are generated using an internal error code. These errors are automatically - converted to the appropriate public version of the @c NSError by the methods in - @c FIRAuthErrorUtils - */ - enum SharedErrorCode { case `public`(AuthErrorCode) case `internal`(AuthInternalErrorCode) } +/// Error codes used internally by Firebase Auth. +/// +/// All errors are generated using an internal error code. These errors are automatically +/// converted to the appropriate public version of the `NSError` by the methods in AuthErrorUtils. enum AuthInternalErrorCode: Int { + /// Indicates a network error occurred (such as a timeout, interrupted connection, or + /// unreachable host.) + /// + /// These types of errors are often recoverable with a retry. + /// + /// See the `NSUnderlyingError` value in the `NSError.userInfo` dictionary for details about + /// the network error which occurred. case networkError = 17020 - /** @var FIRAuthInternalErrorCodeRPCRequestEncodingError - @brief Indicates an error encoding the RPC request. - @remarks This is typically due to some sort of unexpected input value. - - See the @c NSUnderlyingError value in the @c NSError.userInfo dictionary for details. - */ + /// Indicates an error encoding the RPC request. + /// + /// This is typically due to some sort of unexpected input value. + /// + /// See the `NSUnderlyingError` value in the `NSError.userInfo` dictionary for details. case RPCRequestEncodingError = 1 - /** @var FIRAuthInternalErrorCodeJSONSerializationError - @brief Indicates an error serializing an RPC request. - @remarks This is typically due to some sort of unexpected input value. - - If an @c NSJSONSerialization.isValidJSONObject: check fails, the error will contain no - @c NSUnderlyingError key in the @c NSError.userInfo dictionary. If an error was - encountered calling @c NSJSONSerialization.dataWithJSONObject:options:error:, the - resulting error will be associated with the @c NSUnderlyingError key in the - @c NSError.userInfo dictionary. - */ + /// Indicates an error serializing an RPC request. + /// + /// This is typically due to some sort of unexpected input value. + /// + /// If an `JSONSerialization.isValidJSONObject` check fails, the error will contain no + /// `NSUnderlyingError` key in the `NSError.userInfo` dictionary. If an error was + /// encountered calling `NSJSONSerialization.dataWithJSONObject`, the + /// resulting error will be associated with the `NSUnderlyingError` key in the + /// `NSError.userInfo` dictionary. case JSONSerializationError = 2 - /** @var FIRAuthInternalErrorCodeUnexpectedErrorResponse - @brief Indicates an HTTP error occurred and the data returned either couldn't be deserialized - or couldn't be decoded. - @remarks See the @c NSUnderlyingError value in the @c NSError.userInfo dictionary for details - about the HTTP error which occurred. - - If the response could be deserialized as JSON then the @c NSError.userInfo dictionary will - contain a value for the key @c FIRAuthErrorUserInfoDeserializedResponseKey which is the - deserialized response value. - - If the response could not be deserialized as JSON then the @c NSError.userInfo dictionary - will contain values for the @c NSUnderlyingErrorKey and @c FIRAuthErrorUserInfoDataKey - keys. - */ + /// Indicates an HTTP error occurred and the data returned either couldn't be deserialized + /// or couldn't be decoded. + /// + /// See the `NSUnderlyingError` value in the `NSError.userInfo` dictionary for details + /// about the HTTP error which occurred. + /// + /// If the response could be deserialized as JSON then the `NSError.userInfo` dictionary will + /// contain a value for the key `AuthErrorUserInfoDeserializedResponseKey` which is the + /// deserialized response value. + /// + /// If the response could not be deserialized as JSON then the `NSError.userInfo` dictionary + /// will contain values for the `NSUnderlyingErrorKey` and `AuthErrorUserInfoDataKey` keys. case unexpectedErrorResponse = 3 - /** @var FIRAuthInternalErrorCodeUnexpectedResponse - @brief Indicates the HTTP response indicated the request was a successes, but the response - contains something other than a JSON-encoded dictionary, or the data type of the response - indicated it is different from the type of response we expected. - @remarks See the @c NSUnderlyingError value in the @c NSError.userInfo dictionary. - If this key is present in the dictionary, it may contain an error from - @c NSJSONSerialization error (indicating the response received was of the wrong data - type). - - See the @c FIRAuthErrorUserInfoDeserializedResponseKey value in the @c NSError.userInfo - dictionary. If the response could be deserialized, it's deserialized representation will - be associated with this key. If the @c NSUnderlyingError value in the @c NSError.userInfo - dictionary is @c nil, this indicates the JSON didn't represent a dictionary. - */ + /// Indicates the HTTP response indicated the request was a successes, but the response + /// contains something other than a JSON-encoded dictionary, or the data type of the response + /// indicated it is different from the type of response we expected. + /// + /// See the `NSUnderlyingError` value in the `NSError.userInfo` dictionary. + /// If this key is present in the dictionary, it may contain an error from + /// `NSJSONSerialization` error (indicating the response received was of the wrong data type). + /// + /// See the `AuthErrorUserInfoDeserializedResponseKey` value in the `NSError.userInfo` + /// dictionary. If the response could be deserialized, it's deserialized representation will + /// be associated with this key. If the @c NSUnderlyingError value in the @c NSError.userInfo + /// dictionary is @c nil, this indicates the JSON didn't represent a dictionary. case unexpectedResponse = 4 - /** @var FIRAuthInternalErrorCodeRPCResponseDecodingError - @brief Indicates an error decoding the RPC response. - This is typically due to some sort of unexpected response value from the server. - @remarks See the @c NSUnderlyingError value in the @c NSError.userInfo dictionary for details. - - See the @c FIRErrorUserInfoDecodedResponseKey value in the @c NSError.userInfo dictionary. - The deserialized representation of the response will be associated with this key. - */ + /// Indicates an error decoding the RPC response. + /// + /// This is typically due to some sort of unexpected response value from the server. + /// + /// See the `NSUnderlyingError` value in the `NSError.userInfo` dictionary for details. + /// + /// See the `userInfoDeserializedResponseKey` value in the `NSError.userInfo` dictionary. + /// The deserialized representation of the response will be associated with this key. case RPCResponseDecodingError = 5 } diff --git a/FirebaseAuth/Sources/Swift/Utilities/AuthUIDelegate.swift b/FirebaseAuth/Sources/Swift/Utilities/AuthUIDelegate.swift index 856b69a949b..1b6b573995b 100644 --- a/FirebaseAuth/Sources/Swift/Utilities/AuthUIDelegate.swift +++ b/FirebaseAuth/Sources/Swift/Utilities/AuthUIDelegate.swift @@ -17,31 +17,26 @@ import Foundation import UIKit - /** @protocol AuthUIDelegate - @brief A protocol to handle user interface interactions for Firebase Auth. - This protocol is available on iOS, macOS Catalyst, and tvOS only. - */ + /// A protocol to handle user interface interactions for Firebase Auth. + /// + /// This protocol is available on iOS, macOS Catalyst, and tvOS only. @objc(FIRAuthUIDelegate) public protocol AuthUIDelegate: NSObjectProtocol { - /** @fn presentViewController:animated:completion: - @brief If implemented, this method will be invoked when Firebase Auth needs to display a view - controller. - @param viewControllerToPresent The view controller to be presented. - @param flag Decides whether the view controller presentation should be animated or not. - @param completion The block to execute after the presentation finishes. This block has no return - value and takes no parameters. - */ + /// If implemented, this method will be invoked when Firebase Auth needs to display a view + /// controller. + /// - Parameter viewControllerToPresent: The view controller to be presented. + /// - Parameter flag: Decides whether the view controller presentation should be animated. + /// - Parameter completion: The block to execute after the presentation finishes. + /// This block has no return value and takes no parameters. @objc(presentViewController:animated:completion:) func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) - /** @fn dismissViewControllerAnimated:completion: - @brief If implemented, this method will be invoked when Firebase Auth needs to display a view - controller. - @param flag Decides whether removing the view controller should be animated or not. - @param completion The block to execute after the presentation finishes. This block has no return - value and takes no parameters. - */ + /// If implemented, this method will be invoked when Firebase Auth needs to display a view + /// controller. + /// - Parameter flag: Decides whether removing the view controller should be animated or not. + /// - Parameter completion: The block to execute after the presentation finishes. + /// This block has no return value and takes no parameters. @objc(dismissViewControllerAnimated:completion:) func dismiss(animated flag: Bool, completion: (() -> Void)?) } diff --git a/FirebaseAuth/Sources/Swift/Utilities/AuthURLPresenter.swift b/FirebaseAuth/Sources/Swift/Utilities/AuthURLPresenter.swift index 784d097324a..20de31ca968 100644 --- a/FirebaseAuth/Sources/Swift/Utilities/AuthURLPresenter.swift +++ b/FirebaseAuth/Sources/Swift/Utilities/AuthURLPresenter.swift @@ -19,19 +19,16 @@ import UIKit import WebKit - /** @class AuthURLPresenter - @brief A Class responsible for presenting URL via SFSafariViewController or WKWebView. - */ + /// A Class responsible for presenting URL via SFSafariViewController or WKWebView. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) class AuthURLPresenter: NSObject, SFSafariViewControllerDelegate, AuthWebViewControllerDelegate { - /** @fn - @brief Presents an URL to interact with user. - @param url The URL to present. - @param uiDelegate The UI delegate to present view controller. - @param completion A block to be called either synchronously if the presentation fails to start, - or asynchronously in future on an unspecified thread once the presentation finishes. - */ + /// Presents an URL to interact with user. + /// - Parameter url: The URL to present. + /// - Parameter uiDelegate: The UI delegate to present view controller. + /// - Parameter completion: A block to be called either synchronously if the presentation fails + /// to start, or asynchronously in future on an unspecified thread once the presentation + /// finishes. func present(_ url: URL, uiDelegate: AuthUIDelegate?, callbackMatcher: @escaping (URL?) -> Bool, @@ -74,11 +71,9 @@ } } - /** @fn canHandleURL: - @brief Determines if a URL was produced by the currently presented URL. - @param url The URL to handle. - @return Whether the URL could be handled or not. - */ + /// Determines if a URL was produced by the currently presented URL. + /// - Parameter url: The URL to handle. + /// - Returns: Whether the URL could be handled or not. func canHandle(url: URL) -> Bool { if isPresenting, let callbackMatcher = callbackMatcher, @@ -119,44 +114,33 @@ } } - /** @var_isPresenting - @brief Whether or not some web-based content is being presented. - Accesses to this property are serialized on the global Auth work queue - and thus this variable should not be read or written outside of the work queue. - */ + /// Whether or not some web-based content is being presented. + /// + /// Accesses to this property are serialized on the global Auth work queue + /// and thus this variable should not be read or written outside of the work queue. private var isPresenting: Bool = false - /** @var callbackMatcher - @brief The callback URL matcher for the current presentation, if one is active. - */ + /// The callback URL matcher for the current presentation, if one is active. private var callbackMatcher: ((URL) -> Bool)? - /** @var safariViewController - @brief The SFSafariViewController used for the current presentation, if any. - */ + /// The SFSafariViewController used for the current presentation, if any. private var safariViewController: SFSafariViewController? - /** @var webViewController - @brief The FIRAuthWebViewController used for the current presentation, if any. - */ + /// The `AuthWebViewController` used for the current presentation, if any. private var webViewController: AuthWebViewController? - /** @var uiDelegate - @brief The UIDelegate used to present the SFSafariViewController. - */ + /// The UIDelegate used to present the SFSafariViewController. var uiDelegate: AuthUIDelegate? - /** @var completion - @brief The completion handler for the current presentation, if one is active. - Accesses to this variable are serialized on the global Auth work queue - and thus this variable should not be read or written outside of the work queue. - @remarks This variable is also used as a flag to indicate a presentation is active. - */ + /// The completion handler for the current presentation, if one is active. + /// + /// Accesses to this variable are serialized on the global Auth work queue + /// and thus this variable should not be read or written outside of the work queue. + /// + /// This variable is also used as a flag to indicate a presentation is active. var completion: ((URL?, Error?) -> Void)? - /** @var fakeUIDelegate - @brief Test-only option to validate the calls to the uiDelegate. - */ + /// Test-only option to validate the calls to the uiDelegate. var fakeUIDelegate: AuthUIDelegate? // MARK: Private methods diff --git a/FirebaseAuth/Sources/Swift/Utilities/AuthWebUtils.swift b/FirebaseAuth/Sources/Swift/Utilities/AuthWebUtils.swift index 7814fc74cd7..47456070935 100644 --- a/FirebaseAuth/Sources/Swift/Utilities/AuthWebUtils.swift +++ b/FirebaseAuth/Sources/Swift/Utilities/AuthWebUtils.swift @@ -77,10 +77,8 @@ class AuthWebUtils: NSObject { return false } - /** @fn extractDomain:urlString - @brief Strips url of scheme and path string to extract domain name - @param urlString URL string for domain - */ + /// Strips url of scheme and path string to extract domain name + /// - Parameter urlString: URL string for domain static func extractDomain(urlString: String) -> String? { var domain = urlString diff --git a/FirebaseAuth/Sources/Swift/Utilities/AuthWebView.swift b/FirebaseAuth/Sources/Swift/Utilities/AuthWebView.swift index 474f020a152..8950052112e 100644 --- a/FirebaseAuth/Sources/Swift/Utilities/AuthWebView.swift +++ b/FirebaseAuth/Sources/Swift/Utilities/AuthWebView.swift @@ -17,9 +17,7 @@ import UIKit import WebKit - /** @class AuthWebView - @brief A class responsible for creating a WKWebView for use within Firebase Auth. - */ + /// A class responsible for creating a WKWebView for use within Firebase Auth. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) class AuthWebView: UIView { lazy var webView: WKWebView = createWebView() diff --git a/FirebaseAuth/Sources/Swift/Utilities/AuthWebViewController.swift b/FirebaseAuth/Sources/Swift/Utilities/AuthWebViewController.swift index 6fb56a4060d..f476c0e06f5 100644 --- a/FirebaseAuth/Sources/Swift/Utilities/AuthWebViewController.swift +++ b/FirebaseAuth/Sources/Swift/Utilities/AuthWebViewController.swift @@ -18,38 +18,29 @@ import UIKit import WebKit - /** @protocol AuthWebViewControllerDelegate - @brief Defines a delegate for AuthWebViewController - */ + /// Defines a delegate for AuthWebViewController @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) protocol AuthWebViewControllerDelegate: AnyObject { - /** @fn webViewControllerDidCancel: - @brief Notifies the delegate that the web view controller is being cancelled by the user. - @param webViewController The web view controller in question. - */ + /// Notifies the delegate that the web view controller is being cancelled by the user. + /// - Parameter webViewController: The web view controller in question. func webViewControllerDidCancel(_ controller: AuthWebViewController) - /** @fn webViewController:canHandleURL: - @brief Determines if a URL should be handled by the delegate. - @param URL The URL to handle. - @return Whether the URL could be handled or not. - */ + /// Determines if a URL should be handled by the delegate. + /// - Parameter url: The URL to handle. + /// - Returns: Whether the URL could be handled or not. func webViewController(_ controller: AuthWebViewController, canHandle url: URL) -> Bool - /** @fn webViewController:didFailWithError: - @brief Notifies the delegate that the web view controller failed to load a page. - @param webViewController The web view controller in question. - @param error The error that has occurred. - */ + /// Notifies the delegate that the web view controller failed to load a page. + /// - Parameter webViewController: The web view controller in question. + /// - Parameter error: The error that has occurred. func webViewController(_ controller: AuthWebViewController, didFailWithError error: Error) - /** @fn presentURL:UIDelegate:callbackMatcher:completion: - @brief Presents an URL to interact with user. - @param url The URL to present. - @param uiDelegate The UI delegate to present view controller. - @param completion A block to be called either synchronously if the presentation fails to start, - or asynchronously in future on an unspecified thread once the presentation finishes. - */ + /// Presents an URL to interact with user. + /// - Parameter url: The URL to present. + /// - Parameter uiDelegate: The UI delegate to present view controller. + /// - Parameter completion: A block to be called either synchronously if the presentation fails + /// to start, or asynchronously in future on an unspecified thread once the presentation + /// finishes. func present(_ url: URL, uiDelegate: AuthUIDelegate?, callbackMatcher: @escaping (URL?) -> Bool, diff --git a/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/Assets.xcassets/Auth Provider Icons/Contents.json b/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/Assets.xcassets/Auth Provider Icons/Contents.json index da4a164c918..73c00596a7f 100644 --- a/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/Assets.xcassets/Auth Provider Icons/Contents.json +++ b/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/Assets.xcassets/Auth Provider Icons/Contents.json @@ -1,6 +1,6 @@ { "info" : { - "version" : 1, - "author" : "xcode" + "author" : "xcode", + "version" : 1 } -} \ No newline at end of file +} diff --git a/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/Assets.xcassets/Auth Provider Icons/Game Center.imageset/Contents.json b/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/Assets.xcassets/Auth Provider Icons/Game Center.imageset/Contents.json index ca69c269602..8cd53c205c6 100644 --- a/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/Assets.xcassets/Auth Provider Icons/Game Center.imageset/Contents.json +++ b/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/Assets.xcassets/Auth Provider Icons/Game Center.imageset/Contents.json @@ -5,7 +5,7 @@ "scale" : "1x" }, { - "filename" : "game-center icon.png", + "filename" : "gamecontroller.png", "idiom" : "universal", "scale" : "2x" }, diff --git a/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/Assets.xcassets/Auth Provider Icons/Game Center.imageset/game-center icon.png b/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/Assets.xcassets/Auth Provider Icons/Game Center.imageset/game-center icon.png deleted file mode 100644 index 4c96fc2e25c..00000000000 Binary files a/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/Assets.xcassets/Auth Provider Icons/Game Center.imageset/game-center icon.png and /dev/null differ diff --git a/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/Assets.xcassets/Auth Provider Icons/Game Center.imageset/gamecontroller.png b/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/Assets.xcassets/Auth Provider Icons/Game Center.imageset/gamecontroller.png new file mode 100644 index 00000000000..14b182a9c45 Binary files /dev/null and b/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/Assets.xcassets/Auth Provider Icons/Game Center.imageset/gamecontroller.png differ diff --git a/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/SwiftApplication.plist b/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/SwiftApplication.plist index 633b311e4b7..50378fe58b0 100644 --- a/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/SwiftApplication.plist +++ b/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/SwiftApplication.plist @@ -24,6 +24,7 @@ CFBundleURLName CFBundleURLSchemes + CFBundleVersion diff --git a/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/ViewControllers/AccountLinkingViewController.swift b/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/ViewControllers/AccountLinkingViewController.swift index d4b59e60c82..34bf50f0a44 100644 --- a/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/ViewControllers/AccountLinkingViewController.swift +++ b/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/ViewControllers/AccountLinkingViewController.swift @@ -241,7 +241,6 @@ class AccountLinkingViewController: UIViewController, DataSourceProviderDelegate // Step 1: Ensure Game Center Authentication guard GKLocalPlayer.local.isAuthenticated else { print("Error: Player not authenticated with Game Center.") - // TODO: Handle the 'not authenticated' scenario (e.g., prompt the user) return } @@ -249,13 +248,11 @@ class AccountLinkingViewController: UIViewController, DataSourceProviderDelegate GameCenterAuthProvider.getCredential { credential, error in if let error = error { print("Error getting Game Center credential: \(error.localizedDescription)") - // TODO: Handle the credential error return } guard let credential = credential else { print("Error: Missing Game Center credential") - // TODO: Handle the missing credential case return } @@ -263,12 +260,8 @@ class AccountLinkingViewController: UIViewController, DataSourceProviderDelegate Auth.auth().currentUser?.link(with: credential) { authResult, error in if let error = error { print("Error linking Game Center to Firebase: \(error.localizedDescription)") - // TODO: Handle the linking error return } - - // Linking successful - print("Successfully linked Game Center to Firebase") } } } diff --git a/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/ViewControllers/AuthViewController.swift b/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/ViewControllers/AuthViewController.swift index 8afd72d1976..f9ef9679805 100644 --- a/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/ViewControllers/AuthViewController.swift +++ b/FirebaseAuth/Tests/SampleSwift/AuthenticationExample/ViewControllers/AuthViewController.swift @@ -518,206 +518,6 @@ class AuthViewController: UIViewController, DataSourceProviderDelegate { } } - private func toggleActionCodeRequestType(at indexPath: IndexPath) { - switch actionCodeRequestType { - case .inApp: - actionCodeRequestType = .continue - case .continue: - actionCodeRequestType = .email - case .email: - actionCodeRequestType = .inApp - } - dataSourceProvider.updateItem( - at: indexPath, - item: Item(title: AuthMenu.actionType.name, detailTitle: actionCodeRequestType.name) - ) - tableView.reloadData() - } - - private func changeActionCodeContinueURL(at indexPath: IndexPath) { - showTextInputPrompt(with: "Continue URL:", completion: { newContinueURL in - self.actionCodeContinueURL = URL(string: newContinueURL) - print("Successfully set Continue URL to: \(newContinueURL)") - self.dataSourceProvider.updateItem( - at: indexPath, - item: Item( - title: AuthMenu.continueURL.name, - detailTitle: self.actionCodeContinueURL?.absoluteString, - isEditable: true - ) - ) - self.tableView.reloadData() - }) - } - - private func requestVerifyEmail() { - showSpinner() - let completionHandler: (Error?) -> Void = { [weak self] error in - guard let self = self else { return } - self.hideSpinner() - - if let error = error { - let errorMessage = "Error sending verification email: \(error.localizedDescription)" - showAlert(for: errorMessage) - print(errorMessage) - } else { - let successMessage = "Verification email sent successfully!" - showAlert(for: successMessage) - print(successMessage) - } - } - if actionCodeRequestType == .email { - AppManager.shared.auth().currentUser?.sendEmailVerification(completion: completionHandler) - } else { - if actionCodeContinueURL == nil { - print("Error: Action code continue URL is nil.") - return - } - AppManager.shared.auth().currentUser?.sendEmailVerification( - with: actionCodeSettings(), - completion: completionHandler - ) - } - } - - func requestPasswordReset() { - showTextInputPrompt(with: "Email:", completion: { email in - print("Sending password reset link to: \(email)") - self.showSpinner() - let completionHandler: (Error?) -> Void = { [weak self] error in - guard let self = self else { return } - self.hideSpinner() - if let error = error { - print("Request password reset failed: \(error)") - showAlert(for: error.localizedDescription) - return - } - print("Request password reset succeeded.") - showAlert(for: "Sent!") - } - if self.actionCodeRequestType == .email { - AppManager.shared.auth().sendPasswordReset(withEmail: email, completion: completionHandler) - } else { - guard let actionCodeContinueURL = self.actionCodeContinueURL else { - print("Error: Action code continue URL is nil.") - return - } - AppManager.shared.auth().sendPasswordReset( - withEmail: email, - actionCodeSettings: self.actionCodeSettings(), - completion: completionHandler - ) - } - }) - } - - private func resetPassword() { - showSpinner() - let completionHandler: (Error?) -> Void = { [weak self] error in - guard let self = self else { return } - self.hideSpinner() - if let error = error { - print("Password reset failed \(error)") - showAlert(for: error.localizedDescription) - return - } - print("Password reset succeeded") - showAlert(for: "Password reset succeeded!") - } - showTextInputPrompt(with: "OOB Code:") { - code in - self.showTextInputPrompt(with: "New Password") { - password in - AppManager.shared.auth().confirmPasswordReset( - withCode: code, - newPassword: password, - completion: completionHandler - ) - } - } - } - - private func nameForActionCodeOperation(_ operation: ActionCodeOperation) -> String { - switch operation { - case .verifyEmail: - return "Verify Email" - case .recoverEmail: - return "Recover Email" - case .passwordReset: - return "Password Reset" - case .emailLink: - return "Email Sign-In Link" - case .verifyAndChangeEmail: - return "Verify Before Change Email" - case .revertSecondFactorAddition: - return "Revert Second Factor Addition" - case .unknown: - return "Unknown action" - } - } - - private func checkActionCode() { - showSpinner() - let completionHandler: (ActionCodeInfo?, Error?) -> Void = { [weak self] info, error in - guard let self = self else { return } - self.hideSpinner() - if let error = error { - print("Check action code failed: \(error)") - showAlert(for: error.localizedDescription) - return - } - guard let info = info else { return } - print("Check action code succeeded") - let email = info.email - let previousEmail = info.previousEmail - let message = previousEmail != nil ? "\(previousEmail!) -> \(email)" : email - let operation = self.nameForActionCodeOperation(info.operation) - showAlert(for: operation) - } - showTextInputPrompt(with: "OOB Code:") { - oobCode in - AppManager.shared.auth().checkActionCode(oobCode, completion: completionHandler) - } - } - - private func applyActionCode() { - showSpinner() - let completionHandler: (Error?) -> Void = { [weak self] error in - guard let self = self else { return } - self.hideSpinner() - if let error = error { - print("Apply action code failed \(error)") - showAlert(for: error.localizedDescription) - return - } - print("Apply action code succeeded") - showAlert(for: "Action code was properly applied") - } - showTextInputPrompt(with: "OOB Code: ") { - oobCode in - AppManager.shared.auth().applyActionCode(oobCode, completion: completionHandler) - } - } - - private func verifyPasswordResetCode() { - showSpinner() - let completionHandler: (String?, Error?) -> Void = { [weak self] email, error in - guard let self = self else { return } - self.hideSpinner() - if let error = error { - print("Verify password reset code failed \(error)") - showAlert(for: error.localizedDescription) - return - } - print("Verify password resest code succeeded.") - showAlert(for: "Code verified for email: \(email)") - } - showTextInputPrompt(with: "OOB Code: ") { - oobCode in - AppManager.shared.auth().verifyPasswordResetCode(oobCode, completion: completionHandler) - } - } - // MARK: - Private Helpers private func showTextInputPrompt(with message: String, completion: ((String) -> Void)? = nil) { diff --git a/FirebaseAuth/Tests/SampleSwift/AuthenticationExampleUITests/AuthenticationExampleUITests.swift b/FirebaseAuth/Tests/SampleSwift/AuthenticationExampleUITests/AuthenticationExampleUITests.swift index 1dc9bddd718..1a0fc7b02b8 100644 --- a/FirebaseAuth/Tests/SampleSwift/AuthenticationExampleUITests/AuthenticationExampleUITests.swift +++ b/FirebaseAuth/Tests/SampleSwift/AuthenticationExampleUITests/AuthenticationExampleUITests.swift @@ -36,10 +36,11 @@ class AuthenticationExampleUITests: XCTestCase { XCTAssertTrue(app.navigationBars["Firebase Auth"].exists) } - func testAuthOptions() { - // There are 15 sign in methods, each with its own cell - XCTAssertEqual(app.tables.cells.count, 15) - } + // TODO: Modify this test after code refactoring, current AuthMenu items aren't necessarily sign in methods + // func testAuthOptions() { + // // There are 16 sign in methods, each with its own cell + // XCTAssertEqual(app.tables.cells.count, 16) + // } func testAuthAnonymously() { app.staticTexts["Anonymous Authentication"].tap() diff --git a/FirebaseAuth/Tests/Unit/AuthBackendRPCImplentationTests.swift b/FirebaseAuth/Tests/Unit/AuthBackendRPCImplentationTests.swift index dc43d57168b..8a425c82599 100644 --- a/FirebaseAuth/Tests/Unit/AuthBackendRPCImplentationTests.swift +++ b/FirebaseAuth/Tests/Unit/AuthBackendRPCImplentationTests.swift @@ -235,7 +235,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { expected. We are expecting to receive an @c NSError with the code @c FIRAuthErrorCodeUnexpectedServerResponse with the decoded response in the @c NSError.userInfo dictionary associated with the key - @c FIRAuthErrorUserInfoDecodedResponseKey. + `userInfoDeserializedResponseKey`. */ func testNonDictionarySuccessResponse() async throws { // We are responding with a JSON-encoded string value representing an array - which is diff --git a/FirebaseAuth/Tests/Unit/SwiftAPI.swift b/FirebaseAuth/Tests/Unit/SwiftAPI.swift index 6e43a499abb..d620e9c0ea7 100644 --- a/FirebaseAuth/Tests/Unit/SwiftAPI.swift +++ b/FirebaseAuth/Tests/Unit/SwiftAPI.swift @@ -614,8 +614,8 @@ class AuthAPI_hOnlyTests: XCTestCase { changeRequest.commitChanges { _ in } let _: String = user.providerID - if let _: String = user.uid, - let _: String = user.displayName, + let _: String = user.uid + if let _: String = user.displayName, let _: URL = user.photoURL, let _: String = user.email, let _: String = user.phoneNumber {} @@ -671,8 +671,8 @@ class AuthAPI_hOnlyTests: XCTestCase { func userInfoProperties(userInfo: UserInfo) { let _: String = userInfo.providerID - if let _: String = userInfo.uid, - let _: String = userInfo.displayName, + let _: String = userInfo.uid + if let _: String = userInfo.displayName, let _: URL = userInfo.photoURL, let _: String = userInfo.email, let _: String = userInfo.phoneNumber {} diff --git a/FirebaseAuthInterop.podspec b/FirebaseAuthInterop.podspec index d82a8dd0115..19d59f41e64 100644 --- a/FirebaseAuthInterop.podspec +++ b/FirebaseAuthInterop.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAuthInterop' - s.version = '10.21.0' + s.version = '10.23.0' s.summary = 'Interfaces that allow other Firebase SDKs to use Auth functionality.' s.description = <<-DESC @@ -25,6 +25,6 @@ Pod::Spec.new do |s| s.tvos.deployment_target = '12.0' s.watchos.deployment_target = '6.0' - s.source_files = 'FirebaseAuth/Interop/*.[hm]' - s.public_header_files = 'FirebaseAuth/Interop/*.h' + s.source_files = 'FirebaseAuth/Interop/**/*.[hm]' + s.public_header_files = 'FirebaseAuth/Interop/Public/FirebaseAuthInterop/*.h' end diff --git a/FirebaseAuthTestingSupport.podspec b/FirebaseAuthTestingSupport.podspec index ff7544f518f..f581d15d40e 100644 --- a/FirebaseAuthTestingSupport.podspec +++ b/FirebaseAuthTestingSupport.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAuthTestingSupport' - s.version = '1.0.0' + s.version = '2.0.0' s.summary = 'Firebase SDKs testing support types and utilities.' s.description = <<-DESC @@ -17,10 +17,10 @@ Pod::Spec.new do |s| :tag => 'CocoaPods-' + s.version.to_s } - ios_deployment_target = '11.0' + ios_deployment_target = '13.0' osx_deployment_target = '10.13' - tvos_deployment_target = '12.0' - watchos_deployment_target = '6.0' + tvos_deployment_target = '13.0' + watchos_deployment_target = '7.0' s.swift_version = '5.3' @@ -29,26 +29,17 @@ Pod::Spec.new do |s| s.tvos.deployment_target = tvos_deployment_target s.watchos.deployment_target = watchos_deployment_target - s.cocoapods_version = '>= 1.4.0' + s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false s.requires_arc = true base_dir = 'FirebaseTestingSupport/Auth/' s.source_files = [ - base_dir + 'Sources/**/*.{m,mm,h}', + base_dir + 'Sources/**/*.swift', ] - s.public_header_files = base_dir + '**/*.h' - - s.dependency 'FirebaseAuth', '~> 10.0' - - s.pod_target_xcconfig = { - 'GCC_C_LANGUAGE_STANDARD' => 'c99', - 'OTHER_CFLAGS' => '-fno-autolink', - 'HEADER_SEARCH_PATHS' => - '"${PODS_TARGET_SRCROOT}" ' - } + s.dependency 'FirebaseAuth', '~> 10.22' s.test_spec 'unit' do |unit_tests| unit_tests.scheme = { :code_coverage => true } diff --git a/FirebaseCombineSwift.podspec b/FirebaseCombineSwift.podspec index a48fa8db5c1..75ce5dfbda4 100644 --- a/FirebaseCombineSwift.podspec +++ b/FirebaseCombineSwift.podspec @@ -31,7 +31,7 @@ for internal testing only. It should not be published. s.tvos.deployment_target = tvos_deployment_target s.watchos.deployment_target = watchos_deployment_target - s.cocoapods_version = '>= 1.4.0' + s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false source = 'FirebaseCombineSwift/Sources/' diff --git a/FirebaseCore.podspec b/FirebaseCore.podspec index c1e238771d7..4957df4fa6e 100644 --- a/FirebaseCore.podspec +++ b/FirebaseCore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseCore' - s.version = '10.21.0' + s.version = '10.23.0' s.summary = 'Firebase Core' s.description = <<-DESC @@ -28,7 +28,7 @@ Firebase Core includes FIRApp and FIROptions which provide central configuration s.tvos.deployment_target = tvos_deployment_target s.watchos.deployment_target = watchos_deployment_target - s.cocoapods_version = '>= 1.4.0' + s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false s.source_files = [ @@ -36,6 +36,10 @@ Firebase Core includes FIRApp and FIROptions which provide central configuration 'FirebaseCore/Extension/*.h' ] + s.resource_bundles = { + "#{s.module_name}_Privacy" => 'FirebaseCore/Sources/Resources/PrivacyInfo.xcprivacy' + } + s.swift_version = '5.3' s.public_header_files = 'FirebaseCore/Sources/Public/FirebaseCore/*.h' diff --git a/FirebaseCore/CHANGELOG.md b/FirebaseCore/CHANGELOG.md index 92a4297058b..c5a879602d9 100644 --- a/FirebaseCore/CHANGELOG.md +++ b/FirebaseCore/CHANGELOG.md @@ -1,7 +1,47 @@ +# Firebase 10.23.0 +- Fix validation issue for macOS and macCatalyst XCFrameworks. (#12505) + +# Firebase 10.22.1 +- [Swift Package Manager / CocoaPods] Fix app validation issues on Xcode 15.3 + for those using the `FirebaseAnalyticsOnDeviceConversion` SDK. This issue was + caused by embedding an incomplete `Info.plist` from a dependency of the SDK. + (#12441) + +# Firebase 10.22.0 +- [Swift Package Manager] Firebase now enforces a Swift 5.7.1 minimum version, + which is aligned with the Xcode 14.1 minimum. (#12350) +- Revert Firebase 10.20.0 change that removed `Info.plist` files from + static xcframeworks (#12390). +- Added privacy manifests for Firebase SDKs named in + https://developer.apple.com/support/third-party-SDK-requirements/. Please + review https://firebase.google.com/docs/ios/app-store-data-collection for + updated guidance on interpreting Firebase's privacy manifests and completing + app Privacy Nutrition Labels. (#11490) +- Fixed validation issues in Xcode 15.3 that affected binary distributions + including Analytics, Firestore (SwiftPM binary distribution), and the + Firebase zip distribution. (#12441) +- [Zip Distribution] The manual integration instructions found in the + `Firebase.zip` have been updated for Xcode 15 users. The updated instructions + call for embedding SDKs dragged in from the `Firebase.zip`. This will enable + Xcode's tooling to detect privacy manifests bundled within the xcframework. +- [Zip Distribution] Several xcframeworks have been renamed to resolve the above + Xcode 15.3 validation issues. Please ensure that the following renamed + xcframeworks are removed from your project when upgrading (#12437, #12447): + - `abseil.xcframework` to `absl.xcframework` + - `BoringSSL-GRPC.xcframework` to `openssl_grpc.xcframework` + - `gRPC-Core.xcframework` to `grpc.xcframework` + - `gRPC-C++.xcframework` to `grpcpp.xcframework` + - `leveldb-library.xcframework` to `leveldb.xcframework` + - `PromisesSwift.xcframework` to `Promises.xcframework` + +# Firebase 10.21.0 +- Firebase now requires at least CocoaPods version 1.12.0 to enable privacy + manifest support. + # Firebase 10.20.0 - The following change only applies to those using a binary distribution of a Firebase SDK(s): In preparation for supporting Privacy Manifests, each - platform framework directory within a static xcframewok no longer contains + platform framework directory within a static xcframework no longer contains an `Info.plist` file (#12243). # Firebase 10.14.0 diff --git a/FirebaseCore/Extension/FIRComponentType.h b/FirebaseCore/Extension/FIRComponentType.h index 6f2aca7b863..c69085d1983 100644 --- a/FirebaseCore/Extension/FIRComponentType.h +++ b/FirebaseCore/Extension/FIRComponentType.h @@ -27,7 +27,8 @@ NS_SWIFT_NAME(ComponentType) /// Do not use directly. A factory method to retrieve an instance that provides a specific /// functionality. -+ (T)instanceForProtocol:(Protocol *)protocol inContainer:(FIRComponentContainer *)container; ++ (nullable T)instanceForProtocol:(Protocol *)protocol + inContainer:(FIRComponentContainer *)container; @end diff --git a/FirebaseCore/Extension/Resources/PrivacyInfo.xcprivacy b/FirebaseCore/Extension/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 00000000000..c89c88f62f5 --- /dev/null +++ b/FirebaseCore/Extension/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,18 @@ + + + + + NSPrivacyTracking + + NSPrivacyTrackingDomains + + + NSPrivacyCollectedDataTypes + + + NSPrivacyAccessedAPITypes + + + + + diff --git a/FirebaseCore/Internal/Sources/Resources/PrivacyInfo.xcprivacy b/FirebaseCore/Internal/Sources/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 00000000000..3fb515ffdd3 --- /dev/null +++ b/FirebaseCore/Internal/Sources/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,26 @@ + + + + + NSPrivacyTracking + + NSPrivacyTrackingDomains + + + NSPrivacyCollectedDataTypes + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + 1C8F.1 + + + + + + diff --git a/FirebaseCore/Sources/FIRComponentType.m b/FirebaseCore/Sources/FIRComponentType.m index c9cd2ad2c2c..2204fd65b32 100644 --- a/FirebaseCore/Sources/FIRComponentType.m +++ b/FirebaseCore/Sources/FIRComponentType.m @@ -20,7 +20,8 @@ @implementation FIRComponentType -+ (id)instanceForProtocol:(Protocol *)protocol inContainer:(FIRComponentContainer *)container { ++ (nullable id)instanceForProtocol:(Protocol *)protocol + inContainer:(FIRComponentContainer *)container { // Forward the call to the container. return [container instanceForProtocol:protocol]; } diff --git a/FirebaseCore/Sources/Resources/PrivacyInfo.xcprivacy b/FirebaseCore/Sources/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 00000000000..0244f2f54b0 --- /dev/null +++ b/FirebaseCore/Sources/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,26 @@ + + + + + NSPrivacyTracking + + NSPrivacyTrackingDomains + + + NSPrivacyCollectedDataTypes + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + + + diff --git a/FirebaseCoreExtension.podspec b/FirebaseCoreExtension.podspec index 3ba07f3d5c9..5c2e7e8a4d0 100644 --- a/FirebaseCoreExtension.podspec +++ b/FirebaseCoreExtension.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseCoreExtension' - s.version = '10.21.0' + s.version = '10.23.0' s.summary = 'Extended FirebaseCore APIs for Firebase product SDKs' s.description = <<-DESC @@ -30,5 +30,9 @@ Pod::Spec.new do |s| s.source_files = 'FirebaseCore/Extension/*.[hm]' s.public_header_files = 'FirebaseCore/Extension/*.h' + s.resource_bundles = { + "#{s.module_name}_Privacy" => 'FirebaseCore/Extension/Resources/PrivacyInfo.xcprivacy' + } + s.dependency 'FirebaseCore', '~> 10.0' end diff --git a/FirebaseCoreInternal.podspec b/FirebaseCoreInternal.podspec index cb545f2e8b7..1990db42d28 100644 --- a/FirebaseCoreInternal.podspec +++ b/FirebaseCoreInternal.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseCoreInternal' - s.version = '10.21.0' + s.version = '10.23.0' s.summary = 'APIs for internal FirebaseCore usage.' s.description = <<-DESC @@ -32,6 +32,10 @@ Pod::Spec.new do |s| 'FirebaseCore/Internal/Sources/**/*.swift' ] + s.resource_bundles = { + "#{s.module_name}_Privacy" => 'FirebaseCore/Internal/Sources/Resources/PrivacyInfo.xcprivacy' + } + s.swift_version = '5.3' s.dependency 'GoogleUtilities/NSData+zlib', '~> 7.8' diff --git a/FirebaseCrashlytics.podspec b/FirebaseCrashlytics.podspec index 395f941235a..e67e886b399 100644 --- a/FirebaseCrashlytics.podspec +++ b/FirebaseCrashlytics.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseCrashlytics' - s.version = '10.21.0' + s.version = '10.23.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/' @@ -23,11 +23,11 @@ Pod::Spec.new do |s| s.tvos.deployment_target = tvos_deployment_target s.watchos.deployment_target = watchos_deployment_target - s.cocoapods_version = '>= 1.4.0' + s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false s.source_files = [ - 'Crashlytics/Crashlytics/**/*.{c,h,m,mm}', + 'Crashlytics/Crashlytics/**/*.{c,h,m,mm,swift}', 'Crashlytics/Protogen/**/*.{c,h,m,mm}', 'Crashlytics/Shared/**/*.{c,h,m,mm}', 'Crashlytics/third_party/**/*.{c,h,m,mm}', @@ -36,6 +36,10 @@ Pod::Spec.new do |s| 'Interop/Analytics/Public/*.h', ] + s.resource_bundles = { + "#{s.module_name}_Privacy" => 'Crashlytics/Resources/PrivacyInfo.xcprivacy' + } + s.public_header_files = [ 'Crashlytics/Crashlytics/Public/FirebaseCrashlytics/*.h' ] @@ -58,10 +62,11 @@ Pod::Spec.new do |s| s.dependency 'FirebaseCore', '~> 10.5' s.dependency 'FirebaseInstallations', '~> 10.0' s.dependency 'FirebaseSessions', '~> 10.5' + s.dependency 'FirebaseRemoteConfigInterop', '~> 10.23' s.dependency 'PromisesObjC', '~> 2.1' s.dependency 'GoogleDataTransport', '~> 9.2' s.dependency 'GoogleUtilities/Environment', '~> 7.8' - s.dependency 'nanopb', '>= 2.30908.0', '< 2.30910.0' + s.dependency 'nanopb', '>= 2.30908.0', '< 2.30911.0' s.libraries = 'c++', 'z' s.ios.frameworks = 'Security', 'SystemConfiguration' @@ -115,7 +120,8 @@ Pod::Spec.new do |s| :tvos => tvos_deployment_target } unit_tests.source_files = 'Crashlytics/UnitTests/*.[mh]', - 'Crashlytics/UnitTests/*/*.[mh]' + 'Crashlytics/UnitTests/*/*.[mh]', + 'Crashlytics/UnitTestsSwift/*.swift' unit_tests.resources = 'Crashlytics/UnitTests/Data/*', 'Crashlytics/UnitTests/*.clsrecord', 'Crashlytics/UnitTests/FIRCLSMachO/machO_data/*' diff --git a/FirebaseDatabase.podspec b/FirebaseDatabase.podspec index f6e66bbe5f0..3aa94748bda 100644 --- a/FirebaseDatabase.podspec +++ b/FirebaseDatabase.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseDatabase' - s.version = '10.21.0' + s.version = '10.23.0' s.summary = 'Firebase Realtime Database' s.description = <<-DESC @@ -29,7 +29,7 @@ Simplify your iOS development, grow your user base, and monetize more effectivel s.tvos.deployment_target = tvos_deployment_target s.watchos.deployment_target = watchos_deployment_target - s.cocoapods_version = '>= 1.4.0' + s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false base_dir = "FirebaseDatabase/Sources/" diff --git a/FirebaseDatabase/Sources/Api/FIRDatabase.m b/FirebaseDatabase/Sources/Api/FIRDatabase.m index f90e318ab8f..355de35b85d 100644 --- a/FirebaseDatabase/Sources/Api/FIRDatabase.m +++ b/FirebaseDatabase/Sources/Api/FIRDatabase.m @@ -16,7 +16,7 @@ #import -#import "FirebaseAuth/Interop/FIRAuthInterop.h" +#import "FirebaseAuth/Interop/Public/FirebaseAuthInterop/FIRAuthInterop.h" #import "FirebaseCore/Extension/FirebaseCoreInternal.h" #import "FirebaseDatabase/Sources/Api/FIRDatabaseComponent.h" diff --git a/FirebaseDatabase/Sources/Api/FIRDatabaseComponent.m b/FirebaseDatabase/Sources/Api/FIRDatabaseComponent.m index 6a65b8e91f3..57a6c1e73d9 100644 --- a/FirebaseDatabase/Sources/Api/FIRDatabaseComponent.m +++ b/FirebaseDatabase/Sources/Api/FIRDatabaseComponent.m @@ -20,7 +20,7 @@ #import "FirebaseDatabase/Sources/Core/FRepoManager.h" #import "FirebaseDatabase/Sources/FIRDatabaseConfig_Private.h" -#import "FirebaseAuth/Interop/FIRAuthInterop.h" +#import "FirebaseAuth/Interop/Public/FirebaseAuthInterop/FIRAuthInterop.h" #import "FirebaseCore/Extension/FirebaseCoreInternal.h" #import diff --git a/FirebaseDatabase/Sources/Login/FIRDatabaseConnectionContextProvider.m b/FirebaseDatabase/Sources/Login/FIRDatabaseConnectionContextProvider.m index 358a26d1e70..213886dfd3e 100644 --- a/FirebaseDatabase/Sources/Login/FIRDatabaseConnectionContextProvider.m +++ b/FirebaseDatabase/Sources/Login/FIRDatabaseConnectionContextProvider.m @@ -18,7 +18,7 @@ #import "FirebaseCore/Extension/FirebaseCoreInternal.h" -#import "FirebaseAuth/Interop/FIRAuthInterop.h" +#import "FirebaseAuth/Interop/Public/FirebaseAuthInterop/FIRAuthInterop.h" #import "FirebaseDatabase/Sources/Api/Private/FIRDatabaseQuery_Private.h" #import "FirebaseDatabase/Sources/Utilities/FUtilities.h" diff --git a/FirebaseDatabase/Tests/Helpers/FTestHelpers.m b/FirebaseDatabase/Tests/Helpers/FTestHelpers.m index 26ba73453c9..26fe0141595 100644 --- a/FirebaseDatabase/Tests/Helpers/FTestHelpers.m +++ b/FirebaseDatabase/Tests/Helpers/FTestHelpers.m @@ -16,7 +16,7 @@ #import "FirebaseDatabase/Tests/Helpers/FTestHelpers.h" -#import "FirebaseAuth/Interop/FIRAuthInterop.h" +#import "FirebaseAuth/Interop/Public/FirebaseAuthInterop/FIRAuthInterop.h" #import "FirebaseCore/Extension/FirebaseCoreInternal.h" #import "FirebaseDatabase/Sources/Api/Private/FIRDatabase_Private.h" diff --git a/FirebaseDatabase/Tests/Integration/FData.m b/FirebaseDatabase/Tests/Integration/FData.m index 012603b8ca0..2fa04319f9f 100644 --- a/FirebaseDatabase/Tests/Integration/FData.m +++ b/FirebaseDatabase/Tests/Integration/FData.m @@ -314,9 +314,10 @@ - (void)testWriteLeafNodeOverwriteAtParentMultipleTimesVerifyExpectedEvents { ios - sdk / firebase - ios - sdk / Example / Database / Tests / Helpers / FEventTester - .m : 123 because it was raised inside test case -[FEventTester(null)] which has no - associated XCTestRun object.This may happen when test cases are - constructed and invoked independently of standard XCTest infrastructure, + .m : 123 because it was raised inside test case - + [FEventTester( + null)] which has no associated XCTestRun object.This may happen when test cases + are constructed and invoked independently of standard XCTest infrastructure, or when the test has already finished ." - Expected http://localhost:9000/-M8IJYWb68MuqQKKz2IY/a aa (0) to match " "http://localhost:9000/-M8IJYWb68MuqQKKz2IY/a (null) (4)' from " diff --git a/FirebaseDatabase/Tests/Integration/FIRAuthTests.m b/FirebaseDatabase/Tests/Integration/FIRAuthTests.m index 1bdbe2bca2e..c342d052e93 100644 --- a/FirebaseDatabase/Tests/Integration/FIRAuthTests.m +++ b/FirebaseDatabase/Tests/Integration/FIRAuthTests.m @@ -16,7 +16,7 @@ #import -#import "FirebaseAuth/Interop/FIRAuthInterop.h" +#import "FirebaseAuth/Interop/Public/FirebaseAuthInterop/FIRAuthInterop.h" #import "FirebaseCore/Extension/FirebaseCoreInternal.h" #import "FirebaseDatabase/Sources/FIRDatabaseConfig_Private.h" diff --git a/FirebaseDatabase/Tests/Integration/FIRDatabaseQueryTests.m b/FirebaseDatabase/Tests/Integration/FIRDatabaseQueryTests.m index a9945b50e49..4163cd15ae6 100644 --- a/FirebaseDatabase/Tests/Integration/FIRDatabaseQueryTests.m +++ b/FirebaseDatabase/Tests/Integration/FIRDatabaseQueryTests.m @@ -4192,9 +4192,7 @@ - (void)testGetForParentReturnsCorrectValue { [ref getDataWithCompletionBlock:^(NSError* err, FIRDataSnapshot* snapshot) { XCTAssertNil(err); - XCTAssertEqualObjects( - [snapshot value], - @{@"a" : @1}); + XCTAssertEqualObjects([snapshot value], @{@"a" : @1}); done = YES; }]; } diff --git a/FirebaseDatabase/Tests/Integration/FOrderByTests.m b/FirebaseDatabase/Tests/Integration/FOrderByTests.m index 86642d36e65..e3734f8d915 100644 --- a/FirebaseDatabase/Tests/Integration/FOrderByTests.m +++ b/FirebaseDatabase/Tests/Integration/FOrderByTests.m @@ -235,9 +235,7 @@ - (void)testFiresChildMovedEvents { moved = YES; XCTAssertEqualObjects(snapshot.key, @"greg", @""); XCTAssertEqualObjects(prevName, @"rob", @""); - XCTAssertEqualObjects( - snapshot.value, - @{@"nuggets" : @57}, @""); + XCTAssertEqualObjects(snapshot.value, @{@"nuggets" : @57}, @""); }]; [ref setValue:initial]; diff --git a/FirebaseDatabaseSwift.podspec b/FirebaseDatabaseSwift.podspec index aba011e0dcf..3e066ab7a0f 100644 --- a/FirebaseDatabaseSwift.podspec +++ b/FirebaseDatabaseSwift.podspec @@ -21,7 +21,7 @@ Simplify your iOS development, grow your user base, and monetize more effectivel s.osx.deployment_target = '10.13' s.tvos.deployment_target = '12.0' - s.cocoapods_version = '>= 1.4.0' + s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false s.source_files = [ diff --git a/FirebaseDynamicLinks.podspec b/FirebaseDynamicLinks.podspec index 97af8532edb..aaa43aec721 100644 --- a/FirebaseDynamicLinks.podspec +++ b/FirebaseDynamicLinks.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseDynamicLinks' - s.version = '10.21.0' + s.version = '10.23.0' s.summary = 'Firebase Dynamic Links' s.description = <<-DESC @@ -20,7 +20,7 @@ Firebase Dynamic Links are deep links that enhance user experience and increase s.swift_version = '5.3' - s.cocoapods_version = '>= 1.4.0' + s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false s.source_files = [ @@ -29,6 +29,9 @@ Firebase Dynamic Links are deep links that enhance user experience and increase 'FirebaseCore/Extension/*.h', ] s.public_header_files = 'FirebaseDynamicLinks/Sources/Public/FirebaseDynamicLinks/*.h' + s.resource_bundles = { + "#{s.module_name}_Privacy" => 'FirebaseDynamicLinks/Sources/Resources/PrivacyInfo.xcprivacy' + } s.frameworks = 'QuartzCore' s.weak_framework = 'WebKit' s.dependency 'FirebaseCore', '~> 10.0' diff --git a/FirebaseDynamicLinks/Sources/Resources/PrivacyInfo.xcprivacy b/FirebaseDynamicLinks/Sources/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 00000000000..0fccd3b0f69 --- /dev/null +++ b/FirebaseDynamicLinks/Sources/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,46 @@ + + + + + NSPrivacyTracking + + NSPrivacyTrackingDomains + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeOtherDataTypes + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + 1C8F.1 + + + + + + diff --git a/FirebaseFirestore.podspec b/FirebaseFirestore.podspec index 51bd36d1dbd..b7ea45b827c 100644 --- a/FirebaseFirestore.podspec +++ b/FirebaseFirestore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseFirestore' - s.version = '10.21.0' + s.version = '10.23.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. @@ -21,7 +21,7 @@ Google Cloud Firestore is a NoSQL document database built for automatic scaling, s.weak_framework = 'FirebaseFirestoreInternal' - s.cocoapods_version = '>= 1.4.0' + s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false s.public_header_files = 'FirebaseFirestoreInternal/**/*.h' @@ -31,6 +31,9 @@ Google Cloud Firestore is a NoSQL document database built for automatic scaling, 'FirebaseFirestoreInternal/**/*.[mh]', 'Firestore/Swift/Source/**/*.swift', ] + s.resource_bundles = { + "#{s.module_name}_Privacy" => 'Firestore/Swift/Source/Resources/PrivacyInfo.xcprivacy' + } s.dependency 'FirebaseCore', '~> 10.0' s.dependency 'FirebaseCoreExtension', '~> 10.0' diff --git a/FirebaseFirestoreInternal.podspec b/FirebaseFirestoreInternal.podspec index 4afa3804ff9..4717dcf1cf7 100644 --- a/FirebaseFirestoreInternal.podspec +++ b/FirebaseFirestoreInternal.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseFirestoreInternal' - s.version = '10.21.0' + s.version = '10.23.0' s.summary = 'Google Cloud Firestore' s.description = <<-DESC @@ -22,7 +22,7 @@ Google Cloud Firestore is a NoSQL document database built for automatic scaling, s.swift_version = '5.3' - s.cocoapods_version = '>= 1.4.0' + s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false # Header files that constitute the interface to this module. Only Objective-C @@ -87,10 +87,14 @@ Google Cloud Firestore is a NoSQL document database built for automatic scaling, 'Firestore/core/src/util/secure_random_openssl.cc' ] + s.resource_bundles = { + "#{s.module_name}_Privacy" => 'Firestore/Source/Resources/PrivacyInfo.xcprivacy' + } + s.dependency 'FirebaseAppCheckInterop', '~> 10.17' s.dependency 'FirebaseCore', '~> 10.0' - abseil_version = '~> 1.20220623.0' + abseil_version = '~> 1.20240116.1' s.dependency 'abseil/algorithm', abseil_version s.dependency 'abseil/base', abseil_version s.dependency 'abseil/container/flat_hash_map', abseil_version @@ -100,9 +104,10 @@ Google Cloud Firestore is a NoSQL document database built for automatic scaling, s.dependency 'abseil/time', abseil_version s.dependency 'abseil/types', abseil_version - s.dependency 'gRPC-C++', '~> 1.49.1' + s.dependency 'gRPC-Core', '~> 1.62.0' + s.dependency 'gRPC-C++', '~> 1.62.0' s.dependency 'leveldb-library', '~> 1.22' - s.dependency 'nanopb', '>= 2.30908.0', '< 2.30910.0' + s.dependency 'nanopb', '>= 2.30908.0', '< 2.30911.0' s.ios.frameworks = 'SystemConfiguration', 'UIKit' s.osx.frameworks = 'SystemConfiguration' diff --git a/FirebaseFirestoreInternal/FirebaseFirestore/FIRSnapshotListenOptions.h b/FirebaseFirestoreInternal/FirebaseFirestore/FIRSnapshotListenOptions.h new file mode 100644 index 00000000000..6d61e32c56b --- /dev/null +++ b/FirebaseFirestoreInternal/FirebaseFirestore/FIRSnapshotListenOptions.h @@ -0,0 +1,15 @@ +// 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. + +#import diff --git a/FirebaseFirestoreSwift.podspec b/FirebaseFirestoreSwift.podspec index 4045e2a12a3..4582f9f240f 100644 --- a/FirebaseFirestoreSwift.podspec +++ b/FirebaseFirestoreSwift.podspec @@ -26,7 +26,7 @@ Google Cloud Firestore is a NoSQL document database built for automatic scaling, s.osx.deployment_target = '10.13' s.tvos.deployment_target = '12.0' - s.cocoapods_version = '>= 1.4.0' + s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false s.requires_arc = true diff --git a/FirebaseFirestoreTestingSupport.podspec b/FirebaseFirestoreTestingSupport.podspec index ef3dcefe46c..c2c13ccb23e 100644 --- a/FirebaseFirestoreTestingSupport.podspec +++ b/FirebaseFirestoreTestingSupport.podspec @@ -29,7 +29,7 @@ Pod::Spec.new do |s| s.tvos.deployment_target = tvos_deployment_target s.watchos.deployment_target = watchos_deployment_target - s.cocoapods_version = '>= 1.4.0' + s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false s.requires_arc = true diff --git a/FirebaseFunctions.podspec b/FirebaseFunctions.podspec index 484c291a383..9c74c320285 100644 --- a/FirebaseFunctions.podspec +++ b/FirebaseFunctions.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseFunctions' - s.version = '10.21.0' + s.version = '10.23.0' s.summary = 'Cloud Functions for Firebase' s.description = <<-DESC @@ -28,7 +28,7 @@ Cloud Functions for Firebase. s.tvos.deployment_target = tvos_deployment_target s.watchos.deployment_target = watchos_deployment_target - s.cocoapods_version = '>= 1.4.0' + s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false s.swift_version = '5.3' diff --git a/FirebaseFunctions/README.md b/FirebaseFunctions/README.md index fb80500cd58..765b6c1df9f 100644 --- a/FirebaseFunctions/README.md +++ b/FirebaseFunctions/README.md @@ -7,7 +7,7 @@ integration test FirebaseFunctions: ### Prereqs -- At least CocoaPods 1.10.0 +- At least CocoaPods 1.12.0 - Install [cocoapods-generate](https://github.com/square/cocoapods-generate) ### To Develop diff --git a/FirebaseFunctions/Sources/Callable+Codable.swift b/FirebaseFunctions/Sources/Callable+Codable.swift index 559b9846ce4..5a8e508a9f0 100644 --- a/FirebaseFunctions/Sources/Callable+Codable.swift +++ b/FirebaseFunctions/Sources/Callable+Codable.swift @@ -15,7 +15,7 @@ import FirebaseSharedSwift import Foundation -// A `Callable` is reference to a particular Callable HTTPS trigger in Cloud Functions. +/// A `Callable` is reference to a particular Callable HTTPS trigger in Cloud Functions. public struct Callable { /// The timeout to use when calling the function. Defaults to 60 seconds. public var timeoutInterval: TimeInterval { diff --git a/FirebaseFunctions/Sources/Functions.swift b/FirebaseFunctions/Sources/Functions.swift index 61d00d5e040..14eeb4e4f1b 100644 --- a/FirebaseFunctions/Sources/Functions.swift +++ b/FirebaseFunctions/Sources/Functions.swift @@ -38,9 +38,7 @@ enum FunctionsConstants { static let defaultRegion = "us-central1" } -/** - * `Functions` is the client for Cloud Functions for a Firebase project. - */ +/// `Functions` is the client for Cloud Functions for a Firebase project. @objc(FIRFunctions) open class Functions: NSObject { // MARK: - Private Variables @@ -61,15 +59,12 @@ enum FunctionsConstants { // MARK: - Public APIs - /** - * The current emulator origin, or `nil` if it is not set. - */ + /// The current emulator origin, or `nil` if it is not set. open private(set) var emulatorOrigin: String? - /** - * Creates a Cloud Functions client using the default or returns a pre-existing instance if it already exists. - * - Returns: A shared Functions instance initialized with the default `FirebaseApp`. - */ + /// Creates a Cloud Functions client using the default or returns a pre-existing instance if it + /// already exists. + /// - Returns: A shared Functions instance initialized with the default `FirebaseApp`. @objc(functions) open class func functions() -> Functions { return functions( app: FirebaseApp.app(), @@ -78,57 +73,51 @@ enum FunctionsConstants { ) } - /** - * Creates a Cloud Functions client with the given app, or returns a pre-existing - * instance if one already exists. - * - Parameter app The app for the Firebase project. - * - Returns: A shared Functions instance initialized with the specified `FirebaseApp`. - */ + /// Creates a Cloud Functions client with the given app, or returns a pre-existing + /// instance if one already exists. + /// - Parameter app: The app for the Firebase project. + /// - Returns: A shared Functions instance initialized with the specified `FirebaseApp`. @objc(functionsForApp:) open class func functions(app: FirebaseApp) -> Functions { return functions(app: app, region: FunctionsConstants.defaultRegion, customDomain: nil) } - /** - * Creates a Cloud Functions client with the default app and given region. - * - Parameter region The region for the HTTP trigger, such as `us-central1`. - * - Returns: A shared Functions instance initialized with the default `FirebaseApp` and a custom region. - */ + /// Creates a Cloud Functions client with the default app and given region. + /// - Parameter region: The region for the HTTP trigger, such as `us-central1`. + /// - Returns: A shared Functions instance initialized with the default `FirebaseApp` and a + /// custom region. @objc(functionsForRegion:) open class func functions(region: String) -> Functions { return functions(app: FirebaseApp.app(), region: region, customDomain: nil) } - /** - * Creates a Cloud Functions client with the given app and region, or returns a pre-existing - * instance if one already exists. - * - Parameter customDomain A custom domain for the HTTP trigger, such as "https://mydomain.com". - * - Returns: A shared Functions instance initialized with the default `FirebaseApp` and a custom HTTP trigger domain. - */ + /// Creates a Cloud Functions client with the given custom domain or returns a pre-existing + /// instance if one already exists. + /// - Parameter customDomain: A custom domain for the HTTP trigger, such as + /// "https://mydomain.com". + /// - Returns: A shared Functions instance initialized with the default `FirebaseApp` and a + /// custom HTTP trigger domain. @objc(functionsForCustomDomain:) open class func functions(customDomain: String) -> Functions { return functions(app: FirebaseApp.app(), region: FunctionsConstants.defaultRegion, customDomain: customDomain) } - /** - * Creates a Cloud Functions client with the given app and region, or returns a pre-existing - * instance if one already exists. - * - Parameters: - * - app: The app for the Firebase project. - * - region: The region for the HTTP trigger, such as `us-central1`. - * - Returns: An instance of `Functions` with a custom app and region. - */ + /// Creates a Cloud Functions client with the given app and region, or returns a pre-existing + /// instance if one already exists. + /// - Parameters: + /// - app: The app for the Firebase project. + /// - region: The region for the HTTP trigger, such as `us-central1`. + /// - Returns: An instance of `Functions` with a custom app and region. @objc(functionsForApp:region:) open class func functions(app: FirebaseApp, region: String) -> Functions { return functions(app: app, region: region, customDomain: nil) } - /** - * Creates a Cloud Functions client with the given app and region, or returns a pre-existing - * instance if one already exists. - * - Parameters: - * - app The app for the Firebase project. - * - customDomain A custom domain for the HTTP trigger, such as `https://mydomain.com`. - * - Returns: An instance of `Functions` with a custom app and HTTP trigger domain. - */ + /// Creates a Cloud Functions client with the given app and custom domain, or returns a + /// pre-existing + /// instance if one already exists. + /// - Parameters: + /// - app: The app for the Firebase project. + /// - customDomain: A custom domain for the HTTP trigger, such as `https://mydomain.com`. + /// - Returns: An instance of `Functions` with a custom app and HTTP trigger domain. @objc(functionsForApp:customDomain:) open class func functions(app: FirebaseApp, customDomain: String) -> Functions { diff --git a/FirebaseFunctions/Sources/FunctionsError.swift b/FirebaseFunctions/Sources/FunctionsError.swift index 8227d1cd1e0..8af6df9e077 100644 --- a/FirebaseFunctions/Sources/FunctionsError.swift +++ b/FirebaseFunctions/Sources/FunctionsError.swift @@ -14,7 +14,7 @@ import Foundation -/// The error domain for codes in the `FunctionsErrorCode` enum. +/// The error domain for codes in the ``FunctionsErrorCode`` enum. public let FunctionsErrorDomain: String = "com.firebase.functions" /// The key for finding error details in the `NSError` userInfo. diff --git a/FirebaseFunctions/Sources/HTTPSCallable.swift b/FirebaseFunctions/Sources/HTTPSCallable.swift index 5f773d43463..8391d153236 100644 --- a/FirebaseFunctions/Sources/HTTPSCallable.swift +++ b/FirebaseFunctions/Sources/HTTPSCallable.swift @@ -14,18 +14,14 @@ import Foundation -/** - * A `HTTPSCallableResult` contains the result of calling a `HTTPSCallable`. - */ +/// A `HTTPSCallableResult` contains the result of calling a `HTTPSCallable`. @objc(FIRHTTPSCallableResult) open class HTTPSCallableResult: NSObject { - /** - * The data that was returned from the Callable HTTPS trigger. - * - * The data is in the form of native objects. For example, if your trigger returned an - * array, this object would be an `Array`. If your trigger returned a JavaScript object with - * keys and values, this object would be an instance of `[String: Any]`. - */ + /// The data that was returned from the Callable HTTPS trigger. + /// + /// The data is in the form of native objects. For example, if your trigger returned an + /// array, this object would be an `Array`. If your trigger returned a JavaScript object with + /// keys and values, this object would be an instance of `[String: Any]`. @objc public let data: Any init(data: Any) { @@ -54,9 +50,7 @@ open class HTTPSCallable: NSObject { // MARK: - Public Properties - /** - * The timeout to use when calling the function. Defaults to 70 seconds. - */ + /// The timeout to use when calling the function. Defaults to 70 seconds. @objc open var timeoutInterval: TimeInterval = 70 init(functions: Functions, name: String, options: HTTPSCallableOptions? = nil) { @@ -71,28 +65,27 @@ open class HTTPSCallable: NSObject { endpoint = .url(url) } - /** - * Executes this Callable HTTPS trigger asynchronously. - * - * The data passed into the trigger can be any of the following types: - * - `nil` or `NSNull` - * - `String` - * - `NSNumber`, or any Swift numeric type bridgeable to `NSNumber` - * - `[Any]`, where the contained objects are also one of these types. - * - `[String: Any]` where the values are also one of these types. - * - * The request to the Cloud Functions backend made by this method automatically includes a - * Firebase Installations ID token to identify the app instance. If a user is logged in with - * Firebase Auth, an auth ID token for the user is also automatically included. - * - * Firebase Cloud Messaging sends data to the Firebase backend periodically to collect information - * regarding the app instance. To stop this, see `Messaging.deleteData()`. It - * resumes with a new FCM Token the next time you call this method. - * - * - Parameters: - * - data: Parameters to pass to the trigger. - * - completion: The block to call when the HTTPS request has completed. - */ + /// Executes this Callable HTTPS trigger asynchronously. + /// + /// The data passed into the trigger can be any of the following types: + /// - `nil` or `NSNull` + /// - `String` + /// - `NSNumber`, or any Swift numeric type bridgeable to `NSNumber` + /// - `[Any]`, where the contained objects are also one of these types. + /// - `[String: Any]` where the values are also one of these types. + /// + /// The request to the Cloud Functions backend made by this method automatically includes a + /// Firebase Installations ID token to identify the app instance. If a user is logged in with + /// Firebase Auth, an auth ID token for the user is also automatically included. + /// + /// Firebase Cloud Messaging sends data to the Firebase backend periodically to collect + /// information + /// regarding the app instance. To stop this, see `Messaging.deleteData()`. It + /// resumes with a new FCM Token the next time you call this method. + /// + /// - Parameters: + /// - data: Parameters to pass to the trigger. + /// - completion: The block to call when the HTTPS request has completed. @objc(callWithObject:completion:) open func call(_ data: Any? = nil, completion: @escaping (HTTPSCallableResult?, Error?) -> Void) { @@ -121,39 +114,38 @@ open class HTTPSCallable: NSObject { } } - /** - * Executes this Callable HTTPS trigger asynchronously. This API should only be used from Objective-C. - * - * The request to the Cloud Functions backend made by this method automatically includes a - * Firebase Installations ID token to identify the app instance. If a user is logged in with - * Firebase Auth, an auth ID token for the user is also automatically included. - * - * Firebase Cloud Messaging sends data to the Firebase backend periodically to collect information - * regarding the app instance. To stop this, see `Messaging.deleteData()`. It - * resumes with a new FCM Token the next time you call this method. - * - * - Parameter completion The block to call when the HTTPS request has completed. - */ + /// Executes this Callable HTTPS trigger asynchronously. This API should only be used from + /// Objective-C. + /// + /// The request to the Cloud Functions backend made by this method automatically includes a + /// Firebase Installations ID token to identify the app instance. If a user is logged in with + /// Firebase Auth, an auth ID token for the user is also automatically included. + /// + /// Firebase Cloud Messaging sends data to the Firebase backend periodically to collect + /// information + /// regarding the app instance. To stop this, see `Messaging.deleteData()`. It + /// resumes with a new FCM Token the next time you call this method. + /// + /// - Parameter completion: The block to call when the HTTPS request has completed. @objc(callWithCompletion:) public func __call(completion: @escaping (HTTPSCallableResult?, Error?) -> Void) { call(nil, completion: completion) } - /** - * Executes this Callable HTTPS trigger asynchronously. - * - * The request to the Cloud Functions backend made by this method automatically includes a - * FCM token to identify the app instance. If a user is logged in with Firebase - * Auth, an auth ID token for the user is also automatically included. - * - * Firebase Cloud Messaging sends data to the Firebase backend periodically to collect information - * regarding the app instance. To stop this, see `Messaging.deleteData()`. It - * resumes with a new FCM Token the next time you call this method. - * - * - Parameter data Parameters to pass to the trigger. - * - Throws: An error if the Cloud Functions invocation failed. - * - Returns: The result of the call. - */ + /// Executes this Callable HTTPS trigger asynchronously. + /// + /// The request to the Cloud Functions backend made by this method automatically includes a + /// FCM token to identify the app instance. If a user is logged in with Firebase + /// Auth, an auth ID token for the user is also automatically included. + /// + /// Firebase Cloud Messaging sends data to the Firebase backend periodically to collect + /// information + /// regarding the app instance. To stop this, see `Messaging.deleteData()`. It + /// resumes with a new FCM Token the next time you call this method. + /// + /// - Parameter data: Parameters to pass to the trigger. + /// - Throws: An error if the Cloud Functions invocation failed. + /// - Returns: The result of the call. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) open func call(_ data: Any? = nil) async throws -> HTTPSCallableResult { return try await withCheckedThrowingContinuation { continuation in diff --git a/FirebaseInAppMessaging.podspec b/FirebaseInAppMessaging.podspec index 6bce213aba1..f0122b8ec7c 100644 --- a/FirebaseInAppMessaging.podspec +++ b/FirebaseInAppMessaging.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseInAppMessaging' - s.version = '10.21.0-beta' + s.version = '10.23.0-beta' s.summary = 'Firebase In-App Messaging for iOS' s.description = <<-DESC @@ -22,7 +22,7 @@ See more product details at https://firebase.google.com/products/in-app-messagin s.swift_version = '5.3' - s.cocoapods_version = '>= 1.4.0' + s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false base_dir = "FirebaseInAppMessaging/" @@ -84,7 +84,7 @@ See more product details at https://firebase.google.com/products/in-app-messagin s.dependency 'FirebaseInstallations', '~> 10.0' s.dependency 'FirebaseABTesting', '~> 10.0' s.dependency 'GoogleUtilities/Environment', '~> 7.8' - s.dependency 'nanopb', '>= 2.30908.0', '< 2.30910.0' + s.dependency 'nanopb', '>= 2.30908.0', '< 2.30911.0' s.test_spec 'unit' do |unit_tests| unit_tests.scheme = { :code_coverage => true } diff --git a/FirebaseInAppMessaging/CHANGELOG.md b/FirebaseInAppMessaging/CHANGELOG.md index e8825c0ee6a..82a46fddf1e 100644 --- a/FirebaseInAppMessaging/CHANGELOG.md +++ b/FirebaseInAppMessaging/CHANGELOG.md @@ -1,3 +1,6 @@ +# 10.22.0 +- [fixed] Fixed an `objc_retain` crash. (#12393) + # 10.17.0 - [deprecated] All of the public API from `FirebaseInAppMessagingSwift` can now be accessed through the `FirebaseInAppMessaging` module. Therefore, diff --git a/FirebaseInAppMessaging/Sources/Flows/FIRIAMMessageClientCache.m b/FirebaseInAppMessaging/Sources/Flows/FIRIAMMessageClientCache.m index 7c4e4bcc3e1..37f24531edd 100644 --- a/FirebaseInAppMessaging/Sources/Flows/FIRIAMMessageClientCache.m +++ b/FirebaseInAppMessaging/Sources/Flows/FIRIAMMessageClientCache.m @@ -126,7 +126,9 @@ - (void)setupAnalyticsEventListening { } - (BOOL)hasTestMessage { - return self.testMessages.count > 0; + @synchronized(self) { + return self.testMessages.count > 0; + } } - (nullable FIRIAMMessageDefinition *)nextOnAppLaunchDisplayMsg { @@ -135,7 +137,7 @@ - (nullable FIRIAMMessageDefinition *)nextOnAppLaunchDisplayMsg { - (nullable FIRIAMMessageDefinition *)nextOnAppOpenDisplayMsg { @synchronized(self) { - // always first check test message which always have higher prirority + // always first check test message which always have higher priority if (self.testMessages.count > 0) { FIRIAMMessageDefinition *testMessage = self.testMessages[0]; // always remove test message right away when being fetched for display diff --git a/FirebaseInAppMessagingSwift.podspec b/FirebaseInAppMessagingSwift.podspec index 0a1f2e049c0..02e031b4456 100644 --- a/FirebaseInAppMessagingSwift.podspec +++ b/FirebaseInAppMessagingSwift.podspec @@ -20,7 +20,7 @@ See more product details at https://firebase.google.com/products/in-app-messagin s.swift_version = '5.3' s.ios.deployment_target = '13.0' - s.cocoapods_version = '>= 1.4.0' + s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false s.source_files = [ diff --git a/FirebaseInstallations.podspec b/FirebaseInstallations.podspec index 538e23fe81f..e8cb5c352b8 100644 --- a/FirebaseInstallations.podspec +++ b/FirebaseInstallations.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseInstallations' - s.version = '10.21.0' + s.version = '10.23.0' s.summary = 'Firebase Installations' s.description = <<-DESC @@ -29,7 +29,7 @@ Pod::Spec.new do |s| s.tvos.deployment_target = tvos_deployment_target s.watchos.deployment_target = watchos_deployment_target - s.cocoapods_version = '>= 1.4.0' + s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false base_dir = "FirebaseInstallations/Source/" @@ -40,6 +40,9 @@ Pod::Spec.new do |s| s.public_header_files = [ base_dir + 'Library/Public/FirebaseInstallations/*.h', ] + s.resource_bundles = { + "#{s.module_name}_Privacy" => 'FirebaseInstallations/Source/Library/Resources/PrivacyInfo.xcprivacy' + } s.framework = 'Security' s.dependency 'FirebaseCore', '~> 10.0' diff --git a/FirebaseInstallations/Source/Library/Resources/PrivacyInfo.xcprivacy b/FirebaseInstallations/Source/Library/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 00000000000..1e83fa67acd --- /dev/null +++ b/FirebaseInstallations/Source/Library/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,30 @@ + + + + + NSPrivacyTracking + + NSPrivacyTrackingDomains + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeOtherDiagnosticData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAnalytics + + + + NSPrivacyAccessedAPITypes + + + + + diff --git a/FirebaseMLModelDownloader.podspec b/FirebaseMLModelDownloader.podspec index c14e1851e43..7d17f6a05ac 100644 --- a/FirebaseMLModelDownloader.podspec +++ b/FirebaseMLModelDownloader.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseMLModelDownloader' - s.version = '10.21.0-beta' + s.version = '10.23.0-beta' s.summary = 'Firebase ML Model Downloader' s.description = <<-DESC @@ -28,7 +28,7 @@ Pod::Spec.new do |s| s.tvos.deployment_target = tvos_deployment_target s.watchos.deployment_target = watchos_deployment_target - s.cocoapods_version = '>= 1.4.0' + s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false s.source_files = [ diff --git a/FirebaseMessaging.podspec b/FirebaseMessaging.podspec index f6c0393c7bc..6e3cbcde4e6 100644 --- a/FirebaseMessaging.podspec +++ b/FirebaseMessaging.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseMessaging' - s.version = '10.21.0' + s.version = '10.23.0' s.summary = 'Firebase Messaging' s.description = <<-DESC @@ -32,12 +32,12 @@ device, and it is completely free. s.tvos.deployment_target = tvos_deployment_target s.watchos.deployment_target = watchos_deployment_target - s.cocoapods_version = '>= 1.4.0' + s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false base_dir = "FirebaseMessaging/" s.source_files = [ - base_dir + 'Sources/**/*', + base_dir + 'Sources/**/*.{c,m,h}', base_dir + 'Sources/Protogen/nanopb/*.h', base_dir + 'Interop/*.h', 'Interop/Analytics/Public/*.h', @@ -45,6 +45,9 @@ device, and it is completely free. 'FirebaseInstallations/Source/Library/Private/*.h', ] s.public_header_files = base_dir + 'Sources/Public/FirebaseMessaging/*.h' + s.resource_bundles = { + "#{s.module_name}_Privacy" => 'FirebaseMessaging/Sources/Resources/PrivacyInfo.xcprivacy' + } s.library = 'sqlite3' s.pod_target_xcconfig = { 'GCC_C_LANGUAGE_STANDARD' => 'c99', @@ -65,7 +68,7 @@ device, and it is completely free. s.dependency 'GoogleUtilities/Environment', '~> 7.8' s.dependency 'GoogleUtilities/UserDefaults', '~> 7.8' s.dependency 'GoogleDataTransport', '~> 9.3' - s.dependency 'nanopb', '>= 2.30908.0', '< 2.30910.0' + s.dependency 'nanopb', '>= 2.30908.0', '< 2.30911.0' s.test_spec 'unit' do |unit_tests| unit_tests.scheme = { :code_coverage => true } diff --git a/FirebaseMessaging/CHANGELOG.md b/FirebaseMessaging/CHANGELOG.md index e51c663ce33..8191c09c6b3 100644 --- a/FirebaseMessaging/CHANGELOG.md +++ b/FirebaseMessaging/CHANGELOG.md @@ -1,3 +1,6 @@ +# 10.23.0 +- [fixed] [CocoaPods] Fix "no rule" warning when running `pod install`. (#12511) + # 10.20.0 - [fixed] Fix 10.19.0 regression where the FCM registration token was nil at first app start after update from 10.19.0 or earlier. (#12245) diff --git a/FirebaseMessaging/Sources/FIRMessagingSyncMessageManager.m b/FirebaseMessaging/Sources/FIRMessagingSyncMessageManager.m index 69a6bb028c1..925f701778d 100644 --- a/FirebaseMessaging/Sources/FIRMessagingSyncMessageManager.m +++ b/FirebaseMessaging/Sources/FIRMessagingSyncMessageManager.m @@ -24,8 +24,6 @@ #import "FirebaseMessaging/Sources/FIRMessagingUtilities.h" static const int64_t kDefaultSyncMessageTTL = 4 * 7 * 24 * 60 * 60; // 4 weeks -// 4 MB of free space is required to persist Sync messages -static const uint64_t kMinFreeDiskSpaceInMB = 1; @interface FIRMessagingSyncMessageManager () @@ -63,12 +61,6 @@ - (BOOL)didReceiveAPNSSyncMessage:(NSDictionary *)message { [self.rmqManager querySyncMessageWithRmqID:rmqID]; if (!persistentMessage) { - // Do not persist the new message if we don't have enough disk space - uint64_t freeDiskSpace = FIRMessagingGetFreeDiskSpaceInMB(); - if (freeDiskSpace < kMinFreeDiskSpaceInMB) { - return NO; - } - int64_t expirationTime = [[self class] expirationTimeForSyncMessage:message]; [self.rmqManager saveSyncMessageWithRmqID:rmqID expirationTime:expirationTime]; return NO; diff --git a/FirebaseMessaging/Sources/FIRMessagingUtilities.h b/FirebaseMessaging/Sources/FIRMessagingUtilities.h index d55040ca096..f2b10d9f06d 100644 --- a/FirebaseMessaging/Sources/FIRMessagingUtilities.h +++ b/FirebaseMessaging/Sources/FIRMessagingUtilities.h @@ -34,7 +34,6 @@ FOUNDATION_EXPORT BOOL FIRMessagingIsWatchKitExtension(void); #pragma mark - Others -FOUNDATION_EXPORT uint64_t FIRMessagingGetFreeDiskSpaceInMB(void); FOUNDATION_EXPORT NSSearchPathDirectory FIRMessagingSupportedDirectory(void); #pragma mark - Device Info diff --git a/FirebaseMessaging/Sources/FIRMessagingUtilities.m b/FirebaseMessaging/Sources/FIRMessagingUtilities.m index eea771f9942..713febdb833 100644 --- a/FirebaseMessaging/Sources/FIRMessagingUtilities.m +++ b/FirebaseMessaging/Sources/FIRMessagingUtilities.m @@ -21,7 +21,6 @@ #import "FirebaseCore/Extension/FirebaseCoreInternal.h" #import "FirebaseMessaging/Sources/FIRMessagingLogger.h" -static const uint64_t kBytesToMegabytesDivisor = 1024 * 1024LL; NSString *const kFIRMessagingInstanceIDUserDefaultsKeyLocale = @"com.firebase.instanceid.user_defaults.locale"; // locale key stored in GULUserDefaults static NSString *const kFIRMessagingAPNSSandboxPrefix = @"s_"; @@ -114,28 +113,6 @@ BOOL FIRMessagingIsWatchKitExtension(void) { #endif } -uint64_t FIRMessagingGetFreeDiskSpaceInMB(void) { - NSError *error; - NSArray *paths = - NSSearchPathForDirectoriesInDomains(FIRMessagingSupportedDirectory(), NSUserDomainMask, YES); - - NSDictionary *attributesMap = - [[NSFileManager defaultManager] attributesOfFileSystemForPath:[paths lastObject] - error:&error]; - if (attributesMap) { - uint64_t totalSizeInBytes __unused = [attributesMap[NSFileSystemSize] longLongValue]; - uint64_t freeSizeInBytes = [attributesMap[NSFileSystemFreeSize] longLongValue]; - FIRMessagingLoggerDebug( - kFIRMessagingMessageCodeUtilities001, @"Device has capacity %llu MB with %llu MB free.", - totalSizeInBytes / kBytesToMegabytesDivisor, freeSizeInBytes / kBytesToMegabytesDivisor); - return ((double)freeSizeInBytes) / kBytesToMegabytesDivisor; - } else { - FIRMessagingLoggerError(kFIRMessagingMessageCodeUtilities002, - @"Error in retreiving device's free memory %@", error); - return 0; - } -} - NSSearchPathDirectory FIRMessagingSupportedDirectory(void) { #if TARGET_OS_TV return NSCachesDirectory; diff --git a/FirebaseMessaging/Sources/Resources/PrivacyInfo.xcprivacy b/FirebaseMessaging/Sources/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 00000000000..3b29f79eeaf --- /dev/null +++ b/FirebaseMessaging/Sources/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,54 @@ + + + + + NSPrivacyTracking + + NSPrivacyTrackingDomains + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeDeviceID + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeOtherDataTypes + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAnalytics + + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeOtherDiagnosticData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAppFunctionality + + + + NSPrivacyAccessedAPITypes + + + + + diff --git a/FirebaseMessagingInterop.podspec b/FirebaseMessagingInterop.podspec index 414a2a8ea37..6e8989948cd 100644 --- a/FirebaseMessagingInterop.podspec +++ b/FirebaseMessagingInterop.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseMessagingInterop' - s.version = '10.21.0' + s.version = '10.23.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 0c1a877a595..c8d89bb17f5 100644 --- a/FirebasePerformance.podspec +++ b/FirebasePerformance.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebasePerformance' - s.version = '10.21.0' + s.version = '10.23.0' s.summary = 'Firebase Performance' s.description = <<-DESC @@ -25,7 +25,7 @@ Firebase Performance library to measure performance of Mobile and Web Apps. s.ios.deployment_target = ios_deployment_target s.tvos.deployment_target = tvos_deployment_target - s.cocoapods_version = '>= 1.4.0' + s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false base_dir = "FirebasePerformance/" @@ -67,7 +67,7 @@ Firebase Performance library to measure performance of Mobile and Web Apps. s.dependency 'GoogleUtilities/Environment', '~> 7.8' s.dependency 'GoogleUtilities/ISASwizzler', '~> 7.8' s.dependency 'GoogleUtilities/MethodSwizzler', '~> 7.8' - s.dependency 'nanopb', '>= 2.30908.0', '< 2.30910.0' + s.dependency 'nanopb', '>= 2.30908.0', '< 2.30911.0' s.test_spec 'unit' do |unit_tests| unit_tests.platforms = {:ios => ios_deployment_target, :tvos => tvos_deployment_target} diff --git a/FirebaseRemoteConfig.podspec b/FirebaseRemoteConfig.podspec index b5dc61b9925..9767a218a60 100644 --- a/FirebaseRemoteConfig.podspec +++ b/FirebaseRemoteConfig.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseRemoteConfig' - s.version = '10.21.0' + s.version = '10.23.0' s.summary = 'Firebase Remote Config' s.description = <<-DESC @@ -30,7 +30,7 @@ app update. s.tvos.deployment_target = tvos_deployment_target s.watchos.deployment_target = watchos_deployment_target - s.cocoapods_version = '>= 1.4.0' + s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false base_dir = "FirebaseRemoteConfig/Sources/" @@ -43,6 +43,9 @@ app update. 'FirebaseRemoteConfig/Swift/**/*.swift', ] s.public_header_files = base_dir + 'Public/FirebaseRemoteConfig/*.h' + s.resource_bundles = { + "#{s.module_name}_Privacy" => 'FirebaseRemoteConfig/Swift/Resources/PrivacyInfo.xcprivacy' + } s.pod_target_xcconfig = { 'GCC_C_LANGUAGE_STANDARD' => 'c99', 'HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}"' @@ -53,6 +56,7 @@ app update. s.dependency 'FirebaseInstallations', '~> 10.0' s.dependency 'GoogleUtilities/Environment', '~> 7.8' s.dependency 'GoogleUtilities/NSData+zlib', '~> 7.8' + s.dependency 'FirebaseRemoteConfigInterop', '~> 10.23' s.test_spec 'unit' do |unit_tests| unit_tests.scheme = { :code_coverage => true } @@ -77,7 +81,8 @@ app update. 'FirebaseRemoteConfig/Tests/Unit/RCNTestUtilities.m', 'FirebaseRemoteConfig/Tests/Unit/RCNUserDefaultsManagerTests.m', 'FirebaseRemoteConfig/Tests/Unit/RCNTestUtilities.h', - 'FirebaseRemoteConfig/Tests/Unit/RCNInstanceIDTest.m' + 'FirebaseRemoteConfig/Tests/Unit/RCNInstanceIDTest.m', + 'FirebaseRemoteConfig/Tests/SwiftUnit/*.swift' # Supply plist custom plist testing. unit_tests.resources = 'FirebaseRemoteConfig/Tests/Unit/Defaults-testInfo.plist', diff --git a/FirebaseRemoteConfig/CHANGELOG.md b/FirebaseRemoteConfig/CHANGELOG.md index 47e9aff902e..b2ba32e782c 100644 --- a/FirebaseRemoteConfig/CHANGELOG.md +++ b/FirebaseRemoteConfig/CHANGELOG.md @@ -1,3 +1,6 @@ +# 10.23.0 +- [changed] Add support for other Firebase products to integrate with Remote Config. + # 10.17.0 - [feature] The `FirebaseRemoteConfig` module now contains Firebase Remote Config's Swift-only APIs that were previously only available via the diff --git a/FirebaseRemoteConfig/Interop/RemoteConfigConstants.swift b/FirebaseRemoteConfig/Interop/RemoteConfigConstants.swift new file mode 100644 index 00000000000..f9a10e409b7 --- /dev/null +++ b/FirebaseRemoteConfig/Interop/RemoteConfigConstants.swift @@ -0,0 +1,21 @@ +// 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. + +import Foundation + +@objc(FIRRemoteConfigConstants) +public final class RemoteConfigConstants: NSObject { + @objc(FIRNamespaceGoogleMobilePlatform) public static let NamespaceGoogleMobilePlatform = + "firebase" +} diff --git a/FirebaseRemoteConfig/Interop/RemoteConfigInterop.swift b/FirebaseRemoteConfig/Interop/RemoteConfigInterop.swift new file mode 100644 index 00000000000..b7988efa389 --- /dev/null +++ b/FirebaseRemoteConfig/Interop/RemoteConfigInterop.swift @@ -0,0 +1,21 @@ +// Copyright 2023 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 + +@objc(FIRRemoteConfigInterop) +public protocol RemoteConfigInterop { + func registerRolloutsStateSubscriber(_ subscriber: RolloutsStateSubscriber, + for namespace: String) +} diff --git a/FirebaseRemoteConfig/Interop/RolloutAssignment.swift b/FirebaseRemoteConfig/Interop/RolloutAssignment.swift new file mode 100644 index 00000000000..715412bb4f1 --- /dev/null +++ b/FirebaseRemoteConfig/Interop/RolloutAssignment.swift @@ -0,0 +1,47 @@ +// Copyright 2023 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 + +@objc(FIRRolloutAssignment) +public class RolloutAssignment: NSObject { + @objc public var rolloutId: String + @objc public var variantId: String + @objc public var templateVersion: Int64 + @objc public var parameterKey: String + @objc public var parameterValue: String + + @objc public init(rolloutId: String, variantId: String, templateVersion: Int64, + parameterKey: String, + parameterValue: String) { + self.rolloutId = rolloutId + self.variantId = variantId + self.templateVersion = templateVersion + self.parameterKey = parameterKey + self.parameterValue = parameterValue + super.init() + } +} + +@objc(FIRRolloutsState) +public class RolloutsState: NSObject { + @objc public var assignments: Set = Set() + + @objc public init(assignmentList: [RolloutAssignment]) { + for assignment in assignmentList { + assignments.insert(assignment) + } + super.init() + } +} diff --git a/FirebaseRemoteConfig/Interop/RolloutsStateSubscriber.swift b/FirebaseRemoteConfig/Interop/RolloutsStateSubscriber.swift new file mode 100644 index 00000000000..88e5ba8772d --- /dev/null +++ b/FirebaseRemoteConfig/Interop/RolloutsStateSubscriber.swift @@ -0,0 +1,20 @@ +// Copyright 2023 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 + +@objc(FIRRolloutsStateSubscriber) +public protocol RolloutsStateSubscriber { + func rolloutsStateDidChange(_ rolloutsState: RolloutsState) +} diff --git a/FirebaseRemoteConfig/Sources/FIRRemoteConfig.m b/FirebaseRemoteConfig/Sources/FIRRemoteConfig.m index 4035d558707..561ada50693 100644 --- a/FirebaseRemoteConfig/Sources/FIRRemoteConfig.m +++ b/FirebaseRemoteConfig/Sources/FIRRemoteConfig.m @@ -45,6 +45,8 @@ /// Notification when config is successfully activated const NSNotificationName FIRRemoteConfigActivateNotification = @"FIRRemoteConfigActivateNotification"; +static NSNotificationName FIRRolloutsStateDidChangeNotificationName = + @"FIRRolloutsStateDidChangeNotification"; /// Listener for the get methods. typedef void (^FIRRemoteConfigListener)(NSString *_Nonnull, NSDictionary *_Nonnull); @@ -79,8 +81,9 @@ @implementation FIRRemoteConfig { *RCInstances; + (nonnull FIRRemoteConfig *)remoteConfigWithApp:(FIRApp *_Nonnull)firebaseApp { - return [FIRRemoteConfig remoteConfigWithFIRNamespace:FIRNamespaceGoogleMobilePlatform - app:firebaseApp]; + return [FIRRemoteConfig + remoteConfigWithFIRNamespace:FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform + app:firebaseApp]; } + (nonnull FIRRemoteConfig *)remoteConfigWithFIRNamespace:(NSString *_Nonnull)firebaseNamespace { @@ -116,8 +119,9 @@ + (FIRRemoteConfig *)remoteConfig { @"initializer in SwiftUI."]; } - return [FIRRemoteConfig remoteConfigWithFIRNamespace:FIRNamespaceGoogleMobilePlatform - app:[FIRApp defaultApp]]; + return [FIRRemoteConfig + remoteConfigWithFIRNamespace:FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform + app:[FIRApp defaultApp]]; } /// Singleton instance of serial queue for queuing all incoming RC calls. @@ -329,10 +333,20 @@ - (void)activateWithCompletion:(FIRRemoteConfigActivateChangeCompletion)completi // New config has been activated at this point FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000069", @"Config activated."); [strongSelf->_configContent activatePersonalization]; + // Update last active template version number in setting and userDefaults. + [strongSelf->_settings updateLastActiveTemplateVersion]; + // Update activeRolloutMetadata + [strongSelf->_configContent activateRolloutMetadata:^(BOOL success) { + if (success) { + [self notifyRolloutsStateChange:strongSelf->_configContent.activeRolloutMetadata + versionNumber:strongSelf->_settings.lastActiveTemplateVersion]; + } + }]; + // Update experiments only for 3p namespace NSString *namespace = [strongSelf->_FIRNamespace substringToIndex:[strongSelf->_FIRNamespace rangeOfString:@":"].location]; - if ([namespace isEqualToString:FIRNamespaceGoogleMobilePlatform]) { + if ([namespace isEqualToString:FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform]) { dispatch_async(dispatch_get_main_queue(), ^{ [self notifyConfigHasActivated]; }); @@ -377,6 +391,17 @@ - (NSString *)fullyQualifiedNamespace:(NSString *)namespace { return fullyQualifiedNamespace; } +- (FIRRemoteConfigValue *)defaultValueForFullyQualifiedNamespace:(NSString *)namespace + key:(NSString *)key { + FIRRemoteConfigValue *value = self->_configContent.defaultConfig[namespace][key]; + if (!value) { + value = [[FIRRemoteConfigValue alloc] + initWithData:[NSData data] + source:(FIRRemoteConfigSource)FIRRemoteConfigSourceStatic]; + } + return value; +} + #pragma mark - Get Config Result - (FIRRemoteConfigValue *)objectForKeyedSubscript:(NSString *)key { @@ -402,13 +427,7 @@ - (FIRRemoteConfigValue *)configValueForKey:(NSString *)key { config:[self->_configContent getConfigAndMetadataForNamespace:FQNamespace]]; return; } - value = self->_configContent.defaultConfig[FQNamespace][key]; - if (value) { - return; - } - - value = [[FIRRemoteConfigValue alloc] initWithData:[NSData data] - source:FIRRemoteConfigSourceStatic]; + value = [self defaultValueForFullyQualifiedNamespace:FQNamespace key:key]; }); return value; } @@ -613,4 +632,67 @@ - (FIRConfigUpdateListenerRegistration *)addOnConfigUpdateListener: return [self->_configRealtime addConfigUpdateListener:listener]; } +#pragma mark - Rollout + +- (void)addRemoteConfigInteropSubscriber:(id)subscriber { + [[NSNotificationCenter defaultCenter] + addObserverForName:FIRRolloutsStateDidChangeNotificationName + object:self + queue:nil + usingBlock:^(NSNotification *_Nonnull notification) { + FIRRolloutsState *rolloutsState = + notification.userInfo[FIRRolloutsStateDidChangeNotificationName]; + [subscriber rolloutsStateDidChange:rolloutsState]; + }]; + // Send active rollout metadata stored in persistence while app launched if there is activeConfig + NSString *fullyQualifiedNamespace = [self fullyQualifiedNamespace:_FIRNamespace]; + NSDictionary *activeConfig = self->_configContent.activeConfig; + if (activeConfig[fullyQualifiedNamespace] && activeConfig[fullyQualifiedNamespace].count > 0) { + [self notifyRolloutsStateChange:self->_configContent.activeRolloutMetadata + versionNumber:self->_settings.lastActiveTemplateVersion]; + } +} + +- (void)notifyRolloutsStateChange:(NSArray *)rolloutMetadata + versionNumber:(NSString *)versionNumber { + NSArray *rolloutsAssignments = + [self rolloutsAssignmentsWith:rolloutMetadata versionNumber:versionNumber]; + FIRRolloutsState *rolloutsState = + [[FIRRolloutsState alloc] initWithAssignmentList:rolloutsAssignments]; + FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000069", + @"Send rollouts state notification with name %@ to RemoteConfigInterop.", + FIRRolloutsStateDidChangeNotificationName); + [[NSNotificationCenter defaultCenter] + postNotificationName:FIRRolloutsStateDidChangeNotificationName + object:self + userInfo:@{FIRRolloutsStateDidChangeNotificationName : rolloutsState}]; +} + +- (NSArray *)rolloutsAssignmentsWith: + (NSArray *)rolloutMetadata + versionNumber:(NSString *)versionNumber { + NSMutableArray *rolloutsAssignments = [[NSMutableArray alloc] init]; + NSString *FQNamespace = [self fullyQualifiedNamespace:_FIRNamespace]; + for (NSDictionary *metadata in rolloutMetadata) { + NSString *rolloutId = metadata[RCNFetchResponseKeyRolloutID]; + NSString *variantID = metadata[RCNFetchResponseKeyVariantID]; + NSArray *affectedParameterKeys = metadata[RCNFetchResponseKeyAffectedParameterKeys]; + if (rolloutId && variantID && affectedParameterKeys) { + for (NSString *key in affectedParameterKeys) { + FIRRemoteConfigValue *value = self->_configContent.activeConfig[FQNamespace][key]; + if (!value) { + value = [self defaultValueForFullyQualifiedNamespace:FQNamespace key:key]; + } + FIRRolloutAssignment *assignment = + [[FIRRolloutAssignment alloc] initWithRolloutId:rolloutId + variantId:variantID + templateVersion:[versionNumber longLongValue] + parameterKey:key + parameterValue:value.stringValue]; + [rolloutsAssignments addObject:assignment]; + } + } + } + return rolloutsAssignments; +} @end diff --git a/FirebaseRemoteConfig/Sources/FIRRemoteConfigComponent.h b/FirebaseRemoteConfig/Sources/FIRRemoteConfigComponent.h index f015ea14974..e8dda531a01 100644 --- a/FirebaseRemoteConfig/Sources/FIRRemoteConfigComponent.h +++ b/FirebaseRemoteConfig/Sources/FIRRemoteConfigComponent.h @@ -17,6 +17,7 @@ #import #import "FirebaseCore/Extension/FirebaseCoreInternal.h" +@import FirebaseRemoteConfigInterop; @class FIRApp; @class FIRRemoteConfig; @@ -37,7 +38,8 @@ NS_ASSUME_NONNULL_BEGIN /// A concrete implementation for FIRRemoteConfigInterop to create Remote Config instances and /// register with Core's component system. -@interface FIRRemoteConfigComponent : NSObject +@interface FIRRemoteConfigComponent + : NSObject /// The FIRApp that instances will be set up with. @property(nonatomic, weak, readonly) FIRApp *app; @@ -45,6 +47,10 @@ NS_ASSUME_NONNULL_BEGIN /// Cached instances of Remote Config objects. @property(nonatomic, strong) NSMutableDictionary *instances; +/// Clear all the component instances from the singleton which created previously, this is for +/// testing only ++ (void)clearAllComponentInstances; + /// Default method for retrieving a Remote Config instance, or creating one if it doesn't exist. - (FIRRemoteConfig *)remoteConfigForNamespace:(NSString *)remoteConfigNamespace; diff --git a/FirebaseRemoteConfig/Sources/FIRRemoteConfigComponent.m b/FirebaseRemoteConfig/Sources/FIRRemoteConfigComponent.m index e0adc7bccbf..81055451ae4 100644 --- a/FirebaseRemoteConfig/Sources/FIRRemoteConfigComponent.m +++ b/FirebaseRemoteConfig/Sources/FIRRemoteConfigComponent.m @@ -24,6 +24,31 @@ @implementation FIRRemoteConfigComponent +// Because Component now need to register two protocols (provider and interop), we need a way to +// return the same component instance for both registered protocol, this singleton pattern allow us +// to return the same component object for both registration callback. +static NSMutableDictionary *_componentInstances = nil; + ++ (FIRRemoteConfigComponent *)getComponentForApp:(FIRApp *)app { + @synchronized(_componentInstances) { + // need to init the dictionary first + if (!_componentInstances) { + _componentInstances = [[NSMutableDictionary alloc] init]; + } + if (![_componentInstances objectForKey:app.name]) { + _componentInstances[app.name] = [[self alloc] initWithApp:app]; + } + return _componentInstances[app.name]; + } + return nil; +} + ++ (void)clearAllComponentInstances { + @synchronized(_componentInstances) { + [_componentInstances removeAllObjects]; + } +} + /// Default method for retrieving a Remote Config instance, or creating one if it doesn't exist. - (FIRRemoteConfig *)remoteConfigForNamespace:(NSString *)remoteConfigNamespace { if (!remoteConfigNamespace) { @@ -102,9 +127,29 @@ + (void)load { creationBlock:^id _Nullable(FIRComponentContainer *container, BOOL *isCacheable) { // Cache the component so instances of Remote Config are cached. *isCacheable = YES; - return [[FIRRemoteConfigComponent alloc] initWithApp:container.app]; + return [FIRRemoteConfigComponent getComponentForApp:container.app]; + }]; + + // Unlike provider needs to setup a hard dependency on remote config, interop allows an optional + // dependency on RC + FIRComponent *rcInterop = [FIRComponent + componentWithProtocol:@protocol(FIRRemoteConfigInterop) + instantiationTiming:FIRInstantiationTimingAlwaysEager + dependencies:@[] + creationBlock:^id _Nullable(FIRComponentContainer *container, BOOL *isCacheable) { + // Cache the component so instances of Remote Config are cached. + *isCacheable = YES; + return [FIRRemoteConfigComponent getComponentForApp:container.app]; }]; - return @[ rcProvider ]; + return @[ rcProvider, rcInterop ]; +} + +#pragma mark - Remote Config Interop Protocol + +- (void)registerRolloutsStateSubscriber:(id)subscriber + for:(NSString * _Nonnull)namespace { + FIRRemoteConfig *instance = [self remoteConfigForNamespace:namespace]; + [instance addRemoteConfigInteropSubscriber:subscriber]; } @end diff --git a/FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h b/FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h index ef7def6fd9d..4420dcb2679 100644 --- a/FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h +++ b/FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h @@ -23,6 +23,7 @@ @class RCNConfigFetch; @class RCNConfigRealtime; @protocol FIRAnalyticsInterop; +@protocol FIRRolloutsStateSubscriber; NS_ASSUME_NONNULL_BEGIN @@ -78,6 +79,9 @@ NS_ASSUME_NONNULL_BEGIN configContent:(RCNConfigContent *)configContent analytics:(nullable id)analytics; +/// Register RolloutsStateSubcriber to FIRRemoteConfig instance +- (void)addRemoteConfigInteropSubscriber:(id _Nonnull)subscriber; + @end NS_ASSUME_NONNULL_END diff --git a/FirebaseRemoteConfig/Sources/Private/RCNConfigSettings.h b/FirebaseRemoteConfig/Sources/Private/RCNConfigSettings.h index 987f3a98225..36fb8e7435f 100644 --- a/FirebaseRemoteConfig/Sources/Private/RCNConfigSettings.h +++ b/FirebaseRemoteConfig/Sources/Private/RCNConfigSettings.h @@ -79,8 +79,10 @@ @property(nonatomic, readwrite, assign) NSString *lastETag; /// The timestamp of the last eTag update. @property(nonatomic, readwrite, assign) NSTimeInterval lastETagUpdateTime; -// Last fetched template version. -@property(nonatomic, readwrite, assign) NSString *lastTemplateVersion; +/// Last fetched template version. +@property(nonatomic, readwrite, assign) NSString *lastFetchedTemplateVersion; +/// Last active template version. +@property(nonatomic, readwrite, assign) NSString *lastActiveTemplateVersion; #pragma mark Throttling properties @@ -134,6 +136,9 @@ /// indicates a server issue. - (void)updateRealtimeExponentialBackoffTime; +/// Update last active template version from last fetched template version. +- (void)updateLastActiveTemplateVersion; + /// Returns the difference between the Realtime backoff end time and the current time in a /// NSTimeInterval format. - (NSTimeInterval)getRealtimeBackoffInterval; diff --git a/FirebaseRemoteConfig/Sources/RCNConfigConstants.h b/FirebaseRemoteConfig/Sources/RCNConfigConstants.h index db0e0213ae1..51d248c4106 100644 --- a/FirebaseRemoteConfig/Sources/RCNConfigConstants.h +++ b/FirebaseRemoteConfig/Sources/RCNConfigConstants.h @@ -37,6 +37,14 @@ static NSString *const RCNFetchResponseKeyEntries = @"entries"; static NSString *const RCNFetchResponseKeyExperimentDescriptions = @"experimentDescriptions"; /// Key that includes data for Personalization metadata. static NSString *const RCNFetchResponseKeyPersonalizationMetadata = @"personalizationMetadata"; +/// Key that includes data for Rollout metadata. +static NSString *const RCNFetchResponseKeyRolloutMetadata = @"rolloutMetadata"; +/// Key that indicates rollout id in Rollout metadata. +static NSString *const RCNFetchResponseKeyRolloutID = @"rolloutId"; +/// Key that indicates variant id in Rollout metadata. +static NSString *const RCNFetchResponseKeyVariantID = @"variantId"; +/// Key that indicates affected parameter keys in Rollout Metadata. +static NSString *const RCNFetchResponseKeyAffectedParameterKeys = @"affectedParameterKeys"; /// Error key. static NSString *const RCNFetchResponseKeyError = @"error"; /// Error code. @@ -58,5 +66,7 @@ static NSString *const RCNFetchResponseKeyStateNoTemplate = @"NO_TEMPLATE"; static NSString *const RCNFetchResponseKeyStateNoChange = @"NO_CHANGE"; /// Template found, but evaluates to empty (e.g. all keys omitted). static NSString *const RCNFetchResponseKeyStateEmptyConfig = @"EMPTY_CONFIG"; -/// Template Version key +/// Fetched Template Version key static NSString *const RCNFetchResponseKeyTemplateVersion = @"templateVersion"; +/// Active Template Version key +static NSString *const RCNActiveKeyTemplateVersion = @"activeTemplateVersion"; diff --git a/FirebaseRemoteConfig/Sources/RCNConfigContent.h b/FirebaseRemoteConfig/Sources/RCNConfigContent.h index 34d0895243a..e8410074b30 100644 --- a/FirebaseRemoteConfig/Sources/RCNConfigContent.h +++ b/FirebaseRemoteConfig/Sources/RCNConfigContent.h @@ -39,6 +39,8 @@ typedef NS_ENUM(NSInteger, RCNDBSource) { @property(nonatomic, readonly, copy) NSDictionary *activeConfig; /// Local default config that is provided by external users; @property(nonatomic, readonly, copy) NSDictionary *defaultConfig; +/// Active Rollout metadata that is currently used. +@property(nonatomic, readonly, copy) NSArray *activeRolloutMetadata; - (instancetype)init NS_UNAVAILABLE; @@ -65,6 +67,9 @@ typedef NS_ENUM(NSInteger, RCNDBSource) { /// Gets the active config and Personalization metadata. - (NSDictionary *)getConfigAndMetadataForNamespace:(NSString *)FIRNamespace; +/// Sets the fetched rollout metadata to active with a success completion handler. +- (void)activateRolloutMetadata:(void (^)(BOOL success))completionHandler; + /// Returns the updated parameters between fetched and active config. - (FIRRemoteConfigUpdate *)getConfigUpdateForNamespace:(NSString *)FIRNamespace; diff --git a/FirebaseRemoteConfig/Sources/RCNConfigContent.m b/FirebaseRemoteConfig/Sources/RCNConfigContent.m index 4f55a2e9274..1c266734c40 100644 --- a/FirebaseRemoteConfig/Sources/RCNConfigContent.m +++ b/FirebaseRemoteConfig/Sources/RCNConfigContent.m @@ -38,6 +38,10 @@ @implementation RCNConfigContent { /// Pending Personalization metadata that is latest data from server that might or might not be /// applied. NSDictionary *_fetchedPersonalization; + /// Active Rollout metadata that is currently used. + NSArray *_activeRolloutMetadata; + /// Pending Rollout metadata that is latest data from server that might or might not be applied. + NSArray *_fetchedRolloutMetadata; /// DBManager RCNConfigDBManager *_DBManager; /// Current bundle identifier; @@ -80,6 +84,8 @@ - (instancetype)initWithDBManager:(RCNConfigDBManager *)DBManager { _defaultConfig = [[NSMutableDictionary alloc] init]; _activePersonalization = [[NSDictionary alloc] init]; _fetchedPersonalization = [[NSDictionary alloc] init]; + _activeRolloutMetadata = [[NSArray alloc] init]; + _fetchedRolloutMetadata = [[NSArray alloc] init]; _bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier]; if (!_bundleIdentifier) { FIRLogNotice(kFIRLoggerRemoteConfig, @"I-RCN000038", @@ -115,25 +121,30 @@ - (void)loadConfigFromMainTable { _isDatabaseLoadAlreadyInitiated = true; dispatch_group_enter(_dispatch_group); - [_DBManager - loadMainWithBundleIdentifier:_bundleIdentifier - completionHandler:^(BOOL success, NSDictionary *fetchedConfig, - NSDictionary *activeConfig, NSDictionary *defaultConfig) { - self->_fetchedConfig = [fetchedConfig mutableCopy]; - self->_activeConfig = [activeConfig mutableCopy]; - self->_defaultConfig = [defaultConfig mutableCopy]; - dispatch_group_leave(self->_dispatch_group); - }]; + [_DBManager loadMainWithBundleIdentifier:_bundleIdentifier + completionHandler:^( + BOOL success, NSDictionary *fetchedConfig, NSDictionary *activeConfig, + NSDictionary *defaultConfig, NSDictionary *rolloutMetadata) { + self->_fetchedConfig = [fetchedConfig mutableCopy]; + self->_activeConfig = [activeConfig mutableCopy]; + self->_defaultConfig = [defaultConfig mutableCopy]; + self->_fetchedRolloutMetadata = + [rolloutMetadata[@RCNRolloutTableKeyFetchedMetadata] copy]; + self->_activeRolloutMetadata = + [rolloutMetadata[@RCNRolloutTableKeyActiveMetadata] copy]; + dispatch_group_leave(self->_dispatch_group); + }]; // TODO(karenzeng): Refactor personalization to be returned in loadMainWithBundleIdentifier above dispatch_group_enter(_dispatch_group); - [_DBManager loadPersonalizationWithCompletionHandler:^( - BOOL success, NSDictionary *fetchedPersonalization, - NSDictionary *activePersonalization, NSDictionary *defaultConfig) { - self->_fetchedPersonalization = [fetchedPersonalization copy]; - self->_activePersonalization = [activePersonalization copy]; - dispatch_group_leave(self->_dispatch_group); - }]; + [_DBManager + loadPersonalizationWithCompletionHandler:^( + BOOL success, NSDictionary *fetchedPersonalization, NSDictionary *activePersonalization, + NSDictionary *defaultConfig, NSDictionary *rolloutMetadata) { + self->_fetchedPersonalization = [fetchedPersonalization copy]; + self->_activePersonalization = [activePersonalization copy]; + dispatch_group_leave(self->_dispatch_group); + }]; } /// Update the current config result to main table. @@ -269,6 +280,7 @@ - (void)updateConfigContentWithResponse:(NSDictionary *)response [self handleUpdateStateForConfigNamespace:currentNamespace withEntries:response[RCNFetchResponseKeyEntries]]; [self handleUpdatePersonalization:response[RCNFetchResponseKeyPersonalizationMetadata]]; + [self handleUpdateRolloutFetchedMetadata:response[RCNFetchResponseKeyRolloutMetadata]]; return; } } @@ -279,6 +291,15 @@ - (void)activatePersonalization { fromSource:RCNDBSourceActive]; } +- (void)activateRolloutMetadata:(void (^)(BOOL success))completionHandler { + _activeRolloutMetadata = _fetchedRolloutMetadata; + [_DBManager insertOrUpdateRolloutTableWithKey:@RCNRolloutTableKeyActiveMetadata + value:_activeRolloutMetadata + completionHandler:^(BOOL success, NSDictionary *result) { + completionHandler(success); + }]; +} + #pragma mark State handling - (void)handleNoChangeStateForConfigNamespace:(NSString *)currentNamespace { if (!_fetchedConfig[currentNamespace]) { @@ -342,6 +363,16 @@ - (void)handleUpdatePersonalization:(NSDictionary *)metadata { [_DBManager insertOrUpdatePersonalizationConfig:metadata fromSource:RCNDBSourceFetched]; } +- (void)handleUpdateRolloutFetchedMetadata:(NSArray *)metadata { + if (!metadata) { + metadata = [[NSArray alloc] init]; + } + _fetchedRolloutMetadata = metadata; + [_DBManager insertOrUpdateRolloutTableWithKey:@RCNRolloutTableKeyFetchedMetadata + value:metadata + completionHandler:nil]; +} + #pragma mark - getter/setter - (NSDictionary *)fetchedConfig { /// If this is the first time reading the fetchedConfig, we might still be reading it from the @@ -369,6 +400,11 @@ - (NSDictionary *)activePersonalization { return _activePersonalization; } +- (NSArray *)activeRolloutMetadata { + [self checkAndWaitForInitialDatabaseLoad]; + return _activeRolloutMetadata; +} + - (NSDictionary *)getConfigAndMetadataForNamespace:(NSString *)FIRNamespace { /// If this is the first time reading the active metadata, we might still be reading it from the /// database. @@ -411,6 +447,8 @@ - (FIRRemoteConfigUpdate *)getConfigUpdateForNamespace:(NSString *)FIRNamespace _activeConfig[FIRNamespace] ? _activeConfig[FIRNamespace] : [[NSDictionary alloc] init]; NSDictionary *fetchedP13n = _fetchedPersonalization; NSDictionary *activeP13n = _activePersonalization; + NSArray *fetchedRolloutMetadata = _fetchedRolloutMetadata; + NSArray *activeRolloutMetadata = _activeRolloutMetadata; // add new/updated params for (NSString *key in [fetchedConfig allKeys]) { @@ -439,8 +477,50 @@ - (FIRRemoteConfigUpdate *)getConfigUpdateForNamespace:(NSString *)FIRNamespace } } + NSDictionary *fetchedRollouts = + [self getParameterKeyToRolloutMetadata:fetchedRolloutMetadata]; + NSDictionary *activeRollouts = + [self getParameterKeyToRolloutMetadata:activeRolloutMetadata]; + + // add params with new/updated rollout metadata + for (NSString *key in [fetchedRollouts allKeys]) { + if (activeRollouts[key] == nil || + ![activeRollouts[key] isEqualToDictionary:fetchedRollouts[key]]) { + [updatedKeys addObject:key]; + } + } + // add params with deleted rollout metadata + for (NSString *key in [activeRollouts allKeys]) { + if (fetchedRollouts[key] == nil) { + [updatedKeys addObject:key]; + } + } + configUpdate = [[FIRRemoteConfigUpdate alloc] initWithUpdatedKeys:updatedKeys]; return configUpdate; } +- (NSDictionary *)getParameterKeyToRolloutMetadata: + (NSArray *)rolloutMetadata { + NSMutableDictionary *result = + [[NSMutableDictionary alloc] init]; + for (NSDictionary *metadata in rolloutMetadata) { + NSString *rolloutId = metadata[RCNFetchResponseKeyRolloutID]; + NSString *variantId = metadata[RCNFetchResponseKeyVariantID]; + NSArray *affectedKeys = metadata[RCNFetchResponseKeyAffectedParameterKeys]; + if (rolloutId && variantId && affectedKeys) { + for (NSString *key in affectedKeys) { + if (result[key]) { + NSMutableDictionary *rolloutIdToVariantId = result[key]; + [rolloutIdToVariantId setValue:variantId forKey:rolloutId]; + } else { + NSMutableDictionary *rolloutIdToVariantId = [@{rolloutId : variantId} mutableCopy]; + [result setValue:rolloutIdToVariantId forKey:key]; + } + } + } + } + return [result copy]; +} + @end diff --git a/FirebaseRemoteConfig/Sources/RCNConfigDBManager.h b/FirebaseRemoteConfig/Sources/RCNConfigDBManager.h index 39c3e213b73..fba094624ca 100644 --- a/FirebaseRemoteConfig/Sources/RCNConfigDBManager.h +++ b/FirebaseRemoteConfig/Sources/RCNConfigDBManager.h @@ -53,10 +53,12 @@ typedef void (^RCNDBCompletion)(BOOL success, NSDictionary *result); /// @param fetchedConfig Return fetchedConfig loaded from DB /// @param activeConfig Return activeConfig loaded from DB /// @param defaultConfig Return defaultConfig loaded from DB +/// @param rolloutMetadata Return fetched and active RolloutMetadata loaded from DB typedef void (^RCNDBLoadCompletion)(BOOL success, NSDictionary *fetchedConfig, NSDictionary *activeConfig, - NSDictionary *defaultConfig); + NSDictionary *defaultConfig, + NSDictionary *rolloutMetadata); /// Returns the current version of the Remote Config database. + (NSString *)remoteConfigPathForDatabase; @@ -78,7 +80,6 @@ typedef void (^RCNDBLoadCompletion)(BOOL success, /// Load Personalization from table. /// @param handler The callback when reading from DB is complete. - (void)loadPersonalizationWithCompletionHandler:(RCNDBLoadCompletion)handler; - /// Insert a record in metadata table. /// @param columnNameToValue The column name and its value to be inserted in metadata table. /// @param handler The callback. @@ -110,6 +111,15 @@ typedef void (^RCNDBLoadCompletion)(BOOL success, /// Insert or update the data in Personalization config. - (BOOL)insertOrUpdatePersonalizationConfig:(NSDictionary *)metadata fromSource:(RCNDBSource)source; +/// Insert rollout metadata in rollout table. +/// @param key Key indicating whether rollout metadata is fetched or active and defined in +/// RCNConfigDefines.h. +/// @param metadataList The metadata info for each rollout entry . +/// @param handler The callback. +- (void)insertOrUpdateRolloutTableWithKey:(NSString *)key + value:(NSArray *)metadataList + completionHandler:(RCNDBCompletion)handler; + /// Clear the record of given namespace and package name /// before updating the table. - (void)deleteRecordFromMainTableWithNamespace:(NSString *)namespace_p diff --git a/FirebaseRemoteConfig/Sources/RCNConfigDBManager.m b/FirebaseRemoteConfig/Sources/RCNConfigDBManager.m index 6550760c16b..5b21306a85a 100644 --- a/FirebaseRemoteConfig/Sources/RCNConfigDBManager.m +++ b/FirebaseRemoteConfig/Sources/RCNConfigDBManager.m @@ -31,6 +31,7 @@ #define RCNTableNameInternalMetadata "internal_metadata" #define RCNTableNameExperiment "experiment" #define RCNTableNamePersonalization "personalization" +#define RCNTableNameRollout "rollout" static BOOL gIsNewDatabase; /// SQLite file name in versions 0, 1 and 2. @@ -284,11 +285,14 @@ - (BOOL)createTableSchema { "create TABLE IF NOT EXISTS " RCNTableNamePersonalization " (_id INTEGER PRIMARY KEY, key INTEGER, value BLOB)"; + static const char *createTableRollout = "create TABLE IF NOT EXISTS " RCNTableNameRollout + " (_id INTEGER PRIMARY KEY, key TEXT, value BLOB)"; + return [self executeQuery:createTableMain] && [self executeQuery:createTableMainActive] && [self executeQuery:createTableMainDefault] && [self executeQuery:createTableMetadata] && [self executeQuery:createTableInternalMetadata] && [self executeQuery:createTableExperiment] && - [self executeQuery:createTablePersonalization]; + [self executeQuery:createTablePersonalization] && [self executeQuery:createTableRollout]; } - (void)removeDatabaseOnDatabaseQueueAtPath:(NSString *)path { @@ -618,6 +622,52 @@ - (BOOL)insertOrUpdatePersonalizationConfig:(NSDictionary *)dataValue return YES; } +- (void)insertOrUpdateRolloutTableWithKey:(NSString *)key + value:(NSArray *)metadataList + completionHandler:(RCNDBCompletion)handler { + dispatch_async(_databaseOperationQueue, ^{ + BOOL success = [self insertOrUpdateRolloutTableWithKey:key value:metadataList]; + if (handler) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + handler(success, nil); + }); + } + }); +} + +- (BOOL)insertOrUpdateRolloutTableWithKey:(NSString *)key + value:(NSArray *)arrayValue { + RCN_MUST_NOT_BE_MAIN_THREAD(); + NSError *error; + NSData *dataValue = [NSJSONSerialization dataWithJSONObject:arrayValue + options:NSJSONWritingPrettyPrinted + error:&error]; + const char *SQL = + "INSERT OR REPLACE INTO " RCNTableNameRollout + " (_id, key, value) values ((SELECT _id from " RCNTableNameRollout " WHERE key = ?), ?, ?)"; + sqlite3_stmt *statement = [self prepareSQL:SQL]; + if (!statement) { + return NO; + } + if (![self bindStringToStatement:statement index:1 string:key]) { + return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO]; + } + + if (![self bindStringToStatement:statement index:2 string:key]) { + return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO]; + } + + if (sqlite3_bind_blob(statement, 3, dataValue.bytes, (int)dataValue.length, NULL) != SQLITE_OK) { + return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO]; + } + + if (sqlite3_step(statement) != SQLITE_DONE) { + return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO]; + } + sqlite3_finalize(statement); + return YES; +} + #pragma mark - update - (void)updateMetadataWithOption:(RCNUpdateOption)option @@ -852,7 +902,6 @@ - (void)loadExperimentWithCompletionHandler:(RCNDBCompletion)handler { - (NSMutableArray *)loadExperimentTableFromKey:(NSString *)key { RCN_MUST_NOT_BE_MAIN_THREAD(); - NSMutableArray *results = [[NSMutableArray alloc] init]; const char *SQL = "SELECT value FROM " RCNTableNameExperiment " WHERE key = ?"; sqlite3_stmt *statement = [self prepareSQL:SQL]; if (!statement) { @@ -861,12 +910,49 @@ - (void)loadExperimentWithCompletionHandler:(RCNDBCompletion)handler { NSArray *params = @[ key ]; [self bindStringsToStatement:statement stringArray:params]; - NSData *experimentData; + NSMutableArray *results = [self loadValuesFromStatement:statement]; + return results; +} + +- (NSArray *)loadRolloutTableFromKey:(NSString *)key { + RCN_MUST_NOT_BE_MAIN_THREAD(); + const char *SQL = "SELECT value FROM " RCNTableNameRollout " WHERE key = ?"; + sqlite3_stmt *statement = [self prepareSQL:SQL]; + if (!statement) { + return nil; + } + NSArray *params = @[ key ]; + [self bindStringsToStatement:statement stringArray:params]; + NSMutableArray *results = [self loadValuesFromStatement:statement]; + // There should be only one entry in this table. + if (results.count != 1) { + return nil; + } + NSArray *rollout; + // Convert from NSData to NSArray + if (results[0]) { + NSError *error; + rollout = [NSJSONSerialization JSONObjectWithData:results[0] options:0 error:&error]; + if (!rollout) { + FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000011", + @"Failed to convert NSData to NSAarry for Rollout Metadata with error %@.", + error); + } + } + if (!rollout) { + rollout = [[NSArray alloc] init]; + } + return rollout; +} + +- (NSMutableArray *)loadValuesFromStatement:(sqlite3_stmt *)statement { + NSMutableArray *results = [[NSMutableArray alloc] init]; + NSData *value; while (sqlite3_step(statement) == SQLITE_ROW) { - experimentData = [NSData dataWithBytes:(char *)sqlite3_column_blob(statement, 0) - length:sqlite3_column_bytes(statement, 0)]; - if (experimentData) { - [results addObject:experimentData]; + value = [NSData dataWithBytes:(char *)sqlite3_column_blob(statement, 0) + length:sqlite3_column_bytes(statement, 0)]; + if (value) { + [results addObject:value]; } } @@ -880,7 +966,7 @@ - (void)loadPersonalizationWithCompletionHandler:(RCNDBLoadCompletion)handler { RCNConfigDBManager *strongSelf = weakSelf; if (!strongSelf) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - handler(NO, [NSMutableDictionary new], [NSMutableDictionary new], nil); + handler(NO, [NSMutableDictionary new], [NSMutableDictionary new], nil, nil); }); return; } @@ -913,7 +999,7 @@ - (void)loadPersonalizationWithCompletionHandler:(RCNDBLoadCompletion)handler { if (handler) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - handler(YES, fetchedPersonalization, activePersonalization, nil); + handler(YES, fetchedPersonalization, activePersonalization, nil, nil); }); } }); @@ -987,7 +1073,7 @@ - (void)loadMainWithBundleIdentifier:(NSString *)bundleIdentifier RCNConfigDBManager *strongSelf = weakSelf; if (!strongSelf) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - handler(NO, [NSDictionary new], [NSDictionary new], [NSDictionary new]); + handler(NO, [NSDictionary new], [NSDictionary new], [NSDictionary new], [NSDictionary new]); }); return; } @@ -1000,12 +1086,26 @@ - (void)loadMainWithBundleIdentifier:(NSString *)bundleIdentifier __block NSDictionary *defaultConfig = [strongSelf loadMainTableWithBundleIdentifier:bundleIdentifier fromSource:RCNDBSourceDefault]; + + __block NSArray *fetchedRolloutMetadata = + [strongSelf loadRolloutTableFromKey:@RCNRolloutTableKeyFetchedMetadata]; + __block NSArray *activeRolloutMetadata = + [strongSelf loadRolloutTableFromKey:@RCNRolloutTableKeyActiveMetadata]; + if (handler) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ fetchedConfig = fetchedConfig ? fetchedConfig : [[NSDictionary alloc] init]; activeConfig = activeConfig ? activeConfig : [[NSDictionary alloc] init]; defaultConfig = defaultConfig ? defaultConfig : [[NSDictionary alloc] init]; - handler(YES, fetchedConfig, activeConfig, defaultConfig); + fetchedRolloutMetadata = + fetchedRolloutMetadata ? fetchedRolloutMetadata : [[NSArray alloc] init]; + activeRolloutMetadata = + activeRolloutMetadata ? activeRolloutMetadata : [[NSArray alloc] init]; + NSDictionary *rolloutMetadata = @{ + @RCNRolloutTableKeyActiveMetadata : [activeRolloutMetadata copy], + @RCNRolloutTableKeyFetchedMetadata : [fetchedRolloutMetadata copy] + }; + handler(YES, fetchedConfig, activeConfig, defaultConfig, rolloutMetadata); }); } }); diff --git a/FirebaseRemoteConfig/Sources/RCNConfigDefines.h b/FirebaseRemoteConfig/Sources/RCNConfigDefines.h index cf08f738105..1e95373541b 100644 --- a/FirebaseRemoteConfig/Sources/RCNConfigDefines.h +++ b/FirebaseRemoteConfig/Sources/RCNConfigDefines.h @@ -31,5 +31,7 @@ #define RCNExperimentTableKeyPayload "experiment_payload" #define RCNExperimentTableKeyMetadata "experiment_metadata" #define RCNExperimentTableKeyActivePayload "experiment_active_payload" +#define RCNRolloutTableKeyActiveMetadata "active_rollout_metadata" +#define RCNRolloutTableKeyFetchedMetadata "fetched_rollout_metadata" #endif diff --git a/FirebaseRemoteConfig/Sources/RCNConfigFetch.m b/FirebaseRemoteConfig/Sources/RCNConfigFetch.m index c3a0f16ddd8..dbc4b9bec56 100644 --- a/FirebaseRemoteConfig/Sources/RCNConfigFetch.m +++ b/FirebaseRemoteConfig/Sources/RCNConfigFetch.m @@ -25,6 +25,7 @@ #import "FirebaseRemoteConfig/Sources/RCNConfigContent.h" #import "FirebaseRemoteConfig/Sources/RCNConfigExperiment.h" #import "FirebaseRemoteConfig/Sources/RCNDevice.h" +@import FirebaseRemoteConfigInterop; #ifdef RCN_STAGING_SERVER static NSString *const kServerURLDomain = @@ -105,7 +106,7 @@ - (instancetype)initWithContent:(RCNConfigContent *)content _content = content; _fetchSession = [self newFetchSession]; _options = options; - _templateVersionNumber = [self->_settings lastTemplateVersion]; + _templateVersionNumber = [self->_settings lastFetchedTemplateVersion]; } return self; } @@ -572,7 +573,7 @@ - (void)fetchWithUserProperties:(NSDictionary *)userProperties // Update experiments only for 3p namespace NSString *namespace = [strongSelf->_FIRNamespace substringToIndex:[strongSelf->_FIRNamespace rangeOfString:@":"].location]; - if ([namespace isEqualToString:FIRNamespaceGoogleMobilePlatform]) { + if ([namespace isEqualToString:FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform]) { [strongSelf->_experiment updateExperimentsWithResponse: fetchedConfig[RCNFetchResponseKeyExperimentDescriptions]]; } diff --git a/FirebaseRemoteConfig/Sources/RCNConfigSettings.m b/FirebaseRemoteConfig/Sources/RCNConfigSettings.m index 0b3e3ad1164..e85a63f4873 100644 --- a/FirebaseRemoteConfig/Sources/RCNConfigSettings.m +++ b/FirebaseRemoteConfig/Sources/RCNConfigSettings.m @@ -110,7 +110,8 @@ - (instancetype)initWithDatabaseManager:(RCNConfigDBManager *)manager } _isFetchInProgress = NO; - _lastTemplateVersion = [_userDefaultsManager lastTemplateVersion]; + _lastFetchedTemplateVersion = [_userDefaultsManager lastFetchedTemplateVersion]; + _lastActiveTemplateVersion = [_userDefaultsManager lastActiveTemplateVersion]; _realtimeExponentialBackoffRetryInterval = [_userDefaultsManager currentRealtimeThrottlingRetryIntervalSeconds]; _realtimeExponentialBackoffThrottleEndTime = [_userDefaultsManager realtimeThrottleEndTime]; @@ -292,7 +293,8 @@ - (void)updateMetadataWithFetchSuccessStatus:(BOOL)fetchSuccess [self updateLastFetchTimeInterval:[[NSDate date] timeIntervalSince1970]]; // Note: We expect the googleAppID to always be available. _deviceContext = FIRRemoteConfigDeviceContextWithProjectIdentifier(_googleAppID); - [_userDefaultsManager setLastTemplateVersion:templateVersion]; + _lastFetchedTemplateVersion = templateVersion; + [_userDefaultsManager setLastFetchedTemplateVersion:templateVersion]; } [self updateMetadataTable]; @@ -377,6 +379,11 @@ - (void)updateMetadataTable { [_DBManager insertMetadataTableWithValues:columnNameToValue completionHandler:nil]; } +- (void)updateLastActiveTemplateVersion { + _lastActiveTemplateVersion = _lastFetchedTemplateVersion; + [_userDefaultsManager setLastActiveTemplateVersion:_lastActiveTemplateVersion]; +} + #pragma mark - fetch request /// Returns a fetch request with the latest device and config change. diff --git a/FirebaseRemoteConfig/Sources/RCNConstants3P.m b/FirebaseRemoteConfig/Sources/RCNConstants3P.m index 6bd5d78d094..e64295be62c 100644 --- a/FirebaseRemoteConfig/Sources/RCNConstants3P.m +++ b/FirebaseRemoteConfig/Sources/RCNConstants3P.m @@ -17,4 +17,5 @@ #import "FirebaseRemoteConfig/Sources/Public/FirebaseRemoteConfig/FIRRemoteConfig.h" /// Firebase Remote Config service default namespace. +/// TODO(doudounan): Change to use this namespace defined in RemoteConfigInterop. NSString *const FIRNamespaceGoogleMobilePlatform = @"firebase"; diff --git a/FirebaseRemoteConfig/Sources/RCNUserDefaultsManager.h b/FirebaseRemoteConfig/Sources/RCNUserDefaultsManager.h index acbcd5842f4..b235f217d81 100644 --- a/FirebaseRemoteConfig/Sources/RCNUserDefaultsManager.h +++ b/FirebaseRemoteConfig/Sources/RCNUserDefaultsManager.h @@ -44,7 +44,9 @@ NS_ASSUME_NONNULL_BEGIN /// Realtime retry count. @property(nonatomic, assign) int realtimeRetryCount; /// Last fetched template version. -@property(nonatomic, assign) NSString *lastTemplateVersion; +@property(nonatomic, assign) NSString *lastFetchedTemplateVersion; +/// Last active template version. +@property(nonatomic, assign) NSString *lastActiveTemplateVersion; /// Designated initializer. - (instancetype)initWithAppName:(NSString *)appName diff --git a/FirebaseRemoteConfig/Sources/RCNUserDefaultsManager.m b/FirebaseRemoteConfig/Sources/RCNUserDefaultsManager.m index 29ec2e87a06..880a2157fe1 100644 --- a/FirebaseRemoteConfig/Sources/RCNUserDefaultsManager.m +++ b/FirebaseRemoteConfig/Sources/RCNUserDefaultsManager.m @@ -111,7 +111,7 @@ - (void)setLastETag:(NSString *)lastETag { } } -- (NSString *)lastTemplateVersion { +- (NSString *)lastFetchedTemplateVersion { NSDictionary *userDefaults = [self instanceUserDefaults]; if ([userDefaults objectForKey:RCNFetchResponseKeyTemplateVersion]) { return [userDefaults objectForKey:RCNFetchResponseKeyTemplateVersion]; @@ -120,12 +120,27 @@ - (NSString *)lastTemplateVersion { return @"0"; } -- (void)setLastTemplateVersion:(NSString *)templateVersion { +- (void)setLastFetchedTemplateVersion:(NSString *)templateVersion { if (templateVersion) { [self setInstanceUserDefaultsValue:templateVersion forKey:RCNFetchResponseKeyTemplateVersion]; } } +- (NSString *)lastActiveTemplateVersion { + NSDictionary *userDefaults = [self instanceUserDefaults]; + if ([userDefaults objectForKey:RCNActiveKeyTemplateVersion]) { + return [userDefaults objectForKey:RCNActiveKeyTemplateVersion]; + } + + return @"0"; +} + +- (void)setLastActiveTemplateVersion:(NSString *)templateVersion { + if (templateVersion) { + [self setInstanceUserDefaultsValue:templateVersion forKey:RCNActiveKeyTemplateVersion]; + } +} + - (NSTimeInterval)lastETagUpdateTime { NSNumber *lastETagUpdateTime = [[self instanceUserDefaults] objectForKey:kRCNUserDefaultsKeyNamelastETagUpdateTime]; diff --git a/FirebaseRemoteConfig/Swift/Resources/PrivacyInfo.xcprivacy b/FirebaseRemoteConfig/Swift/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 00000000000..719a06f4937 --- /dev/null +++ b/FirebaseRemoteConfig/Swift/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,38 @@ + + + + + NSPrivacyTracking + + NSPrivacyTrackingDomains + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeOtherDiagnosticData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAnalytics + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + 1C8F.1 + + + + + + diff --git a/FirebaseRemoteConfig/Tests/FeatureRolloutsTestApp/FeatureRolloutsTestApp.xcodeproj/project.pbxproj b/FirebaseRemoteConfig/Tests/FeatureRolloutsTestApp/FeatureRolloutsTestApp.xcodeproj/project.pbxproj new file mode 100644 index 00000000000..d7f955d7c07 --- /dev/null +++ b/FirebaseRemoteConfig/Tests/FeatureRolloutsTestApp/FeatureRolloutsTestApp.xcodeproj/project.pbxproj @@ -0,0 +1,1059 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 61A5654706089C41A7398CF3 /* Pods_FeatureRolloutsTestApp_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2FE5945D035CAAB3297D2CAC /* Pods_FeatureRolloutsTestApp_iOS.framework */; }; + 848D345C8969AF72BCC0E2E4 /* Pods_FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1729B0ED2CACB9C5A62A6F8C /* Pods_FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS.framework */; }; + 951D70152B71AD9B00BE7EED /* RemoteConfigButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 951D70142B71AD9A00BE7EED /* RemoteConfigButtonView.swift */; }; + 951D70162B71AD9B00BE7EED /* RemoteConfigButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 951D70142B71AD9A00BE7EED /* RemoteConfigButtonView.swift */; }; + AD11C57C978D52894BFDC47F /* Pods_FeatureRolloutsTestApp_RemoteConfig_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E60230146BDE14D306856CB /* Pods_FeatureRolloutsTestApp_RemoteConfig_iOS.framework */; }; + C427C4A32B4603F60088A488 /* FeatureRolloutsTestAppApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C427C4A22B4603F60088A488 /* FeatureRolloutsTestAppApp.swift */; }; + C427C4A52B4603F60088A488 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C427C4A42B4603F60088A488 /* ContentView.swift */; }; + C49C486C2B4704D900BC1456 /* FeatureRolloutsTestAppApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C427C4A22B4603F60088A488 /* FeatureRolloutsTestAppApp.swift */; }; + C49C48702B4704F300BC1456 /* FeatureRolloutsTestAppApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C427C4A22B4603F60088A488 /* FeatureRolloutsTestAppApp.swift */; }; + C49C487A2B4704F500BC1456 /* FeatureRolloutsTestAppApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C427C4A22B4603F60088A488 /* FeatureRolloutsTestAppApp.swift */; }; + C49C48832B47074400BC1456 /* FirebaseCrashlytics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C49C48822B47074400BC1456 /* FirebaseCrashlytics.framework */; }; + C49C48872B47075600BC1456 /* FirebaseRemoteConfig.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C49C48862B47075600BC1456 /* FirebaseRemoteConfig.framework */; }; + C49C488B2B47075C00BC1456 /* FirebaseCrashlytics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C49C488A2B47075C00BC1456 /* FirebaseCrashlytics.framework */; }; + C49C488F2B47076200BC1456 /* FirebaseRemoteConfig.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C49C488E2B47076200BC1456 /* FirebaseRemoteConfig.framework */; }; + C49C48952B47207200BC1456 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49C48942B47207200BC1456 /* ContentView.swift */; }; + C49C48992B4720AE00BC1456 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49C48982B4720AE00BC1456 /* ContentView.swift */; }; + C49C489C2B4720DD00BC1456 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49C489B2B4720DD00BC1456 /* ContentView.swift */; }; + C49C489E2B4722C100BC1456 /* CrashButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49C489D2B4722C100BC1456 /* CrashButtonView.swift */; }; + C49C489F2B47233000BC1456 /* CrashButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49C489D2B4722C100BC1456 /* CrashButtonView.swift */; }; + C49C48A12B47261000BC1456 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = C49C48A02B47261000BC1456 /* GoogleService-Info.plist */; }; + C49C48A22B47261000BC1456 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = C49C48A02B47261000BC1456 /* GoogleService-Info.plist */; }; + C49C48A32B47261000BC1456 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = C49C48A02B47261000BC1456 /* GoogleService-Info.plist */; }; + C49C48A42B47261000BC1456 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = C49C48A02B47261000BC1456 /* GoogleService-Info.plist */; }; + F07A9478976524A8264259F0 /* Pods_FeatureRolloutsTestApp_Crashlytics_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5472955122D0CE0A8A3CE4D5 /* Pods_FeatureRolloutsTestApp_Crashlytics_iOS.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + C49C48852B47074400BC1456 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + C49C48892B47075600BC1456 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; + C49C488D2B47075C00BC1456 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 025F972344BB0B489CC052D6 /* Pods-FeatureRolloutsTestApp_Crashlytics_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FeatureRolloutsTestApp_Crashlytics_iOS.debug.xcconfig"; path = "Target Support Files/Pods-FeatureRolloutsTestApp_Crashlytics_iOS/Pods-FeatureRolloutsTestApp_Crashlytics_iOS.debug.xcconfig"; sourceTree = ""; }; + 10710CAF870FA7E8D1ABF94C /* Pods-FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS.release.xcconfig"; path = "Target Support Files/Pods-FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS/Pods-FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS.release.xcconfig"; sourceTree = ""; }; + 1729B0ED2CACB9C5A62A6F8C /* Pods_FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 2842D338F32EE531C752262E /* Pods-FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS.debug.xcconfig"; path = "Target Support Files/Pods-FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS/Pods-FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS.debug.xcconfig"; sourceTree = ""; }; + 2D15DD53784CDDE94D00AB02 /* Pods-FeatureRolloutsTestApp_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FeatureRolloutsTestApp_iOS.release.xcconfig"; path = "Target Support Files/Pods-FeatureRolloutsTestApp_iOS/Pods-FeatureRolloutsTestApp_iOS.release.xcconfig"; sourceTree = ""; }; + 2FE5945D035CAAB3297D2CAC /* Pods_FeatureRolloutsTestApp_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FeatureRolloutsTestApp_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 30BB126FCB8D5F53B5795500 /* Pods-FeatureRolloutsTestApp_RemoteConfig_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FeatureRolloutsTestApp_RemoteConfig_iOS.release.xcconfig"; path = "Target Support Files/Pods-FeatureRolloutsTestApp_RemoteConfig_iOS/Pods-FeatureRolloutsTestApp_RemoteConfig_iOS.release.xcconfig"; sourceTree = ""; }; + 4E60230146BDE14D306856CB /* Pods_FeatureRolloutsTestApp_RemoteConfig_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FeatureRolloutsTestApp_RemoteConfig_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 5472955122D0CE0A8A3CE4D5 /* Pods_FeatureRolloutsTestApp_Crashlytics_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FeatureRolloutsTestApp_Crashlytics_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 6D30A6D1F2CE622B6D5D563F /* Pods-FeatureRolloutsTestApp_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FeatureRolloutsTestApp_iOS.debug.xcconfig"; path = "Target Support Files/Pods-FeatureRolloutsTestApp_iOS/Pods-FeatureRolloutsTestApp_iOS.debug.xcconfig"; sourceTree = ""; }; + 8BA72854B19D7A9D9BE15E1D /* Pods-FeatureRolloutsTestApp_Crashlytics_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FeatureRolloutsTestApp_Crashlytics_iOS.release.xcconfig"; path = "Target Support Files/Pods-FeatureRolloutsTestApp_Crashlytics_iOS/Pods-FeatureRolloutsTestApp_Crashlytics_iOS.release.xcconfig"; sourceTree = ""; }; + 951D70142B71AD9A00BE7EED /* RemoteConfigButtonView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteConfigButtonView.swift; sourceTree = ""; }; + AF260B513E38B2528E7B13CC /* Pods-FeatureRolloutsTestApp_RemoteConfig_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FeatureRolloutsTestApp_RemoteConfig_iOS.debug.xcconfig"; path = "Target Support Files/Pods-FeatureRolloutsTestApp_RemoteConfig_iOS/Pods-FeatureRolloutsTestApp_RemoteConfig_iOS.debug.xcconfig"; sourceTree = ""; }; + C427C49F2B4603F60088A488 /* FeatureRolloutsTestApp_iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FeatureRolloutsTestApp_iOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; + C427C4A22B4603F60088A488 /* FeatureRolloutsTestAppApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureRolloutsTestAppApp.swift; sourceTree = ""; }; + C427C4A42B4603F60088A488 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + C427C4A82B4603F80088A488 /* FeatureRolloutsTestApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = FeatureRolloutsTestApp.entitlements; sourceTree = ""; }; + C49C48412B460FC600BC1456 /* FeatureRolloutsTestApp_Crashlytics_iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FeatureRolloutsTestApp_Crashlytics_iOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; + C49C48772B4704F300BC1456 /* FeatureRolloutsTestApp_RemoteConfig_iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FeatureRolloutsTestApp_RemoteConfig_iOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; + C49C48812B4704F500BC1456 /* FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; + C49C48822B47074400BC1456 /* FirebaseCrashlytics.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FirebaseCrashlytics.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C49C48862B47075600BC1456 /* FirebaseRemoteConfig.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FirebaseRemoteConfig.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C49C488A2B47075C00BC1456 /* FirebaseCrashlytics.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FirebaseCrashlytics.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C49C488E2B47076200BC1456 /* FirebaseRemoteConfig.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = FirebaseRemoteConfig.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C49C48942B47207200BC1456 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + C49C48982B4720AE00BC1456 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + C49C489B2B4720DD00BC1456 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + C49C489D2B4722C100BC1456 /* CrashButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashButtonView.swift; sourceTree = ""; }; + C49C48A02B47261000BC1456 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + C427C49C2B4603F60088A488 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 61A5654706089C41A7398CF3 /* Pods_FeatureRolloutsTestApp_iOS.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C49C483E2B460FC600BC1456 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + F07A9478976524A8264259F0 /* Pods_FeatureRolloutsTestApp_Crashlytics_iOS.framework in Frameworks */, + C49C48832B47074400BC1456 /* FirebaseCrashlytics.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C49C48722B4704F300BC1456 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + AD11C57C978D52894BFDC47F /* Pods_FeatureRolloutsTestApp_RemoteConfig_iOS.framework in Frameworks */, + C49C48872B47075600BC1456 /* FirebaseRemoteConfig.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C49C487C2B4704F500BC1456 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C49C488F2B47076200BC1456 /* FirebaseRemoteConfig.framework in Frameworks */, + 848D345C8969AF72BCC0E2E4 /* Pods_FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS.framework in Frameworks */, + C49C488B2B47075C00BC1456 /* FirebaseCrashlytics.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 29E7B4F9D5112B2AFBA1C6F8 /* Pods */ = { + isa = PBXGroup; + children = ( + 2842D338F32EE531C752262E /* Pods-FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS.debug.xcconfig */, + 10710CAF870FA7E8D1ABF94C /* Pods-FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS.release.xcconfig */, + 025F972344BB0B489CC052D6 /* Pods-FeatureRolloutsTestApp_Crashlytics_iOS.debug.xcconfig */, + 8BA72854B19D7A9D9BE15E1D /* Pods-FeatureRolloutsTestApp_Crashlytics_iOS.release.xcconfig */, + AF260B513E38B2528E7B13CC /* Pods-FeatureRolloutsTestApp_RemoteConfig_iOS.debug.xcconfig */, + 30BB126FCB8D5F53B5795500 /* Pods-FeatureRolloutsTestApp_RemoteConfig_iOS.release.xcconfig */, + 6D30A6D1F2CE622B6D5D563F /* Pods-FeatureRolloutsTestApp_iOS.debug.xcconfig */, + 2D15DD53784CDDE94D00AB02 /* Pods-FeatureRolloutsTestApp_iOS.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 4D9F4C8E7175D4479AD28BAC /* Frameworks */ = { + isa = PBXGroup; + children = ( + C49C488E2B47076200BC1456 /* FirebaseRemoteConfig.framework */, + C49C488A2B47075C00BC1456 /* FirebaseCrashlytics.framework */, + C49C48862B47075600BC1456 /* FirebaseRemoteConfig.framework */, + C49C48822B47074400BC1456 /* FirebaseCrashlytics.framework */, + 1729B0ED2CACB9C5A62A6F8C /* Pods_FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS.framework */, + 5472955122D0CE0A8A3CE4D5 /* Pods_FeatureRolloutsTestApp_Crashlytics_iOS.framework */, + 4E60230146BDE14D306856CB /* Pods_FeatureRolloutsTestApp_RemoteConfig_iOS.framework */, + 2FE5945D035CAAB3297D2CAC /* Pods_FeatureRolloutsTestApp_iOS.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + C427C4962B4603F60088A488 = { + isa = PBXGroup; + children = ( + C49C489A2B4720C700BC1456 /* FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS */, + C49C48972B47208B00BC1456 /* FeatureRolloutsTestApp_RemoteConfig_iOS */, + C49C48932B47205400BC1456 /* FeatureRolloutsTestApp_Crashlytics_iOS */, + C427C4A12B4603F60088A488 /* FeatureRolloutsTestApp */, + C49C486B2B47048000BC1456 /* Shared */, + C427C4A02B4603F60088A488 /* Products */, + 29E7B4F9D5112B2AFBA1C6F8 /* Pods */, + 4D9F4C8E7175D4479AD28BAC /* Frameworks */, + ); + sourceTree = ""; + }; + C427C4A02B4603F60088A488 /* Products */ = { + isa = PBXGroup; + children = ( + C427C49F2B4603F60088A488 /* FeatureRolloutsTestApp_iOS.app */, + C49C48412B460FC600BC1456 /* FeatureRolloutsTestApp_Crashlytics_iOS.app */, + C49C48772B4704F300BC1456 /* FeatureRolloutsTestApp_RemoteConfig_iOS.app */, + C49C48812B4704F500BC1456 /* FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS.app */, + ); + name = Products; + sourceTree = ""; + }; + C427C4A12B4603F60088A488 /* FeatureRolloutsTestApp */ = { + isa = PBXGroup; + children = ( + C427C4A42B4603F60088A488 /* ContentView.swift */, + C427C4A82B4603F80088A488 /* FeatureRolloutsTestApp.entitlements */, + ); + path = FeatureRolloutsTestApp; + sourceTree = ""; + }; + C49C486B2B47048000BC1456 /* Shared */ = { + isa = PBXGroup; + children = ( + C49C48A02B47261000BC1456 /* GoogleService-Info.plist */, + C427C4A22B4603F60088A488 /* FeatureRolloutsTestAppApp.swift */, + 951D70142B71AD9A00BE7EED /* RemoteConfigButtonView.swift */, + C49C489D2B4722C100BC1456 /* CrashButtonView.swift */, + ); + path = Shared; + sourceTree = ""; + }; + C49C48932B47205400BC1456 /* FeatureRolloutsTestApp_Crashlytics_iOS */ = { + isa = PBXGroup; + children = ( + C49C48942B47207200BC1456 /* ContentView.swift */, + ); + path = FeatureRolloutsTestApp_Crashlytics_iOS; + sourceTree = ""; + }; + C49C48972B47208B00BC1456 /* FeatureRolloutsTestApp_RemoteConfig_iOS */ = { + isa = PBXGroup; + children = ( + C49C48982B4720AE00BC1456 /* ContentView.swift */, + ); + path = FeatureRolloutsTestApp_RemoteConfig_iOS; + sourceTree = ""; + }; + C49C489A2B4720C700BC1456 /* FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS */ = { + isa = PBXGroup; + children = ( + C49C489B2B4720DD00BC1456 /* ContentView.swift */, + ); + path = FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + C427C49E2B4603F60088A488 /* FeatureRolloutsTestApp_iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = C427C4C42B4603F80088A488 /* Build configuration list for PBXNativeTarget "FeatureRolloutsTestApp_iOS" */; + buildPhases = ( + 1C5C78EEA66C3693218AA186 /* [CP] Check Pods Manifest.lock */, + C427C49B2B4603F60088A488 /* Sources */, + C427C49C2B4603F60088A488 /* Frameworks */, + C427C49D2B4603F60088A488 /* Resources */, + 3FCED6F36D70B363DD56F3FB /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = FeatureRolloutsTestApp_iOS; + productName = FeatureRolloutsTestApp; + productReference = C427C49F2B4603F60088A488 /* FeatureRolloutsTestApp_iOS.app */; + productType = "com.apple.product-type.application"; + }; + C49C48402B460FC600BC1456 /* FeatureRolloutsTestApp_Crashlytics_iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = C49C48622B460FC800BC1456 /* Build configuration list for PBXNativeTarget "FeatureRolloutsTestApp_Crashlytics_iOS" */; + buildPhases = ( + E894A1751ED4EBA12174B475 /* [CP] Check Pods Manifest.lock */, + C49C483D2B460FC600BC1456 /* Sources */, + C49C483E2B460FC600BC1456 /* Frameworks */, + C49C483F2B460FC600BC1456 /* Resources */, + 8809A9DD2155751AF47F697B /* [CP] Embed Pods Frameworks */, + C49C48852B47074400BC1456 /* Embed Frameworks */, + C49C48A72B47285600BC1456 /* Crashlytics run script */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = FeatureRolloutsTestApp_Crashlytics_iOS; + productName = FeatureRolloutsTestApp_Crashlytics_iOS; + productReference = C49C48412B460FC600BC1456 /* FeatureRolloutsTestApp_Crashlytics_iOS.app */; + productType = "com.apple.product-type.application"; + }; + C49C486E2B4704F300BC1456 /* FeatureRolloutsTestApp_RemoteConfig_iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = C49C48742B4704F300BC1456 /* Build configuration list for PBXNativeTarget "FeatureRolloutsTestApp_RemoteConfig_iOS" */; + buildPhases = ( + 2E2005BA5C63DC9C5F6B0B5C /* [CP] Check Pods Manifest.lock */, + C49C486F2B4704F300BC1456 /* Sources */, + C49C48722B4704F300BC1456 /* Frameworks */, + C49C48732B4704F300BC1456 /* Resources */, + AE72CFC82C05D96F24F22349 /* [CP] Embed Pods Frameworks */, + C49C48892B47075600BC1456 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = FeatureRolloutsTestApp_RemoteConfig_iOS; + productName = FeatureRolloutsTestApp_Crashlytics_iOS; + productReference = C49C48772B4704F300BC1456 /* FeatureRolloutsTestApp_RemoteConfig_iOS.app */; + productType = "com.apple.product-type.application"; + }; + C49C48782B4704F500BC1456 /* FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = C49C487E2B4704F500BC1456 /* Build configuration list for PBXNativeTarget "FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS" */; + buildPhases = ( + 1D1A7736C1202CF5AE3E74DF /* [CP] Check Pods Manifest.lock */, + C49C48792B4704F500BC1456 /* Sources */, + C49C487C2B4704F500BC1456 /* Frameworks */, + C49C487D2B4704F500BC1456 /* Resources */, + 1AFC789ACC0369540ADCC334 /* [CP] Embed Pods Frameworks */, + C49C488D2B47075C00BC1456 /* Embed Frameworks */, + C49C48A52B47279000BC1456 /* Crashlytics run script */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS; + productName = FeatureRolloutsTestApp_Crashlytics_iOS; + productReference = C49C48812B4704F500BC1456 /* FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + C427C4972B4603F60088A488 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1500; + LastUpgradeCheck = 1500; + TargetAttributes = { + C427C49E2B4603F60088A488 = { + CreatedOnToolsVersion = 15.0; + }; + C49C48402B460FC600BC1456 = { + CreatedOnToolsVersion = 15.0; + }; + }; + }; + buildConfigurationList = C427C49A2B4603F60088A488 /* Build configuration list for PBXProject "FeatureRolloutsTestApp" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = C427C4962B4603F60088A488; + productRefGroup = C427C4A02B4603F60088A488 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + C49C48782B4704F500BC1456 /* FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS */, + C49C48402B460FC600BC1456 /* FeatureRolloutsTestApp_Crashlytics_iOS */, + C49C486E2B4704F300BC1456 /* FeatureRolloutsTestApp_RemoteConfig_iOS */, + C427C49E2B4603F60088A488 /* FeatureRolloutsTestApp_iOS */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + C427C49D2B4603F60088A488 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C49C48A12B47261000BC1456 /* GoogleService-Info.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C49C483F2B460FC600BC1456 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C49C48A22B47261000BC1456 /* GoogleService-Info.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C49C48732B4704F300BC1456 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C49C48A32B47261000BC1456 /* GoogleService-Info.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C49C487D2B4704F500BC1456 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C49C48A42B47261000BC1456 /* GoogleService-Info.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 1AFC789ACC0369540ADCC334 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS/Pods-FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS/Pods-FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS/Pods-FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 1C5C78EEA66C3693218AA186 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-FeatureRolloutsTestApp_iOS-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 1D1A7736C1202CF5AE3E74DF /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 2E2005BA5C63DC9C5F6B0B5C /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-FeatureRolloutsTestApp_RemoteConfig_iOS-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 3FCED6F36D70B363DD56F3FB /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-FeatureRolloutsTestApp_iOS/Pods-FeatureRolloutsTestApp_iOS-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-FeatureRolloutsTestApp_iOS/Pods-FeatureRolloutsTestApp_iOS-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-FeatureRolloutsTestApp_iOS/Pods-FeatureRolloutsTestApp_iOS-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 8809A9DD2155751AF47F697B /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-FeatureRolloutsTestApp_Crashlytics_iOS/Pods-FeatureRolloutsTestApp_Crashlytics_iOS-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-FeatureRolloutsTestApp_Crashlytics_iOS/Pods-FeatureRolloutsTestApp_Crashlytics_iOS-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-FeatureRolloutsTestApp_Crashlytics_iOS/Pods-FeatureRolloutsTestApp_Crashlytics_iOS-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + AE72CFC82C05D96F24F22349 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-FeatureRolloutsTestApp_RemoteConfig_iOS/Pods-FeatureRolloutsTestApp_RemoteConfig_iOS-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-FeatureRolloutsTestApp_RemoteConfig_iOS/Pods-FeatureRolloutsTestApp_RemoteConfig_iOS-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-FeatureRolloutsTestApp_RemoteConfig_iOS/Pods-FeatureRolloutsTestApp_RemoteConfig_iOS-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + C49C48A52B47279000BC1456 /* Crashlytics run script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${BUILD_NAME}", + "$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)", + ); + name = "Crashlytics run script"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Type a script or drag a script file from your workspace to insert its path.\n${PODS_ROOT}/../../../../Crashlytics/run\n"; + }; + C49C48A72B47285600BC1456 /* Crashlytics run script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${BUILD_DIR%Build/*}SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run", + "$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH)", + ); + name = "Crashlytics run script"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Type a script or drag a script file from your workspace to insert its path.\n${PODS_ROOT}/../../../../Crashlytics/run\n"; + }; + E894A1751ED4EBA12174B475 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-FeatureRolloutsTestApp_Crashlytics_iOS-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + C427C49B2B4603F60088A488 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C427C4A52B4603F60088A488 /* ContentView.swift in Sources */, + C427C4A32B4603F60088A488 /* FeatureRolloutsTestAppApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C49C483D2B460FC600BC1456 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C49C489F2B47233000BC1456 /* CrashButtonView.swift in Sources */, + C49C486C2B4704D900BC1456 /* FeatureRolloutsTestAppApp.swift in Sources */, + C49C48952B47207200BC1456 /* ContentView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C49C486F2B4704F300BC1456 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C49C48702B4704F300BC1456 /* FeatureRolloutsTestAppApp.swift in Sources */, + 951D70162B71AD9B00BE7EED /* RemoteConfigButtonView.swift in Sources */, + C49C48992B4720AE00BC1456 /* ContentView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C49C48792B4704F500BC1456 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 951D70152B71AD9B00BE7EED /* RemoteConfigButtonView.swift in Sources */, + C49C487A2B4704F500BC1456 /* FeatureRolloutsTestAppApp.swift in Sources */, + C49C489E2B4722C100BC1456 /* CrashButtonView.swift in Sources */, + C49C489C2B4720DD00BC1456 /* ContentView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + C427C4C22B4603F80088A488 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + C427C4C32B4603F80088A488 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SWIFT_COMPILATION_MODE = wholemodule; + }; + name = Release; + }; + C427C4C52B4603F80088A488 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6D30A6D1F2CE622B6D5D563F /* Pods-FeatureRolloutsTestApp_iOS.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = ""; + DEVELOPMENT_TEAM = ""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 14.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.firebase.config.featureRolloutsTestApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + C427C4C62B4603F80088A488 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2D15DD53784CDDE94D00AB02 /* Pods-FeatureRolloutsTestApp_iOS.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = ""; + DEVELOPMENT_TEAM = ""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; + "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault; + "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 14.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.firebase.config.featureRolloutsTestApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + C49C48632B460FC800BC1456 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 025F972344BB0B489CC052D6 /* Pods-FeatureRolloutsTestApp_Crashlytics_iOS.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = ""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.firebase.config.featureRolloutsTestApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + C49C48642B460FC800BC1456 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8BA72854B19D7A9D9BE15E1D /* Pods-FeatureRolloutsTestApp_Crashlytics_iOS.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = ""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.firebase.config.featureRolloutsTestApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + C49C48752B4704F300BC1456 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AF260B513E38B2528E7B13CC /* Pods-FeatureRolloutsTestApp_RemoteConfig_iOS.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = ""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.firebase.config.featureRolloutsTestApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + C49C48762B4704F300BC1456 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 30BB126FCB8D5F53B5795500 /* Pods-FeatureRolloutsTestApp_RemoteConfig_iOS.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = ""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.firebase.config.featureRolloutsTestApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + C49C487F2B4704F500BC1456 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 2842D338F32EE531C752262E /* Pods-FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = ""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.firebase.config.featureRolloutsTestApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + C49C48802B4704F500BC1456 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 10710CAF870FA7E8D1ABF94C /* Pods-FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_ASSET_PATHS = ""; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.firebase.config.featureRolloutsTestApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + C427C49A2B4603F60088A488 /* Build configuration list for PBXProject "FeatureRolloutsTestApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C427C4C22B4603F80088A488 /* Debug */, + C427C4C32B4603F80088A488 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C427C4C42B4603F80088A488 /* Build configuration list for PBXNativeTarget "FeatureRolloutsTestApp_iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C427C4C52B4603F80088A488 /* Debug */, + C427C4C62B4603F80088A488 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C49C48622B460FC800BC1456 /* Build configuration list for PBXNativeTarget "FeatureRolloutsTestApp_Crashlytics_iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C49C48632B460FC800BC1456 /* Debug */, + C49C48642B460FC800BC1456 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C49C48742B4704F300BC1456 /* Build configuration list for PBXNativeTarget "FeatureRolloutsTestApp_RemoteConfig_iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C49C48752B4704F300BC1456 /* Debug */, + C49C48762B4704F300BC1456 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + C49C487E2B4704F500BC1456 /* Build configuration list for PBXNativeTarget "FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C49C487F2B4704F500BC1456 /* Debug */, + C49C48802B4704F500BC1456 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = C427C4972B4603F60088A488 /* Project object */; +} diff --git a/FirebaseRemoteConfig/Tests/FeatureRolloutsTestApp/FeatureRolloutsTestApp/ContentView.swift b/FirebaseRemoteConfig/Tests/FeatureRolloutsTestApp/FeatureRolloutsTestApp/ContentView.swift new file mode 100644 index 00000000000..cd875f49230 --- /dev/null +++ b/FirebaseRemoteConfig/Tests/FeatureRolloutsTestApp/FeatureRolloutsTestApp/ContentView.swift @@ -0,0 +1,29 @@ +// +// 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. +// + +import SwiftUI + +struct ContentView: View { + var body: some View { + VStack { + Image(systemName: "globe") + .imageScale(.large) + .foregroundStyle(.tint) + Text("Hello, world!") + } + .padding() + } +} diff --git a/FirebaseRemoteConfig/Tests/FeatureRolloutsTestApp/FeatureRolloutsTestApp/FeatureRolloutsTestApp.entitlements b/FirebaseRemoteConfig/Tests/FeatureRolloutsTestApp/FeatureRolloutsTestApp/FeatureRolloutsTestApp.entitlements new file mode 100644 index 00000000000..f2ef3ae0265 --- /dev/null +++ b/FirebaseRemoteConfig/Tests/FeatureRolloutsTestApp/FeatureRolloutsTestApp/FeatureRolloutsTestApp.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-only + + + diff --git a/FirebaseRemoteConfig/Tests/FeatureRolloutsTestApp/FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS/ContentView.swift b/FirebaseRemoteConfig/Tests/FeatureRolloutsTestApp/FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS/ContentView.swift new file mode 100644 index 00000000000..ac68e43b8a8 --- /dev/null +++ b/FirebaseRemoteConfig/Tests/FeatureRolloutsTestApp/FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS/ContentView.swift @@ -0,0 +1,27 @@ +// +// 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. +// + +import Foundation +import SwiftUI + +struct ContentView: View { + var body: some View { + CrashButtonView() + .padding() + RemoteConfigButtonView() + .padding() + } +} diff --git a/FirebaseRemoteConfig/Tests/FeatureRolloutsTestApp/FeatureRolloutsTestApp_Crashlytics_iOS/ContentView.swift b/FirebaseRemoteConfig/Tests/FeatureRolloutsTestApp/FeatureRolloutsTestApp_Crashlytics_iOS/ContentView.swift new file mode 100644 index 00000000000..acb951d35a1 --- /dev/null +++ b/FirebaseRemoteConfig/Tests/FeatureRolloutsTestApp/FeatureRolloutsTestApp_Crashlytics_iOS/ContentView.swift @@ -0,0 +1,26 @@ +// +// 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. +// + +import FirebaseCrashlytics +import Foundation +import SwiftUI + +struct ContentView: View { + var body: some View { + CrashButtonView() + .padding() + } +} diff --git a/FirebaseRemoteConfig/Tests/FeatureRolloutsTestApp/FeatureRolloutsTestApp_RemoteConfig_iOS/ContentView.swift b/FirebaseRemoteConfig/Tests/FeatureRolloutsTestApp/FeatureRolloutsTestApp_RemoteConfig_iOS/ContentView.swift new file mode 100644 index 00000000000..51e437b030a --- /dev/null +++ b/FirebaseRemoteConfig/Tests/FeatureRolloutsTestApp/FeatureRolloutsTestApp_RemoteConfig_iOS/ContentView.swift @@ -0,0 +1,25 @@ +// +// 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. +// + +import Foundation +import SwiftUI + +struct ContentView: View { + var body: some View { + RemoteConfigButtonView() + .padding() + } +} diff --git a/FirebaseRemoteConfig/Tests/FeatureRolloutsTestApp/Podfile b/FirebaseRemoteConfig/Tests/FeatureRolloutsTestApp/Podfile new file mode 100644 index 00000000000..975c45eaa98 --- /dev/null +++ b/FirebaseRemoteConfig/Tests/FeatureRolloutsTestApp/Podfile @@ -0,0 +1,52 @@ +# Uncomment the next line to define a global platform for your project +# platform :ios, '9.0' + +def shared_pods + pod 'FirebaseCore', :path => '../../../' + pod 'FirebaseInstallations', :path => '../../../' + pod 'FirebaseCoreInternal', :path => '../../../' + pod 'FirebaseCoreExtension', :path => '../../../' + pod 'FirebaseRemoteConfigInterop', :path => '../../../' + pod 'FirebasePerformance', :path => '../../../' +end + +target 'FeatureRolloutsTestApp_iOS' do + platform :ios, '11.0' + + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! + + shared_pods +end + +target 'FeatureRolloutsTestApp_Crashlytics_iOS' do + platform :ios, '11.0' + + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! + + shared_pods + pod 'FirebaseCrashlytics', :path => '../../../' +end + +target 'FeatureRolloutsTestApp_RemoteConfig_iOS' do + platform :ios, '11.0' + + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! + + shared_pods + pod 'FirebaseRemoteConfig', :path => '../../../' +end + +target 'FeatureRolloutsTestApp_CrashlyticsRemoteConfig_iOS' do + platform :ios, '11.0' + + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! + + shared_pods + pod 'FirebaseCrashlytics', :path => '../../../' + pod 'FirebaseRemoteConfig', :path => '../../../' +end + diff --git a/FirebaseRemoteConfig/Tests/FeatureRolloutsTestApp/Shared/CrashButtonView.swift b/FirebaseRemoteConfig/Tests/FeatureRolloutsTestApp/Shared/CrashButtonView.swift new file mode 100644 index 00000000000..4fb004e196c --- /dev/null +++ b/FirebaseRemoteConfig/Tests/FeatureRolloutsTestApp/Shared/CrashButtonView.swift @@ -0,0 +1,62 @@ +// +// 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. +// + +import FirebaseCrashlytics +import Foundation +import SwiftUI + +struct CrashButtonView: View { + var body: some View { + var counter = 0 + + NavigationView { + VStack( + alignment: .leading, + spacing: 10 + ) { + Button(action: { + Crashlytics.crashlytics().setUserID("ThisIsABot") + }) { + Text("Set User Id") + } + + Button(action: { + assertionFailure("Throw a Crash") + }) { + Text("Crash") + } + + Button(action: { + Crashlytics.crashlytics().record(error: NSError( + domain: "This is a test non-fatal", + code: 400 + )) + }) { + Text("Record Non-fatal event") + } + + Button(action: { + Crashlytics.crashlytics().setCustomValue(counter, forKey: "counter " + String(counter)) + let i = counter + counter = i + 1 + }) { + Text("Set custom key") + } + } + .navigationTitle("Crashlytics Example") + } + } +} diff --git a/FirebaseRemoteConfig/Tests/FeatureRolloutsTestApp/Shared/FeatureRolloutsTestAppApp.swift b/FirebaseRemoteConfig/Tests/FeatureRolloutsTestApp/Shared/FeatureRolloutsTestAppApp.swift new file mode 100644 index 00000000000..b00e9bc6e6b --- /dev/null +++ b/FirebaseRemoteConfig/Tests/FeatureRolloutsTestApp/Shared/FeatureRolloutsTestAppApp.swift @@ -0,0 +1,31 @@ +// +// 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. +// + +import FirebaseCore +import SwiftUI + +@main +struct FeatureRolloutsTestAppApp: App { + init() { + FirebaseApp.configure() + } + + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/FirebaseRemoteConfig/Tests/FeatureRolloutsTestApp/Shared/RemoteConfigButtonView.swift b/FirebaseRemoteConfig/Tests/FeatureRolloutsTestApp/Shared/RemoteConfigButtonView.swift new file mode 100644 index 00000000000..1391ad16e55 --- /dev/null +++ b/FirebaseRemoteConfig/Tests/FeatureRolloutsTestApp/Shared/RemoteConfigButtonView.swift @@ -0,0 +1,51 @@ +// +// 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. +// + +import FirebaseRemoteConfig +import Foundation +import SwiftUI + +struct RemoteConfigButtonView: View { + @State private var turnOnRealTimeRC = false + let rc = RemoteConfig.remoteConfig() + @RemoteConfigProperty(key: "ios_rollouts", fallback: "unfetched") var iosRollouts: String + + var body: some View { + NavigationView { + VStack( + alignment: .leading, + spacing: 10 + ) { + Button(action: { + rc.fetch() + }) { + Text("Fetch") + } + Button(action: { + rc.activate() + }) { + Text("Activate") + } + Text(iosRollouts) + Toggle("Turn on RealTime RC", isOn: $turnOnRealTimeRC).toggleStyle(.button).tint(.mint) + .onChange(of: self.turnOnRealTimeRC, perform: { value in + rc.addOnConfigUpdateListener { u, e in rc.activate() } + }) + } + .navigationTitle("Remote Config Example") + } + } +} diff --git a/FirebaseRemoteConfig/Tests/Sample/Podfile b/FirebaseRemoteConfig/Tests/Sample/Podfile index 961df70b58e..bfff53e6fea 100644 --- a/FirebaseRemoteConfig/Tests/Sample/Podfile +++ b/FirebaseRemoteConfig/Tests/Sample/Podfile @@ -14,6 +14,7 @@ target 'RemoteConfigSampleApp' do pod 'FirebaseInstallations', :path => '../../../' pod 'FirebaseRemoteConfig', :path => '../../../' pod 'FirebaseABTesting', :path => '../../..' + pod 'FirebaseRemoteConfigInterop', :path => '../../..' # Pods for RemoteConfigSampleApp diff --git a/FirebaseRemoteConfig/Tests/Sample/RemoteConfigSampleApp/ViewController.m b/FirebaseRemoteConfig/Tests/Sample/RemoteConfigSampleApp/ViewController.m index 57c766a9035..e57cc930ea2 100644 --- a/FirebaseRemoteConfig/Tests/Sample/RemoteConfigSampleApp/ViewController.m +++ b/FirebaseRemoteConfig/Tests/Sample/RemoteConfigSampleApp/ViewController.m @@ -21,6 +21,7 @@ #import #import "../../../Sources/Private/FIRRemoteConfig_Private.h" #import "FRCLog.h" +@import FirebaseRemoteConfigInterop; static NSString *const FIRPerfNamespace = @"fireperf"; static NSString *const FIRDefaultFIRAppName = @"__FIRAPP_DEFAULT"; @@ -81,7 +82,8 @@ - (void)viewDidLoad { // TODO(mandard): Add support for deleting and adding namespaces in the app. self.namespacePickerData = - [[NSArray alloc] initWithObjects:FIRNamespaceGoogleMobilePlatform, FIRPerfNamespace, nil]; + [[NSArray alloc] initWithObjects:FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform, + FIRPerfNamespace, nil]; self.appPickerData = [[NSArray alloc] initWithObjects:FIRDefaultFIRAppName, FIRSecondFIRAppName, nil]; self.RCInstances = [[NSMutableDictionary alloc] init]; @@ -91,7 +93,8 @@ - (void)viewDidLoad { if (!self.RCInstances[namespaceString]) { self.RCInstances[namespaceString] = [[NSMutableDictionary alloc] init]; } - if ([namespaceString isEqualToString:FIRNamespaceGoogleMobilePlatform] && + if ([namespaceString + isEqualToString:FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform] && [appString isEqualToString:FIRDefaultFIRAppName]) { self.RCInstances[namespaceString][appString] = [FIRRemoteConfig remoteConfig]; } else { @@ -120,7 +123,7 @@ - (void)viewDidLoad { [alert addAction:defaultAction]; // Add realtime listener for firebase namespace - [self.RCInstances[FIRNamespaceGoogleMobilePlatform][FIRDefaultFIRAppName] + [self.RCInstances[FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform][FIRDefaultFIRAppName] addOnConfigUpdateListener:^(FIRRemoteConfigUpdate *_Nullable update, NSError *_Nullable error) { if (error != nil) { diff --git a/FirebaseRemoteConfig/Tests/SwiftUnit/RemoteConfigInteropTests.swift b/FirebaseRemoteConfig/Tests/SwiftUnit/RemoteConfigInteropTests.swift new file mode 100644 index 00000000000..d4610a03d65 --- /dev/null +++ b/FirebaseRemoteConfig/Tests/SwiftUnit/RemoteConfigInteropTests.swift @@ -0,0 +1,65 @@ +// Copyright 2023 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 FirebaseRemoteConfigInterop +import XCTest + +class MockRCInterop: RemoteConfigInterop { + weak var subscriber: FirebaseRemoteConfigInterop.RolloutsStateSubscriber? + func registerRolloutsStateSubscriber(_ subscriber: FirebaseRemoteConfigInterop + .RolloutsStateSubscriber, + for namespace: String) { + self.subscriber = subscriber + } +} + +class MockRolloutSubscriber: RolloutsStateSubscriber { + var isSubscriberCalled = false + var rolloutsState: RolloutsState? + func rolloutsStateDidChange(_ rolloutsState: FirebaseRemoteConfigInterop.RolloutsState) { + isSubscriberCalled = true + self.rolloutsState = rolloutsState + } +} + +final class RemoteConfigInteropTests: XCTestCase { + let rollouts: RolloutsState = { + let assignment1 = RolloutAssignment( + rolloutId: "rollout_1", + variantId: "control", + templateVersion: 1, + parameterKey: "my_feature", + parameterValue: "false" + ) + let assignment2 = RolloutAssignment( + rolloutId: "rollout_2", + variantId: "enabled", + templateVersion: 123, + parameterKey: "themis_big_feature", + parameterValue: "1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111" + ) + let rollouts = RolloutsState(assignmentList: [assignment1, assignment2]) + return rollouts + }() + + func testRemoteConfigIntegration() throws { + let rcSubscriber = MockRolloutSubscriber() + let rcInterop = MockRCInterop() + rcInterop.registerRolloutsStateSubscriber(rcSubscriber, for: "namespace") + rcInterop.subscriber?.rolloutsStateDidChange(rollouts) + + XCTAssertTrue(rcSubscriber.isSubscriberCalled) + XCTAssertEqual(rcSubscriber.rolloutsState?.assignments.count, 2) + } +} diff --git a/FirebaseRemoteConfig/Tests/Unit/FIRRemoteConfigComponentTest.m b/FirebaseRemoteConfig/Tests/Unit/FIRRemoteConfigComponentTest.m index 077702b7b19..52d56bb3852 100644 --- a/FirebaseRemoteConfig/Tests/Unit/FIRRemoteConfigComponentTest.m +++ b/FirebaseRemoteConfig/Tests/Unit/FIRRemoteConfigComponentTest.m @@ -20,6 +20,7 @@ #import "FirebaseRemoteConfig/Sources/FIRRemoteConfigComponent.h" #import "FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h" #import "FirebaseRemoteConfig/Tests/Unit/RCNTestUtilities.h" +@import FirebaseRemoteConfigInterop; @interface FIRRemoteConfigComponentTest : XCTestCase @end @@ -31,6 +32,7 @@ - (void)tearDown { // Clear out any apps that were called with `configure`. [FIRApp resetApps]; + [FIRRemoteConfigComponent clearAllComponentInstances]; } - (void)testRCInstanceCreationAndCaching { @@ -92,7 +94,8 @@ - (void)testInitialization { } - (void)testRegistersAsLibrary { - XCTAssertEqual([FIRRemoteConfigComponent componentsToRegister].count, 1); + // Now component has two register, one is provider and another one is Interop + XCTAssertEqual([FIRRemoteConfigComponent componentsToRegister].count, 2); // Configure a test FIRApp for fetching an instance of the FIRRemoteConfigProvider. NSString *appName = [self generatedTestAppName]; @@ -101,12 +104,50 @@ - (void)testRegistersAsLibrary { // Attempt to fetch the component and verify it's a valid instance. id provider = FIR_COMPONENT(FIRRemoteConfigProvider, app.container); + id interop = FIR_COMPONENT(FIRRemoteConfigInterop, app.container); XCTAssertNotNil(provider); + XCTAssertNotNil(interop); // Ensure that the instance that comes from the container is cached. id sameProvider = FIR_COMPONENT(FIRRemoteConfigProvider, app.container); + id sameInterop = FIR_COMPONENT(FIRRemoteConfigInterop, app.container); XCTAssertNotNil(sameProvider); + XCTAssertNotNil(sameInterop); XCTAssertEqual(provider, sameProvider); + XCTAssertEqual(interop, sameInterop); + + // Dynamic typing, both prototols are refering to the same component instance + id providerID = provider; + id interopID = interop; + XCTAssertEqualObjects(providerID, interopID); +} + +- (void)testTwoAppsCreateTwoComponents { + NSString *appName = [self generatedTestAppName]; + [FIRApp configureWithName:appName options:[self fakeOptions]]; + FIRApp *app = [FIRApp appNamed:appName]; + + [FIRApp configureWithOptions:[self fakeOptions]]; + FIRApp *defaultApp = [FIRApp defaultApp]; + XCTAssertNotNil(defaultApp); + XCTAssertNotEqualObjects(app, defaultApp); + + id provider = FIR_COMPONENT(FIRRemoteConfigProvider, app.container); + id interop = FIR_COMPONENT(FIRRemoteConfigInterop, app.container); + id defaultAppProvider = + FIR_COMPONENT(FIRRemoteConfigProvider, defaultApp.container); + id defaultAppInterop = + FIR_COMPONENT(FIRRemoteConfigInterop, defaultApp.container); + + id providerID = provider; + id interopID = interop; + id defaultAppProviderID = defaultAppProvider; + id defaultAppInteropID = defaultAppInterop; + + XCTAssertEqualObjects(providerID, interopID); + XCTAssertEqualObjects(defaultAppProviderID, defaultAppInteropID); + // Check two apps get their own component to register + XCTAssertNotEqualObjects(interopID, defaultAppInteropID); } - (void)testThrowsWithEmptyGoogleAppID { diff --git a/FirebaseRemoteConfig/Tests/Unit/RCNConfigContentTest.m b/FirebaseRemoteConfig/Tests/Unit/RCNConfigContentTest.m index d4f33bf0f71..e02f22f2454 100644 --- a/FirebaseRemoteConfig/Tests/Unit/RCNConfigContentTest.m +++ b/FirebaseRemoteConfig/Tests/Unit/RCNConfigContentTest.m @@ -24,6 +24,7 @@ #import "FirebaseRemoteConfig/Sources/RCNConfigDBManager.h" #import "FirebaseRemoteConfig/Sources/RCNConfigValue_Internal.h" #import "FirebaseRemoteConfig/Tests/Unit/RCNTestUtilities.h" +@import FirebaseRemoteConfigInterop; @interface RCNConfigContent (Testing) - (BOOL)checkAndWaitForInitialDatabaseLoad; @@ -44,7 +45,7 @@ - (void)loadMainWithBundleIdentifier:(NSString *)bundleIdentifier dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(justSmallDelay * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ self.isLoadMainCompleted = YES; - handler(YES, nil, nil, nil); + handler(YES, nil, nil, nil, nil); }); } - (void)loadPersonalizationWithCompletionHandler:(RCNDBLoadCompletion)handler { @@ -53,7 +54,7 @@ - (void)loadPersonalizationWithCompletionHandler:(RCNDBLoadCompletion)handler { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(justOtherSmallDelay * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ self.isLoadPersonalizationCompleted = YES; - handler(YES, nil, nil, nil); + handler(YES, nil, nil, nil, nil); }); } @end @@ -62,6 +63,7 @@ @interface RCNConfigContentTest : XCTestCase { NSTimeInterval _expectationTimeout; RCNConfigContent *_configContent; NSString *namespaceApp1, *namespaceApp2; + NSString *_namespaceGoogleMobilePlatform; } @end @@ -70,11 +72,12 @@ @implementation RCNConfigContentTest - (void)setUp { [super setUp]; _expectationTimeout = 1.0; + _namespaceGoogleMobilePlatform = FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform; namespaceApp1 = [NSString - stringWithFormat:@"%@:%@", FIRNamespaceGoogleMobilePlatform, RCNTestsDefaultFIRAppName]; + stringWithFormat:@"%@:%@", _namespaceGoogleMobilePlatform, RCNTestsDefaultFIRAppName]; namespaceApp2 = [NSString - stringWithFormat:@"%@:%@", FIRNamespaceGoogleMobilePlatform, RCNTestsSecondFIRAppName]; + stringWithFormat:@"%@:%@", _namespaceGoogleMobilePlatform, RCNTestsSecondFIRAppName]; _configContent = [[RCNConfigContent alloc] initWithDBManager:nil]; @@ -129,14 +132,14 @@ - (void)testUpdateConfigContentWithResponse { NSDictionary *entries = @{@"key1" : @"value1", @"key2" : @"value2"}; [configToSet setValue:entries forKey:@"entries"]; [_configContent updateConfigContentWithResponse:configToSet - forNamespace:FIRNamespaceGoogleMobilePlatform]; + forNamespace:_namespaceGoogleMobilePlatform]; NSDictionary *fetchedConfig = _configContent.fetchedConfig; - XCTAssertNotNil(fetchedConfig[FIRNamespaceGoogleMobilePlatform][@"key1"]); - XCTAssertEqualObjects([fetchedConfig[FIRNamespaceGoogleMobilePlatform][@"key1"] stringValue], + XCTAssertNotNil(fetchedConfig[_namespaceGoogleMobilePlatform][@"key1"]); + XCTAssertEqualObjects([fetchedConfig[_namespaceGoogleMobilePlatform][@"key1"] stringValue], @"value1"); - XCTAssertNotNil(fetchedConfig[FIRNamespaceGoogleMobilePlatform][@"key2"]); - XCTAssertEqualObjects([fetchedConfig[FIRNamespaceGoogleMobilePlatform][@"key2"] stringValue], + XCTAssertNotNil(fetchedConfig[_namespaceGoogleMobilePlatform][@"key2"]); + XCTAssertEqualObjects([fetchedConfig[_namespaceGoogleMobilePlatform][@"key2"] stringValue], @"value2"); } @@ -147,20 +150,20 @@ - (void)testUpdateConfigContentWithStatusUpdateWithDifferentKeys { NSDictionary *entries = @{@"key1" : @"value1"}; [configToSet setValue:entries forKey:@"entries"]; [_configContent updateConfigContentWithResponse:configToSet - forNamespace:FIRNamespaceGoogleMobilePlatform]; + forNamespace:_namespaceGoogleMobilePlatform]; configToSet = [[NSMutableDictionary alloc] initWithObjectsAndKeys:@"UPDATE", @"state", nil]; entries = @{@"key2" : @"value2", @"key3" : @"value3"}; [configToSet setValue:entries forKey:@"entries"]; [_configContent updateConfigContentWithResponse:configToSet - forNamespace:FIRNamespaceGoogleMobilePlatform]; + forNamespace:_namespaceGoogleMobilePlatform]; NSDictionary *fetchedConfig = _configContent.fetchedConfig; - XCTAssertNil(fetchedConfig[FIRNamespaceGoogleMobilePlatform][@"key1"]); - XCTAssertNotNil(fetchedConfig[FIRNamespaceGoogleMobilePlatform][@"key2"]); - XCTAssertEqualObjects([fetchedConfig[FIRNamespaceGoogleMobilePlatform][@"key2"] stringValue], + XCTAssertNil(fetchedConfig[_namespaceGoogleMobilePlatform][@"key1"]); + XCTAssertNotNil(fetchedConfig[_namespaceGoogleMobilePlatform][@"key2"]); + XCTAssertEqualObjects([fetchedConfig[_namespaceGoogleMobilePlatform][@"key2"] stringValue], @"value2"); - XCTAssertNotNil(fetchedConfig[FIRNamespaceGoogleMobilePlatform][@"key3"]); - XCTAssertEqualObjects([fetchedConfig[FIRNamespaceGoogleMobilePlatform][@"key3"] stringValue], + XCTAssertNotNil(fetchedConfig[_namespaceGoogleMobilePlatform][@"key3"]); + XCTAssertEqualObjects([fetchedConfig[_namespaceGoogleMobilePlatform][@"key3"] stringValue], @"value3"); } @@ -332,7 +335,9 @@ - (void)testConfigUpdate_noChange_emptyResponse { // populate fetched config NSMutableDictionary *fetchResponse = - [self createFetchResponseWithConfigEntries:@{@"key1" : @"value1"} p13nMetadata:nil]; + [self createFetchResponseWithConfigEntries:@{@"key1" : @"value1"} + p13nMetadata:nil + rolloutMetadata:nil]; [_configContent updateConfigContentWithResponse:fetchResponse forNamespace:namespace]; // active config is the same as fetched config @@ -365,7 +370,8 @@ - (void)testConfigUpdate_paramAdded_returnsNewKey { // fetch response has new param NSMutableDictionary *fetchResponse = [self createFetchResponseWithConfigEntries:@{@"key1" : @"value1", newParam : @"value2"} - p13nMetadata:nil]; + p13nMetadata:nil + rolloutMetadata:nil]; [_configContent updateConfigContentWithResponse:fetchResponse forNamespace:namespace]; FIRRemoteConfigUpdate *update = [_configContent getConfigUpdateForNamespace:namespace]; @@ -391,7 +397,9 @@ - (void)testConfigUpdate_paramValueChanged_returnsUpdatedKey { // fetch response contains updated value NSMutableDictionary *fetchResponse = - [self createFetchResponseWithConfigEntries:@{existingParam : updatedValue} p13nMetadata:nil]; + [self createFetchResponseWithConfigEntries:@{existingParam : updatedValue} + p13nMetadata:nil + rolloutMetadata:nil]; [_configContent updateConfigContentWithResponse:fetchResponse forNamespace:namespace]; FIRRemoteConfigUpdate *update = [_configContent getConfigUpdateForNamespace:namespace]; @@ -417,7 +425,9 @@ - (void)testConfigUpdate_paramDeleted_returnsDeletedKey { // fetch response does not contain existing param NSMutableDictionary *fetchResponse = - [self createFetchResponseWithConfigEntries:@{newParam : value1} p13nMetadata:nil]; + [self createFetchResponseWithConfigEntries:@{newParam : value1} + p13nMetadata:nil + rolloutMetadata:nil]; [_configContent updateConfigContentWithResponse:fetchResponse forNamespace:namespace]; FIRRemoteConfigUpdate *update = [_configContent getConfigUpdateForNamespace:namespace]; @@ -437,7 +447,8 @@ - (void)testConfigUpdate_p13nMetadataUpdated_returnsKey { // popuate fetched config NSMutableDictionary *fetchResponse = [self createFetchResponseWithConfigEntries:@{existingParam : value1} - p13nMetadata:@{existingParam : oldMetadata}]; + p13nMetadata:@{existingParam : oldMetadata} + rolloutMetadata:nil]; [_configContent updateConfigContentWithResponse:fetchResponse forNamespace:namespace]; // populate active config with the same content @@ -461,6 +472,148 @@ - (void)testConfigUpdate_p13nMetadataUpdated_returnsKey { XCTAssertTrue([[update updatedKeys] containsObject:existingParam]); } +- (void)testConfigUpdate_rolloutMetadataUpdated_returnsKey { + NSString *namespace = @"test_namespace"; + NSString *key1 = @"key1"; + NSString *key2 = @"kety2"; + NSString *value = @"value"; + NSString *rolloutId1 = @"1"; + NSString *rolloutId2 = @"2"; + NSString *variantId1 = @"A"; + NSString *variantId2 = @"B"; + NSArray *rolloutMetadata = @[ @{ + RCNFetchResponseKeyRolloutID : rolloutId1, + RCNFetchResponseKeyVariantID : variantId1, + RCNFetchResponseKeyAffectedParameterKeys : @[ key1 ] + } ]; + // Update rolltou metadata + NSArray *updatedRolloutMetadata = @[ + @{ + RCNFetchResponseKeyRolloutID : rolloutId1, + RCNFetchResponseKeyVariantID : variantId2, + RCNFetchResponseKeyAffectedParameterKeys : @[ key1 ] + }, + @{ + RCNFetchResponseKeyRolloutID : rolloutId2, + RCNFetchResponseKeyVariantID : variantId1, + RCNFetchResponseKeyAffectedParameterKeys : @[ key2 ] + }, + ]; + // Populate fetched config + NSMutableDictionary *fetchResponse = [self createFetchResponseWithConfigEntries:@{key1 : value} + p13nMetadata:nil + rolloutMetadata:rolloutMetadata]; + [_configContent updateConfigContentWithResponse:fetchResponse forNamespace:namespace]; + // populate active config with the same content + [_configContent activateRolloutMetadata:nil]; + XCTAssertEqualObjects(rolloutMetadata, _configContent.activeRolloutMetadata); + FIRRemoteConfigValue *rcValue = + [[FIRRemoteConfigValue alloc] initWithData:[value dataUsingEncoding:NSUTF8StringEncoding] + source:FIRRemoteConfigSourceRemote]; + + NSDictionary *namespaceToConfig = @{namespace : @{key1 : rcValue}}; + [_configContent copyFromDictionary:namespaceToConfig + toSource:RCNDBSourceActive + forNamespace:namespace]; + // New fetch response has updated rollout metadata + [fetchResponse setValue:updatedRolloutMetadata forKey:RCNFetchResponseKeyRolloutMetadata]; + [_configContent updateConfigContentWithResponse:fetchResponse forNamespace:namespace]; + + FIRRemoteConfigUpdate *update = [_configContent getConfigUpdateForNamespace:namespace]; + + XCTAssertTrue([update updatedKeys].count == 2); + XCTAssertTrue([[update updatedKeys] containsObject:key1]); + XCTAssertTrue([[update updatedKeys] containsObject:key2]); +} + +- (void)testConfigUpdate_rolloutMetadataDeleted_returnsKey { + NSString *namespace = @"test_namespace"; + NSString *key1 = @"key1"; + NSString *key2 = @"key2"; + NSString *value = @"value"; + NSString *rolloutId1 = @"1"; + NSString *variantId1 = @"A"; + NSArray *rolloutMetadata = @[ @{ + RCNFetchResponseKeyRolloutID : rolloutId1, + RCNFetchResponseKeyVariantID : variantId1, + RCNFetchResponseKeyAffectedParameterKeys : @[ key1, key2 ] + } ]; + // Remove key2 from rollout metadata + NSArray *updatedRolloutMetadata = @[ @{ + RCNFetchResponseKeyRolloutID : rolloutId1, + RCNFetchResponseKeyVariantID : variantId1, + RCNFetchResponseKeyAffectedParameterKeys : @[ key1 ] + } ]; + // Populate fetched config + NSMutableDictionary *fetchResponse = + [self createFetchResponseWithConfigEntries:@{key1 : value, key2 : value} + p13nMetadata:nil + rolloutMetadata:rolloutMetadata]; + [_configContent updateConfigContentWithResponse:fetchResponse forNamespace:namespace]; + // populate active config with the same content + [_configContent activateRolloutMetadata:nil]; + XCTAssertEqualObjects(rolloutMetadata, _configContent.activeRolloutMetadata); + FIRRemoteConfigValue *rcValue = + [[FIRRemoteConfigValue alloc] initWithData:[value dataUsingEncoding:NSUTF8StringEncoding] + source:FIRRemoteConfigSourceRemote]; + + NSDictionary *namespaceToConfig = @{namespace : @{key1 : rcValue, key2 : rcValue}}; + [_configContent copyFromDictionary:namespaceToConfig + toSource:RCNDBSourceActive + forNamespace:namespace]; + // New fetch response has updated rollout metadata + [fetchResponse setValue:updatedRolloutMetadata forKey:RCNFetchResponseKeyRolloutMetadata]; + [_configContent updateConfigContentWithResponse:fetchResponse forNamespace:namespace]; + + FIRRemoteConfigUpdate *update = [_configContent getConfigUpdateForNamespace:namespace]; + + XCTAssertTrue([update updatedKeys].count == 1); + XCTAssertTrue([[update updatedKeys] containsObject:key2]); +} + +- (void)testConfigUpdate_rolloutMetadataDeletedAll_returnsKey { + NSString *namespace = @"test_namespace"; + NSString *key = @"key"; + NSString *value = @"value"; + NSString *rolloutId1 = @"1"; + NSString *variantId1 = @"A"; + NSArray *rolloutMetadata = @[ @{ + RCNFetchResponseKeyRolloutID : rolloutId1, + RCNFetchResponseKeyVariantID : variantId1, + RCNFetchResponseKeyAffectedParameterKeys : @[ key ] + } ]; + // Populate fetched config + NSMutableDictionary *fetchResponse = [self createFetchResponseWithConfigEntries:@{key : value} + p13nMetadata:nil + rolloutMetadata:rolloutMetadata]; + [_configContent updateConfigContentWithResponse:fetchResponse forNamespace:namespace]; + // populate active config with the same content + [_configContent activateRolloutMetadata:nil]; + XCTAssertEqualObjects(rolloutMetadata, _configContent.activeRolloutMetadata); + FIRRemoteConfigValue *rcValue = + [[FIRRemoteConfigValue alloc] initWithData:[value dataUsingEncoding:NSUTF8StringEncoding] + source:FIRRemoteConfigSourceRemote]; + + NSDictionary *namespaceToConfig = @{namespace : @{key : rcValue}}; + [_configContent copyFromDictionary:namespaceToConfig + toSource:RCNDBSourceActive + forNamespace:namespace]; + + // New fetch response has updated rollout metadata + NSMutableDictionary *updateFetchResponse = + [self createFetchResponseWithConfigEntries:@{key : value} + p13nMetadata:nil + rolloutMetadata:nil]; + [_configContent updateConfigContentWithResponse:updateFetchResponse forNamespace:namespace]; + + FIRRemoteConfigUpdate *update = [_configContent getConfigUpdateForNamespace:namespace]; + [_configContent activateRolloutMetadata:nil]; + + XCTAssertTrue([update updatedKeys].count == 1); + XCTAssertTrue([[update updatedKeys] containsObject:key]); + XCTAssertTrue(_configContent.activeRolloutMetadata.count == 0); +} + - (void)testConfigUpdate_valueSourceChanged_returnsKey { NSString *namespace = @"test_namespace"; NSString *existingParam = @"key1"; @@ -477,7 +630,9 @@ - (void)testConfigUpdate_valueSourceChanged_returnsKey { // fetch response contains same key->value NSMutableDictionary *fetchResponse = - [self createFetchResponseWithConfigEntries:@{existingParam : value1} p13nMetadata:nil]; + [self createFetchResponseWithConfigEntries:@{existingParam : value1} + p13nMetadata:nil + rolloutMetadata:nil]; [_configContent updateConfigContentWithResponse:fetchResponse forNamespace:namespace]; FIRRemoteConfigUpdate *update = [_configContent getConfigUpdateForNamespace:namespace]; @@ -489,14 +644,18 @@ - (void)testConfigUpdate_valueSourceChanged_returnsKey { #pragma mark - Test Helpers - (NSMutableDictionary *)createFetchResponseWithConfigEntries:(NSDictionary *)config - p13nMetadata:(NSDictionary *)metadata { + p13nMetadata:(NSDictionary *)p13nMetadata + rolloutMetadata:(NSArray *)rolloutMetadata { NSMutableDictionary *fetchResponse = [[NSMutableDictionary alloc] initWithObjectsAndKeys:RCNFetchResponseKeyStateUpdate, RCNFetchResponseKeyState, nil]; if (config) { [fetchResponse setValue:config forKey:RCNFetchResponseKeyEntries]; } - if (metadata) { - [fetchResponse setValue:metadata forKey:RCNFetchResponseKeyPersonalizationMetadata]; + if (p13nMetadata) { + [fetchResponse setValue:p13nMetadata forKey:RCNFetchResponseKeyPersonalizationMetadata]; + } + if (rolloutMetadata) { + [fetchResponse setValue:rolloutMetadata forKey:RCNFetchResponseKeyRolloutMetadata]; } return fetchResponse; } diff --git a/FirebaseRemoteConfig/Tests/Unit/RCNConfigDBManagerTest.m b/FirebaseRemoteConfig/Tests/Unit/RCNConfigDBManagerTest.m index 23705be1abf..773af690935 100644 --- a/FirebaseRemoteConfig/Tests/Unit/RCNConfigDBManagerTest.m +++ b/FirebaseRemoteConfig/Tests/Unit/RCNConfigDBManagerTest.m @@ -83,8 +83,8 @@ - (void)testV1NamespaceMigrationToV2Namespace { BOOL loadSuccess, NSDictionary *> *fetchedConfig, NSDictionary *> *activeConfig, - NSDictionary *> - *defaultConfig) { + NSDictionary *> *defaultConfig, + NSDictionary *unusedRolloutMetadata) { XCTAssertTrue(loadSuccess); NSString *fullyQualifiedNamespace = [NSString stringWithFormat:@"%@:%@", namespace_p, kFIRDefaultAppName]; @@ -125,18 +125,19 @@ - (void)testWriteAndLoadMainTableResult { XCTAssertTrue(success); if (count == 100) { // check DB read correctly - [self->_DBManager loadMainWithBundleIdentifier:bundleIdentifier - completionHandler:^(BOOL success, NSDictionary *fetchedConfig, - NSDictionary *activeConfig, - NSDictionary *defaultConfig) { - NSMutableDictionary *res = [fetchedConfig mutableCopy]; - XCTAssertTrue(success); - FIRRemoteConfigValue *value = res[namespace_p][@"key100"]; - XCTAssertEqualObjects(value.stringValue, @"value100"); - if (success) { - [loadConfigContentExpectation fulfill]; - } - }]; + [self->_DBManager + loadMainWithBundleIdentifier:bundleIdentifier + completionHandler:^(BOOL success, NSDictionary *fetchedConfig, + NSDictionary *activeConfig, NSDictionary *defaultConfig, + NSDictionary *unusedRolloutMetadata) { + NSMutableDictionary *res = [fetchedConfig mutableCopy]; + XCTAssertTrue(success); + FIRRemoteConfigValue *value = res[namespace_p][@"key100"]; + XCTAssertEqualObjects(value.stringValue, @"value100"); + if (success) { + [loadConfigContentExpectation fulfill]; + } + }]; } }; NSString *value = [NSString stringWithFormat:@"value%d", i]; @@ -382,7 +383,8 @@ - (void)testDeleteParamAndLoadMainTable { [self->_DBManager loadMainWithBundleIdentifier:bundleIdentifier completionHandler:^(BOOL success, NSDictionary *fetchedConfig, - NSDictionary *activeConfig, NSDictionary *defaultConfig) { + NSDictionary *activeConfig, NSDictionary *defaultConfig, + NSDictionary *unusedRolloutMetadata) { NSMutableDictionary *res = [activeConfig mutableCopy]; XCTAssertTrue(success); FIRRemoteConfigValue *value = res[namespaceToDelete][@"keyToDelete"]; @@ -403,7 +405,8 @@ - (void)testDeleteParamAndLoadMainTable { [self->_DBManager loadMainWithBundleIdentifier:bundleIdentifier completionHandler:^(BOOL success, NSDictionary *fetchedConfig, - NSDictionary *activeConfig, NSDictionary *defaultConfig) { + NSDictionary *activeConfig, NSDictionary *defaultConfig, + NSDictionary *unusedRolloutMetadata) { NSMutableDictionary *res = [activeConfig mutableCopy]; XCTAssertTrue(success); FIRRemoteConfigValue *value2 = res[namespaceToKeep][@"keyToRetain"]; @@ -587,6 +590,136 @@ - (void)testWriteAndLoadMetadataMultipleTimes { [self waitForExpectationsWithTimeout:_expectionTimeout handler:nil]; } +- (void)testWriteAndLoadFetchedAndActiveRollout { + XCTestExpectation *writeAndLoadFetchedRolloutExpectation = + [self expectationWithDescription:@"Write and load rollout in database successfully"]; + + NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier; + + NSArray *fetchedRollout = @[ + @{ + @"rollout_id" : @"1", + @"variant_id" : @"B", + @"affected_parameter_keys" : @[ @"key_1", @"key_2" ] + }, + @{ + @"rollout_id" : @"2", + @"variant_id" : @"1", + @"affected_parameter_keys" : @[ @"key_1", @"key_3" ] + } + ]; + + NSArray *activeRollout = @[ + @{ + @"rollout_id" : @"1", + @"variant_id" : @"B", + @"affected_parameter_keys" : @[ @"key_1", @"key_2" ] + }, + @{ + @"rollout_id" : @"3", + @"variant_id" : @"a", + @"affected_parameter_keys" : @[ @"key_1", @"key_3" ] + } + ]; + + RCNDBCompletion writeRolloutCompletion = ^(BOOL success, NSDictionary *result) { + XCTAssertTrue(success); + RCNDBLoadCompletion loadCompletion = ^( + BOOL success, NSDictionary *unusedFetchedConfig, NSDictionary *unusedActiveConfig, + NSDictionary *unusedDefaultConfig, NSDictionary *rolloutMetadata) { + XCTAssertTrue(success); + XCTAssertNotNil(rolloutMetadata[@RCNRolloutTableKeyFetchedMetadata]); + XCTAssertEqualObjects(fetchedRollout, rolloutMetadata[@RCNRolloutTableKeyFetchedMetadata]); + XCTAssertNotNil(rolloutMetadata[@RCNRolloutTableKeyActiveMetadata]); + XCTAssertEqualObjects(activeRollout, rolloutMetadata[@RCNRolloutTableKeyActiveMetadata]); + + [writeAndLoadFetchedRolloutExpectation fulfill]; + }; + [self->_DBManager loadMainWithBundleIdentifier:bundleIdentifier + completionHandler:loadCompletion]; + }; + [_DBManager insertOrUpdateRolloutTableWithKey:@RCNRolloutTableKeyFetchedMetadata + value:fetchedRollout + completionHandler:nil]; + [_DBManager insertOrUpdateRolloutTableWithKey:@RCNRolloutTableKeyActiveMetadata + value:activeRollout + completionHandler:writeRolloutCompletion]; + + [self waitForExpectationsWithTimeout:_expectionTimeout handler:nil]; +} + +- (void)testUpdateAndLoadRollout { + XCTestExpectation *updateAndLoadFetchedRolloutExpectation = + [self expectationWithDescription:@"Update and load rollout in database successfully"]; + + NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier; + + NSArray *fetchedRollout = @[ @{ + @"rollout_id" : @"1", + @"variant_id" : @"B", + @"affected_parameter_keys" : @[ @"key_1", @"key_2" ] + } ]; + + NSArray *updatedFetchedRollout = @[ + @{ + @"rollout_id" : @"1", + @"variant_id" : @"B", + @"affected_parameter_keys" : @[ @"key_1", @"key_2" ] + }, + @{ + @"rollout_id" : @"2", + @"variant_id" : @"1", + @"affected_parameter_keys" : @[ @"key_1", @"key_3" ] + } + ]; + + RCNDBCompletion writeRolloutCompletion = ^(BOOL success, NSDictionary *result) { + XCTAssertTrue(success); + RCNDBLoadCompletion loadCompletion = + ^(BOOL success, NSDictionary *unusedFetchedConfig, NSDictionary *unusedActiveConfig, + NSDictionary *unusedDefaultConfig, NSDictionary *rolloutMetadata) { + XCTAssertTrue(success); + XCTAssertNotNil(rolloutMetadata[@RCNRolloutTableKeyFetchedMetadata]); + XCTAssertEqualObjects(updatedFetchedRollout, + rolloutMetadata[@RCNRolloutTableKeyFetchedMetadata]); + + [updateAndLoadFetchedRolloutExpectation fulfill]; + }; + [self->_DBManager loadMainWithBundleIdentifier:bundleIdentifier + completionHandler:loadCompletion]; + }; + [_DBManager insertOrUpdateRolloutTableWithKey:@RCNRolloutTableKeyFetchedMetadata + value:fetchedRollout + completionHandler:nil]; + [_DBManager insertOrUpdateRolloutTableWithKey:@RCNRolloutTableKeyFetchedMetadata + value:updatedFetchedRollout + completionHandler:writeRolloutCompletion]; + + [self waitForExpectationsWithTimeout:_expectionTimeout handler:nil]; +} +- (void)testLoadEmptyRollout { + XCTestExpectation *updateAndLoadFetchedRolloutExpectation = + [self expectationWithDescription:@"Load empty rollout in database successfully"]; + + NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier; + + NSArray *emptyResult = [[NSArray alloc] init]; + + RCNDBLoadCompletion loadCompletion = + ^(BOOL success, NSDictionary *unusedFetchedConfig, NSDictionary *unusedActiveConfig, + NSDictionary *unusedDefaultConfig, NSDictionary *rolloutMetadata) { + XCTAssertTrue(success); + XCTAssertNotNil(rolloutMetadata[@RCNRolloutTableKeyFetchedMetadata]); + XCTAssertEqualObjects(emptyResult, rolloutMetadata[@RCNRolloutTableKeyFetchedMetadata]); + XCTAssertNotNil(rolloutMetadata[@RCNRolloutTableKeyActiveMetadata]); + XCTAssertEqualObjects(emptyResult, rolloutMetadata[@RCNRolloutTableKeyActiveMetadata]); + + [updateAndLoadFetchedRolloutExpectation fulfill]; + }; + [self->_DBManager loadMainWithBundleIdentifier:bundleIdentifier completionHandler:loadCompletion]; + [self waitForExpectationsWithTimeout:_expectionTimeout handler:nil]; +} + - (void)testUpdateAndloadLastFetchStatus { XCTestExpectation *updateAndLoadMetadataExpectation = [self expectationWithDescription:@"Update and load last fetch status in database successfully."]; diff --git a/FirebaseRemoteConfig/Tests/Unit/RCNConfigTest.m b/FirebaseRemoteConfig/Tests/Unit/RCNConfigTest.m index 2a5bd7c67c9..9acb62e0717 100644 --- a/FirebaseRemoteConfig/Tests/Unit/RCNConfigTest.m +++ b/FirebaseRemoteConfig/Tests/Unit/RCNConfigTest.m @@ -23,6 +23,7 @@ #import "FirebaseRemoteConfig/Sources/RCNConfigExperiment.h" #import "FirebaseRemoteConfig/Sources/RCNConfigValue_Internal.h" #import "FirebaseRemoteConfig/Tests/Unit/RCNTestUtilities.h" +@import FirebaseRemoteConfigInterop; static NSString *const RCNFakeSenderID = @"855865492447"; static NSString *const RCNFakeToken = @"ctToAh17Exk:" @@ -48,6 +49,7 @@ @interface RCNConfigTest : XCTestCase { RCNConfigExperiment *_experiment; RCNConfigFetch *_configFetch; dispatch_queue_t _queue; + NSString *_namespaceGoogleMobilePlatform; } @end @@ -66,9 +68,10 @@ - (void)setUp { experiment:_experiment queue:_queue]; _configFetch = OCMPartialMock(fetcher); + _namespaceGoogleMobilePlatform = FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform; // Fake a response with a default namespace and a custom namespace. NSDictionary *namespaceToConfig = @{ - FIRNamespaceGoogleMobilePlatform : @{@"key1" : @"value1", @"key2" : @"value2"}, + _namespaceGoogleMobilePlatform : @{@"key1" : @"value1", @"key2" : @"value2"}, FIRNamespaceGooglePlayPlatform : @{@"playerID" : @"36", @"gameLevel" : @"87"}, }; _response = @@ -149,19 +152,19 @@ - (void)testFetchAllConfigsSuccessfully { XCTAssertNotNil(result); [self checkConfigResult:result - withNamespace:FIRNamespaceGoogleMobilePlatform + withNamespace:_namespaceGoogleMobilePlatform key:@"key1" value:@"value1"]; [self checkConfigResult:result - withNamespace:FIRNamespaceGoogleMobilePlatform + withNamespace:_namespaceGoogleMobilePlatform key:@"key2" value:@"value2"]; [self checkConfigResult:result - withNamespace:FIRNamespaceGooglePlayPlatform + withNamespace:_namespaceGoogleMobilePlatform key:@"playerID" value:@"36"]; [self checkConfigResult:result - withNamespace:FIRNamespaceGooglePlayPlatform + withNamespace:_namespaceGoogleMobilePlatform key:@"gameLevel" value:@"87"]; XCTAssertEqual(self->_settings.expirationInSeconds, 43200, @@ -200,11 +203,11 @@ - (void)testFetchConfigInCachedResults { NSDictionary *result = self->_configContent.fetchedConfig; XCTAssertNotNil(result); [self checkConfigResult:result - withNamespace:FIRNamespaceGoogleMobilePlatform + withNamespace:_namespaceGoogleMobilePlatform key:@"key1" value:@"value1"]; [self checkConfigResult:result - withNamespace:FIRNamespaceGoogleMobilePlatform + withNamespace:_namespaceGoogleMobilePlatform key:@"key2" value:@"value2"]; @@ -246,19 +249,19 @@ - (void)testFetchFailedWithCachedResult { XCTAssertNotNil(result); [self checkConfigResult:result - withNamespace:FIRNamespaceGoogleMobilePlatform + withNamespace:_namespaceGoogleMobilePlatform key:@"key1" value:@"value1"]; [self checkConfigResult:result - withNamespace:FIRNamespaceGoogleMobilePlatform + withNamespace:_namespaceGoogleMobilePlatform key:@"key2" value:@"value2"]; [self checkConfigResult:result - withNamespace:FIRNamespaceGooglePlayPlatform + withNamespace:_namespaceGoogleMobilePlatform key:@"playerID" value:@"36"]; [self checkConfigResult:result - withNamespace:FIRNamespaceGooglePlayPlatform + withNamespace:_namespaceGoogleMobilePlatform key:@"gameLevel" value:@"87"]; @@ -340,19 +343,19 @@ - (void)testFetchThrottledWithStaledCachedResult { NSDictionary *result = self->_configContent.fetchedConfig; XCTAssertNotNil(result); [self checkConfigResult:result - withNamespace:FIRNamespaceGoogleMobilePlatform + withNamespace:_namespaceGoogleMobilePlatform key:@"key1" value:@"value1"]; [self checkConfigResult:result - withNamespace:FIRNamespaceGoogleMobilePlatform + withNamespace:_namespaceGoogleMobilePlatform key:@"key2" value:@"value2"]; [self checkConfigResult:result - withNamespace:FIRNamespaceGooglePlayPlatform + withNamespace:_namespaceGoogleMobilePlatform key:@"playerID" value:@"36"]; [self checkConfigResult:result - withNamespace:FIRNamespaceGooglePlayPlatform + withNamespace:_namespaceGoogleMobilePlatform key:@"gameLevel" value:@"87"]; XCTAssertEqual( diff --git a/FirebaseRemoteConfig/Tests/Unit/RCNInstanceIDTest.m b/FirebaseRemoteConfig/Tests/Unit/RCNInstanceIDTest.m index 5d1b28fb61d..cbbcd0a91bd 100644 --- a/FirebaseRemoteConfig/Tests/Unit/RCNInstanceIDTest.m +++ b/FirebaseRemoteConfig/Tests/Unit/RCNInstanceIDTest.m @@ -29,6 +29,7 @@ #import #import "FirebaseCore/Extension/FirebaseCoreInternal.h" #import "FirebaseInstallations/Source/Library/Private/FirebaseInstallationsInternal.h" +@import FirebaseRemoteConfigInterop; @interface RCNConfigFetch (ForTest) - (instancetype)initWithContent:(RCNConfigContent *)content @@ -136,7 +137,8 @@ - (void)setUpConfigMock { case RCNTestRCInstanceSecondApp: currentAppName = RCNTestsSecondFIRAppName; currentOptions = [self secondAppOptions]; - currentNamespace = FIRNamespaceGoogleMobilePlatform; + currentNamespace = FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform; + ; break; case RCNTestRCInstanceDefault: default: diff --git a/FirebaseRemoteConfig/Tests/Unit/RCNRemoteConfigTest.m b/FirebaseRemoteConfig/Tests/Unit/RCNRemoteConfigTest.m index e1a23b5a695..38cef18cdb3 100644 --- a/FirebaseRemoteConfig/Tests/Unit/RCNRemoteConfigTest.m +++ b/FirebaseRemoteConfig/Tests/Unit/RCNRemoteConfigTest.m @@ -18,6 +18,7 @@ #import #import +#import "FirebaseRemoteConfig/Sources/FIRRemoteConfigComponent.h" #import "FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h" #import "FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h" #import "FirebaseRemoteConfig/Sources/Public/FirebaseRemoteConfig/FIRRemoteConfig.h" @@ -31,6 +32,9 @@ #import #import "FirebaseCore/Extension/FirebaseCoreInternal.h" +@import FirebaseRemoteConfigInterop; + +@protocol FIRRolloutsStateSubscriber; @interface RCNConfigFetch (ForTest) - (instancetype)initWithContent:(RCNConfigContent *)content @@ -130,6 +134,7 @@ @interface RCNRemoteConfigTest : XCTestCase { NSTimeInterval _checkCompletionTimeout; NSMutableArray *_configInstances; NSMutableArray *> *_entries; + NSArray *_rolloutMetadata; NSMutableArray *> *_response; NSMutableArray *_responseData; NSMutableArray *_URLResponse; @@ -145,6 +150,7 @@ @interface RCNRemoteConfigTest : XCTestCase { NSString *_fullyQualifiedNamespace; RCNConfigSettings *_settings; dispatch_queue_t _queue; + NSString *_namespaceGoogleMobilePlatform; } @end @@ -180,6 +186,7 @@ - (void)setUp { _URLResponse = [[NSMutableArray alloc] initWithCapacity:3]; _configFetch = [[NSMutableArray alloc] initWithCapacity:3]; _configRealtime = [[NSMutableArray alloc] initWithCapacity:3]; + _namespaceGoogleMobilePlatform = FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform; // Populate the default, second app, second namespace instances. for (int i = 0; i < RCNTestRCNumTotalInstances; i++) { @@ -204,7 +211,7 @@ - (void)setUp { case RCNTestRCInstanceSecondApp: currentAppName = RCNTestsSecondFIRAppName; currentOptions = [self secondAppOptions]; - currentNamespace = FIRNamespaceGoogleMobilePlatform; + currentNamespace = _namespaceGoogleMobilePlatform; break; case RCNTestRCInstanceDefault: default: @@ -259,7 +266,17 @@ __unsafe_unretained void (^handler)(FIRRemoteConfigFetchStatus status, updateCompletionHandler:nil]; }); - _response[i] = @{@"state" : @"UPDATE", @"entries" : _entries[i]}; + _rolloutMetadata = @[ @{ + RCNFetchResponseKeyRolloutID : @"1", + RCNFetchResponseKeyVariantID : @"0", + RCNFetchResponseKeyAffectedParameterKeys : @[ _entries[i].allKeys[0] ] + } ]; + + _response[i] = @{ + @"state" : @"UPDATE", + @"entries" : _entries[i], + RCNFetchResponseKeyRolloutMetadata : _rolloutMetadata + }; _responseData[i] = [NSJSONSerialization dataWithJSONObject:_response[i] options:0 error:nil]; @@ -286,6 +303,7 @@ __unsafe_unretained void (^handler)(FIRRemoteConfigFetchStatus status, - (void)tearDown { [_DBManager removeDatabaseOnDatabaseQueueAtPath:_DBPath]; + [FIRRemoteConfigComponent clearAllComponentInstances]; [[NSUserDefaults standardUserDefaults] removePersistentDomainForName:_userDefaultsSuiteName]; [_DBManagerMock stopMocking]; _DBManagerMock = nil; @@ -594,7 +612,7 @@ - (void)testFetchConfigsFailed { case RCNTestRCInstanceSecondApp: currentAppName = RCNTestsSecondFIRAppName; currentOptions = [self secondAppOptions]; - currentNamespace = FIRNamespaceGoogleMobilePlatform; + currentNamespace = _namespaceGoogleMobilePlatform; break; case RCNTestRCInstanceDefault: default: @@ -707,7 +725,7 @@ - (void)testFetchConfigsFailedErrorNoNetwork { case RCNTestRCInstanceSecondApp: currentAppName = RCNTestsSecondFIRAppName; currentOptions = [self secondAppOptions]; - currentNamespace = FIRNamespaceGoogleMobilePlatform; + currentNamespace = _namespaceGoogleMobilePlatform; break; case RCNTestRCInstanceDefault: default: @@ -911,7 +929,7 @@ - (void)testActivateOnFetchNoChangeStatus { case RCNTestRCInstanceSecondApp: currentAppName = RCNTestsSecondFIRAppName; currentOptions = [self secondAppOptions]; - currentNamespace = FIRNamespaceGoogleMobilePlatform; + currentNamespace = _namespaceGoogleMobilePlatform; break; case RCNTestRCInstanceDefault: default: @@ -1782,6 +1800,34 @@ - (void)testRealtimeStreamRequestBody { XCTAssertTrue([strData containsString:@"appInstanceId:'iid'"]); } +- (void)testFetchAndActivateRolloutsNotifyInterop { + XCTestExpectation *notificationExpectation = + [self expectationForNotification:@"FIRRolloutsStateDidChangeNotification" + object:nil + handler:nil]; + + XCTAssertEqual(_configInstances[RCNTestRCInstanceDefault].lastFetchStatus, + FIRRemoteConfigFetchStatusNoFetchYet); + + FIRRemoteConfigFetchAndActivateCompletion fetchAndActivateCompletion = + ^void(FIRRemoteConfigFetchAndActivateStatus status, NSError *error) { + XCTAssertEqual(status, FIRRemoteConfigFetchAndActivateStatusSuccessFetchedFromRemote); + XCTAssertNil(error); + + XCTAssertEqual(self->_configInstances[RCNTestRCInstanceDefault].lastFetchStatus, + FIRRemoteConfigFetchStatusSuccess); + XCTAssertNotNil(self->_configInstances[RCNTestRCInstanceDefault].lastFetchTime); + XCTAssertGreaterThan( + self->_configInstances[RCNTestRCInstanceDefault].lastFetchTime.timeIntervalSince1970, 0, + @"last fetch time interval should be set."); + [notificationExpectation fulfill]; + }; + + [_configInstances[RCNTestRCInstanceDefault] + fetchAndActivateWithCompletionHandler:fetchAndActivateCompletion]; + [self waitForExpectations:@[ notificationExpectation ] timeout:_expectationTimeout]; +} + #pragma mark - Test Helpers - (FIROptions *)firstAppOptions { diff --git a/FirebaseRemoteConfig/Tests/Unit/RCNThrottlingTests.m b/FirebaseRemoteConfig/Tests/Unit/RCNThrottlingTests.m index 8721463feb8..5429c61df1f 100644 --- a/FirebaseRemoteConfig/Tests/Unit/RCNThrottlingTests.m +++ b/FirebaseRemoteConfig/Tests/Unit/RCNThrottlingTests.m @@ -25,6 +25,7 @@ #import "FirebaseRemoteConfig/Tests/Unit/RCNTestUtilities.h" #import "FirebaseCore/Extension/FirebaseCoreInternal.h" +@import FirebaseRemoteConfigInterop; @interface RCNThrottlingTests : XCTestCase { RCNConfigContent *_configContentMock; @@ -53,20 +54,22 @@ - (void)setUp { RCNConfigDBManager *DBManager = [[RCNConfigDBManager alloc] init]; _configContentMock = OCMClassMock([RCNConfigContent class]); - _settings = [[RCNConfigSettings alloc] initWithDatabaseManager:DBManager - namespace:FIRNamespaceGoogleMobilePlatform - app:[FIRApp defaultApp]]; + _settings = [[RCNConfigSettings alloc] + initWithDatabaseManager:DBManager + namespace:FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform + app:[FIRApp defaultApp]]; _experimentMock = OCMClassMock([RCNConfigExperiment class]); dispatch_queue_t _queue = dispatch_queue_create( "com.google.GoogleConfigService.FIRRemoteConfigTest", DISPATCH_QUEUE_SERIAL); - _configFetch = [[RCNConfigFetch alloc] initWithContent:_configContentMock - DBManager:DBManager - settings:_settings - experiment:_experimentMock - queue:_queue - namespace:FIRNamespaceGoogleMobilePlatform - app:[FIRApp defaultApp]]; + _configFetch = [[RCNConfigFetch alloc] + initWithContent:_configContentMock + DBManager:DBManager + settings:_settings + experiment:_experimentMock + queue:_queue + namespace:FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform + app:[FIRApp defaultApp]]; } - (void)mockFetchResponseWithStatusCode:(NSInteger)statusCode { diff --git a/FirebaseRemoteConfig/Tests/Unit/RCNUserDefaultsManagerTests.m b/FirebaseRemoteConfig/Tests/Unit/RCNUserDefaultsManagerTests.m index 0c3135e2edd..5f915d73632 100644 --- a/FirebaseRemoteConfig/Tests/Unit/RCNUserDefaultsManagerTests.m +++ b/FirebaseRemoteConfig/Tests/Unit/RCNUserDefaultsManagerTests.m @@ -129,8 +129,17 @@ - (void)testUserDefaultsTemplateVersionWriteAndRead { [[RCNUserDefaultsManager alloc] initWithAppName:AppName bundleID:[NSBundle mainBundle].bundleIdentifier namespace:FQNamespace1]; - [manager setLastTemplateVersion:@"1"]; - XCTAssertEqual([manager lastTemplateVersion], @"1"); + [manager setLastFetchedTemplateVersion:@"1"]; + XCTAssertEqual([manager lastFetchedTemplateVersion], @"1"); +} + +- (void)testUserDefaultsActiveTemplateVersionWriteAndRead { + RCNUserDefaultsManager* manager = + [[RCNUserDefaultsManager alloc] initWithAppName:AppName + bundleID:[NSBundle mainBundle].bundleIdentifier + namespace:FQNamespace1]; + [manager setLastActiveTemplateVersion:@"1"]; + XCTAssertEqual([manager lastActiveTemplateVersion], @"1"); } - (void)testUserDefaultsRealtimeThrottleEndTimeWriteAndRead { @@ -229,10 +238,16 @@ - (void)testUserDefaultsForMultipleNamespaces { XCTAssertEqual([manager2 realtimeRetryCount], 2); /// Fetch template version. - [manager1 setLastTemplateVersion:@"1"]; - [manager2 setLastTemplateVersion:@"2"]; - XCTAssertEqualObjects([manager1 lastTemplateVersion], @"1"); - XCTAssertEqualObjects([manager2 lastTemplateVersion], @"2"); + [manager1 setLastFetchedTemplateVersion:@"1"]; + [manager2 setLastFetchedTemplateVersion:@"2"]; + XCTAssertEqualObjects([manager1 lastFetchedTemplateVersion], @"1"); + XCTAssertEqualObjects([manager2 lastFetchedTemplateVersion], @"2"); + + /// Active template version. + [manager1 setLastActiveTemplateVersion:@"1"]; + [manager2 setLastActiveTemplateVersion:@"2"]; + XCTAssertEqualObjects([manager1 lastActiveTemplateVersion], @"1"); + XCTAssertEqualObjects([manager2 lastActiveTemplateVersion], @"2"); } - (void)testUserDefaultsReset { diff --git a/FirebaseRemoteConfig/generate_featureRolloutsTestApp.sh b/FirebaseRemoteConfig/generate_featureRolloutsTestApp.sh new file mode 100755 index 00000000000..1667fc0fe5a --- /dev/null +++ b/FirebaseRemoteConfig/generate_featureRolloutsTestApp.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +# Copyright 2022 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. +# + +readonly DIR="$( git rev-parse --show-toplevel )" + +# +# This script attempts to copy the Google Services file from google3. If you are not a Google Employee, it will fail, so we'd recommend you create your own Firebase App and place the Google Services file in Tests/TestApp/Shared +# + +echoColor() { + COLOR='\033[0;35m' + NC='\033[0m' + printf "${COLOR}$1${NC}\n" +} + +echoRed() { + COLOR='\033[0;31m' + NC='\033[0m' + printf "${COLOR}$1${NC}\n" +} + +echoColor "Generating Firebase Remote Config Feature Rolouts Test App" +echoColor "Copying GoogleService-Info.plist from google3. Checking gcert status" +if gcertstatus; then + G3Path="/google/src/files/head/depot/google3/third_party/firebase/ios/Secrets/RemoteConfig/FeatureRollouts/GoogleService-Info.plist" + Dest="$DIR/FirebaseRemoteConfig/Tests/FeatureRolloutsTestApp/Shared" + cp $G3Path $Dest + echoColor "Copied $G3Path to $Dest" +else + echoRed "gcert token is not valid. If you are a Google Employee, run 'gcert', and then repeat this command. Non-Google employees will need to download a GoogleService-Info.plist and place it in $DIR/FirebaseRemoteConfig/Tests/FeatureRolloutsTestApp" +fi + + +echoColor "Running 'pod install'" +cd $DIR/FirebaseRemoteConfig/Tests/FeatureRolloutsTestApp +pod install + +# Upon a `pod install`, Crashlytics will copy these files at the root directory +# due to a funky interaction with its cocoapod. This line deletes these extra +# copies of the files as they should only live in Crashlytics/ +rm -f $DIR/run $DIR/upload-symbols + +open *.xcworkspace + diff --git a/FirebaseRemoteConfigInterop.podspec b/FirebaseRemoteConfigInterop.podspec new file mode 100644 index 00000000000..86b86b24e6a --- /dev/null +++ b/FirebaseRemoteConfigInterop.podspec @@ -0,0 +1,34 @@ +Pod::Spec.new do |s| + s.name = 'FirebaseRemoteConfigInterop' + s.version = '10.23.0' + s.summary = 'Interfaces that allow other Firebase SDKs to use Remote Config functionality.' + + s.description = <<-DESC + Not for public use. + A set of protocols that other Firebase SDKs can use to interoperate with FirebaseRemoetConfig in a safe + and reliable manner. + DESC + + s.homepage = 'https://firebase.google.com' + s.license = { :type => 'Apache-2.0', :file => 'LICENSE' } + s.authors = 'Google, Inc.' + + # NOTE that these should not be used externally, this is for Firebase pods to depend on each + # other. + s.source = { + :git => 'https://github.com/firebase/firebase-ios-sdk.git', + :tag => 'CocoaPods-' + s.version.to_s + } + + s.swift_version = '5.3' + s.cocoapods_version = '>= 1.12.0' + s.prefix_header_file = false + + s.social_media_url = 'https://twitter.com/Firebase' + s.ios.deployment_target = '11.0' + s.osx.deployment_target = '10.13' + s.tvos.deployment_target = '12.0' + s.watchos.deployment_target = '6.0' + + s.source_files = 'FirebaseRemoteConfig/Interop/*.swift' +end diff --git a/FirebaseRemoteConfigSwift.podspec b/FirebaseRemoteConfigSwift.podspec index fcedabe6795..3f0b3b3174e 100644 --- a/FirebaseRemoteConfigSwift.podspec +++ b/FirebaseRemoteConfigSwift.podspec @@ -31,7 +31,7 @@ app update. s.tvos.deployment_target = tvos_deployment_target s.watchos.deployment_target = watchos_deployment_target - s.cocoapods_version = '>= 1.4.0' + s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false s.source_files = [ diff --git a/FirebaseRemoteConfigSwift/Tests/SwiftAPI/FirebaseRemoteConfigSwift_APIBuildTests.swift b/FirebaseRemoteConfigSwift/Tests/SwiftAPI/FirebaseRemoteConfigSwift_APIBuildTests.swift index b695e375887..892f14ec834 100644 --- a/FirebaseRemoteConfigSwift/Tests/SwiftAPI/FirebaseRemoteConfigSwift_APIBuildTests.swift +++ b/FirebaseRemoteConfigSwift/Tests/SwiftAPI/FirebaseRemoteConfigSwift_APIBuildTests.swift @@ -16,6 +16,7 @@ import XCTest import FirebaseCore import FirebaseRemoteConfig +import FirebaseRemoteConfigInterop import FirebaseRemoteConfigSwift final class FirebaseRemoteConfigSwift_APIBuildTests: XCTestCase { diff --git a/FirebaseSessions.podspec b/FirebaseSessions.podspec index 6ac318b41b0..9ff30a18019 100644 --- a/FirebaseSessions.podspec +++ b/FirebaseSessions.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseSessions' - s.version = '10.21.0' + s.version = '10.23.0' s.summary = 'Firebase Sessions' s.description = <<-DESC @@ -30,7 +30,7 @@ Pod::Spec.new do |s| s.tvos.deployment_target = tvos_deployment_target s.watchos.deployment_target = watchos_deployment_target - s.cocoapods_version = '>= 1.4.0' + s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false base_dir = "FirebaseSessions/" @@ -44,7 +44,7 @@ Pod::Spec.new do |s| s.dependency 'FirebaseInstallations', '~> 10.0' s.dependency 'GoogleDataTransport', '~> 9.2' s.dependency 'GoogleUtilities/Environment', '~> 7.10' - s.dependency 'nanopb', '>= 2.30908.0', '< 2.30910.0' + s.dependency 'nanopb', '>= 2.30908.0', '< 2.30911.0' s.dependency 'PromisesSwift', '~> 2.1' s.pod_target_xcconfig = { diff --git a/FirebaseSessions/Sources/Development/DevEventConsoleLogger.swift b/FirebaseSessions/Sources/Development/DevEventConsoleLogger.swift index 84e31d63eff..488fca7efc6 100644 --- a/FirebaseSessions/Sources/Development/DevEventConsoleLogger.swift +++ b/FirebaseSessions/Sources/Development/DevEventConsoleLogger.swift @@ -42,6 +42,8 @@ class DevEventConsoleLogger: EventGDTLoggerProtocol { session_index: \(proto.session_data.session_index) event_timestamp_us: \(proto.session_data.event_timestamp_us) firebase_installation_id: \(proto.session_data.firebase_installation_id.description) + firebase_authentication_token: + \(proto.session_data.firebase_authentication_token.description) data_collection_status crashlytics: \(proto.session_data.data_collection_status.crashlytics) performance: \(proto.session_data.data_collection_status.performance) diff --git a/FirebaseSessions/Sources/FirebaseSessions.swift b/FirebaseSessions/Sources/FirebaseSessions.swift index 9c8f7871f22..269cfb9061c 100644 --- a/FirebaseSessions/Sources/FirebaseSessions.swift +++ b/FirebaseSessions/Sources/FirebaseSessions.swift @@ -118,6 +118,10 @@ private enum GoogleDataTransportConfig { .logDebug( "Data Collection is disabled for all subscribers. Skipping this Session Event" ) + case .SessionInstallationsTimeOutError: + Logger.logError( + "Error getting Firebase Installation ID due to timeout. Skipping this Session Event" + ) } } } diff --git a/FirebaseSessions/Sources/FirebaseSessionsError.swift b/FirebaseSessions/Sources/FirebaseSessionsError.swift index 0fae03aa31a..12ed1fb139b 100644 --- a/FirebaseSessions/Sources/FirebaseSessionsError.swift +++ b/FirebaseSessions/Sources/FirebaseSessionsError.swift @@ -20,6 +20,8 @@ enum FirebaseSessionsError: Error { case SessionSamplingError /// Firebase Installation ID related error case SessionInstallationsError(Error) + /// Firebase Installation ID related timeout error + case SessionInstallationsTimeOutError /// Error from the GoogleDataTransport SDK case DataTransportError(Error) /// Sessions SDK is disabled via settings error diff --git a/FirebaseSessions/Sources/Installations+InstallationsProtocol.swift b/FirebaseSessions/Sources/Installations+InstallationsProtocol.swift index 47d7a4f1ed5..17ab5569694 100644 --- a/FirebaseSessions/Sources/Installations+InstallationsProtocol.swift +++ b/FirebaseSessions/Sources/Installations+InstallationsProtocol.swift @@ -16,19 +16,63 @@ import Foundation @_implementationOnly import FirebaseInstallations - protocol InstallationsProtocol { - func installationID(completion: @escaping (Result) -> Void) + var installationsWaitTimeInSecond: Int { get } + + /// Override Installation function for testing + func authToken(completion: @escaping (InstallationsAuthTokenResult?, Error?) -> Void) + + /// Override Installation function for testing + func installationID(completion: @escaping (String?, Error?) -> Void) + + /// Return a tuple: (installationID, authenticationToken) for success result + func installationID(completion: @escaping (Result<(String, String), Error>) -> Void) } -extension Installations: InstallationsProtocol { - func installationID(completion: @escaping (Result) -> Void) { +extension InstallationsProtocol { + var installationsWaitTimeInSecond: Int { + return 10 + } + + func installationID(completion: @escaping (Result<(String, String), Error>) -> Void) { + var authTokenComplete = "" + var intallationComplete: String? + var errorComplete: Error? + + let workingGroup = DispatchGroup() + + workingGroup.enter() + authToken { (authTokenResult: InstallationsAuthTokenResult?, error: Error?) in + authTokenComplete = authTokenResult?.authToken ?? "" + workingGroup.leave() + } + + workingGroup.enter() installationID { (installationID: String?, error: Error?) in if let installationID = installationID { - completion(.success(installationID)) + intallationComplete = installationID } else if let error = error { + errorComplete = error + } + workingGroup.leave() + } + + // adding timeout for 10 seconds + let result = workingGroup + .wait(timeout: .now() + DispatchTimeInterval.seconds(installationsWaitTimeInSecond)) + + switch result { + case .timedOut: + completion(.failure(FirebaseSessionsError.SessionInstallationsTimeOutError)) + return + default: + if let installationID = intallationComplete { + completion(.success((installationID, authTokenComplete))) + } else if let error = errorComplete { completion(.failure(error)) } } } } + +extension Installations: InstallationsProtocol {} diff --git a/FirebaseSessions/Sources/SessionCoordinator.swift b/FirebaseSessions/Sources/SessionCoordinator.swift index 801eafb9e51..3d4cec1b072 100644 --- a/FirebaseSessions/Sources/SessionCoordinator.swift +++ b/FirebaseSessions/Sources/SessionCoordinator.swift @@ -67,11 +67,13 @@ class SessionCoordinator: SessionCoordinatorProtocol { -> Void) { installations.installationID { result in switch result { - case let .success(fiid): - event.setInstallationID(installationId: fiid) + case let .success(installationsInfo): + event.setInstallationID(installationId: installationsInfo.0) + event.setAuthenticationToken(authenticationToken: installationsInfo.1) callback(.success(())) case let .failure(error): event.setInstallationID(installationId: "") + event.setAuthenticationToken(authenticationToken: "") callback(.failure(FirebaseSessionsError.SessionInstallationsError(error))) } } diff --git a/FirebaseSessions/Sources/SessionStartEvent.swift b/FirebaseSessions/Sources/SessionStartEvent.swift index c107849a662..eb1560a86f5 100644 --- a/FirebaseSessions/Sources/SessionStartEvent.swift +++ b/FirebaseSessions/Sources/SessionStartEvent.swift @@ -91,6 +91,7 @@ class SessionStartEvent: NSObject, GDTCOREventDataObject { proto.application_info.session_sdk_version, proto.session_data.session_id, proto.session_data.firebase_installation_id, + proto.session_data.firebase_authentication_token, proto.session_data.first_session_id, ] for pointer in garbage { @@ -104,6 +105,12 @@ class SessionStartEvent: NSObject, GDTCOREventDataObject { nanopb_free(oldID) } + func setAuthenticationToken(authenticationToken: String) { + let oldToken = proto.session_data.firebase_authentication_token + proto.session_data.firebase_authentication_token = makeProtoString(authenticationToken) + nanopb_free(oldToken) + } + func setSamplingRate(samplingRate: Double) { proto.session_data.data_collection_status.session_sampling_rate = samplingRate } @@ -150,18 +157,7 @@ class SessionStartEvent: NSObject, GDTCOREventDataObject { // MARK: - GDTCOREventDataObject func transportBytes() -> Data { - var fields = firebase_appquality_sessions_SessionEvent_fields - var error: NSError? - let data = FIRSESEncodeProto(&fields.0, &proto, &error) - if error != nil { - Logger - .logError("Session Event failed to encode as proto with error: \(error.debugDescription)") - } - guard let data = data else { - Logger.logError("Session Event generated nil transportBytes. Returning empty data.") - return Data() - } - return data + return FIRSESTransportBytes(&proto) } // MARK: - Data Conversion diff --git a/FirebaseSessions/Sources/Settings/SettingsDownloadClient.swift b/FirebaseSessions/Sources/Settings/SettingsDownloadClient.swift index c944291857a..aaea90941dc 100644 --- a/FirebaseSessions/Sources/Settings/SettingsDownloadClient.swift +++ b/FirebaseSessions/Sources/Settings/SettingsDownloadClient.swift @@ -53,8 +53,8 @@ class SettingsDownloader: SettingsDownloadClient { installations.installationID { result in switch result { - case let .success(fiid): - let request = self.buildRequest(url: validURL, fiid: fiid) + case let .success(installationsInfo): + let request = self.buildRequest(url: validURL, fiid: installationsInfo.0) let task = URLSession.shared.dataTask(with: request) { data, response, error in if let data = data { if let dict = try? JSONSerialization.jsonObject(with: data) as? [String: Any] { diff --git a/FirebaseSessions/SourcesObjC/NanoPB/FIRSESNanoPBHelpers.h b/FirebaseSessions/SourcesObjC/NanoPB/FIRSESNanoPBHelpers.h index 8956ff022a6..e07f0e344ed 100644 --- a/FirebaseSessions/SourcesObjC/NanoPB/FIRSESNanoPBHelpers.h +++ b/FirebaseSessions/SourcesObjC/NanoPB/FIRSESNanoPBHelpers.h @@ -91,6 +91,9 @@ pb_size_t FIRSESGetAppleApplicationInfoTag(void); /// private method in GULAppEnvironmentUtil. NSString* _Nullable FIRSESGetSysctlEntry(const char* sysctlKey); +/// C function to bridge from Swift to do nanopb bytes transfer. +NSData* FIRSESTransportBytes(const void* _Nonnull proto); + NS_ASSUME_NONNULL_END #endif /* FIRSESNanoPBHelpers_h */ diff --git a/FirebaseSessions/SourcesObjC/NanoPB/FIRSESNanoPBHelpers.m b/FirebaseSessions/SourcesObjC/NanoPB/FIRSESNanoPBHelpers.m index 22cb8a61b2f..6440ec4d886 100644 --- a/FirebaseSessions/SourcesObjC/NanoPB/FIRSESNanoPBHelpers.m +++ b/FirebaseSessions/SourcesObjC/NanoPB/FIRSESNanoPBHelpers.m @@ -21,6 +21,8 @@ #import "FirebaseSessions/SourcesObjC/Protogen/nanopb/sessions.nanopb.h" +@import FirebaseCoreExtension; + #import #import #import @@ -182,4 +184,22 @@ pb_size_t FIRSESGetAppleApplicationInfoTag(void) { } } +NSData *FIRSESTransportBytes(const void *_Nonnull proto) { + const pb_field_t *fields = firebase_appquality_sessions_SessionEvent_fields; + NSError *error; + NSData *data = FIRSESEncodeProto(fields, proto, &error); + if (error != nil) { + FIRLogError( + @"FirebaseSessions", @"I-SES000001", @"%@", + [NSString stringWithFormat:@"Session Event failed to encode as proto with error: %@", + error.debugDescription]); + } + if (data == nil) { + data = [NSData data]; + FIRLogError(@"FirebaseSessions", @"I-SES000002", + @"Session Event generated nil transportBytes. Returning empty data."); + } + return data; +} + NS_ASSUME_NONNULL_END diff --git a/FirebaseSessions/SourcesObjC/Protogen/nanopb/sessions.nanopb.c b/FirebaseSessions/SourcesObjC/Protogen/nanopb/sessions.nanopb.c index 3673f9d6ccb..f89596ab1df 100644 --- a/FirebaseSessions/SourcesObjC/Protogen/nanopb/sessions.nanopb.c +++ b/FirebaseSessions/SourcesObjC/Protogen/nanopb/sessions.nanopb.c @@ -39,13 +39,14 @@ const pb_field_t firebase_appquality_sessions_NetworkConnectionInfo_fields[3] = PB_LAST_FIELD }; -const pb_field_t firebase_appquality_sessions_SessionInfo_fields[7] = { +const pb_field_t firebase_appquality_sessions_SessionInfo_fields[8] = { PB_FIELD( 1, BYTES , SINGULAR, POINTER , FIRST, firebase_appquality_sessions_SessionInfo, session_id, session_id, 0), PB_FIELD( 3, BYTES , SINGULAR, POINTER , OTHER, firebase_appquality_sessions_SessionInfo, firebase_installation_id, session_id, 0), PB_FIELD( 4, INT64 , SINGULAR, STATIC , OTHER, firebase_appquality_sessions_SessionInfo, event_timestamp_us, firebase_installation_id, 0), PB_FIELD( 6, MESSAGE , SINGULAR, STATIC , OTHER, firebase_appquality_sessions_SessionInfo, data_collection_status, event_timestamp_us, &firebase_appquality_sessions_DataCollectionStatus_fields), PB_FIELD( 7, BYTES , SINGULAR, POINTER , OTHER, firebase_appquality_sessions_SessionInfo, first_session_id, data_collection_status, 0), PB_FIELD( 8, INT32 , SINGULAR, STATIC , OTHER, firebase_appquality_sessions_SessionInfo, session_index, first_session_id, 0), + PB_FIELD( 9, BYTES , SINGULAR, POINTER , OTHER, firebase_appquality_sessions_SessionInfo, firebase_authentication_token, session_index, 0), PB_LAST_FIELD }; diff --git a/FirebaseSessions/SourcesObjC/Protogen/nanopb/sessions.nanopb.h b/FirebaseSessions/SourcesObjC/Protogen/nanopb/sessions.nanopb.h index c8cfeb8edb3..5b604e93870 100644 --- a/FirebaseSessions/SourcesObjC/Protogen/nanopb/sessions.nanopb.h +++ b/FirebaseSessions/SourcesObjC/Protogen/nanopb/sessions.nanopb.h @@ -161,6 +161,7 @@ typedef struct _firebase_appquality_sessions_SessionInfo { firebase_appquality_sessions_DataCollectionStatus data_collection_status; pb_bytes_array_t *first_session_id; int32_t session_index; + pb_bytes_array_t *firebase_authentication_token; /* @@protoc_insertion_point(struct:firebase_appquality_sessions_SessionInfo) */ } firebase_appquality_sessions_SessionInfo; @@ -224,6 +225,7 @@ typedef struct _firebase_appquality_sessions_SessionEvent { #define firebase_appquality_sessions_SessionInfo_firebase_installation_id_tag 3 #define firebase_appquality_sessions_SessionInfo_event_timestamp_us_tag 4 #define firebase_appquality_sessions_SessionInfo_data_collection_status_tag 6 +#define firebase_appquality_sessions_SessionInfo_firebase_authentication_token_tag 9 #define firebase_appquality_sessions_ApplicationInfo_android_app_info_tag 5 #define firebase_appquality_sessions_ApplicationInfo_apple_app_info_tag 6 #define firebase_appquality_sessions_ApplicationInfo_app_id_tag 1 @@ -240,7 +242,7 @@ typedef struct _firebase_appquality_sessions_SessionEvent { /* Struct field encoding specification for nanopb */ extern const pb_field_t firebase_appquality_sessions_SessionEvent_fields[4]; extern const pb_field_t firebase_appquality_sessions_NetworkConnectionInfo_fields[3]; -extern const pb_field_t firebase_appquality_sessions_SessionInfo_fields[7]; +extern const pb_field_t firebase_appquality_sessions_SessionInfo_fields[8]; extern const pb_field_t firebase_appquality_sessions_DataCollectionStatus_fields[4]; extern const pb_field_t firebase_appquality_sessions_ApplicationInfo_fields[10]; extern const pb_field_t firebase_appquality_sessions_AndroidApplicationInfo_fields[3]; diff --git a/FirebaseSessions/Tests/TestApp/Podfile b/FirebaseSessions/Tests/TestApp/Podfile index 67c05149217..4bea966bc4f 100644 --- a/FirebaseSessions/Tests/TestApp/Podfile +++ b/FirebaseSessions/Tests/TestApp/Podfile @@ -7,6 +7,7 @@ def shared_pods pod 'FirebaseCoreInternal', :path => '../../../' pod 'FirebaseCoreExtension', :path => '../../../' pod 'FirebaseSessions', :path => '../../../' + pod 'FirebaseRemoteConfigInterop', :path => '../../../' end target 'AppQualityDevApp_iOS' do diff --git a/FirebaseSessions/Tests/TestApp/Shared/MockSubscriberSDK.swift b/FirebaseSessions/Tests/TestApp/Shared/MockSubscriberSDK.swift index e3ef501dd59..c7427286501 100644 --- a/FirebaseSessions/Tests/TestApp/Shared/MockSubscriberSDK.swift +++ b/FirebaseSessions/Tests/TestApp/Shared/MockSubscriberSDK.swift @@ -44,7 +44,7 @@ protocol MockSubscriberSDKProtocol { let sessions = ComponentType.instance(for: SessionsProvider.self, in: app.container) - sessions.register(subscriber: self) + sessions?.register(subscriber: self) } // MARK: - Library Conformance diff --git a/FirebaseSessions/Tests/Unit/Mocks/MockInstallationsProtocol.swift b/FirebaseSessions/Tests/Unit/Mocks/MockInstallationsProtocol.swift index 0abfdaf2446..10f9392776a 100644 --- a/FirebaseSessions/Tests/Unit/Mocks/MockInstallationsProtocol.swift +++ b/FirebaseSessions/Tests/Unit/Mocks/MockInstallationsProtocol.swift @@ -19,9 +19,24 @@ class MockInstallationsProtocol: InstallationsProtocol { static let testInstallationId = "testInstallationId" - var result: Result = .success(testInstallationId) + static let testAuthToken = "testAuthToken" + var result: Result<(String, String), Error> = .success((testInstallationId, testAuthToken)) + var installationIdFinished = false + var authTokenFinished = false - func installationID(completion: @escaping (Result) -> Void) { - completion(result) + func installationID(completion: @escaping (String?, Error?) -> Void) { + installationIdFinished = true + switch result { + case let .success(success): + completion(success.0, nil) + case let .failure(failure): + completion(nil, failure) + } + } + + func authToken(completion: @escaping (InstallationsAuthTokenResult?, Error?) -> Void) { + Thread.sleep(forTimeInterval: 0.1) + authTokenFinished = true + completion(nil, nil) } } diff --git a/FirebaseSessions/Tests/Unit/SessionCoordinatorTests.swift b/FirebaseSessions/Tests/Unit/SessionCoordinatorTests.swift index 7086a9bc135..ba9b10186b6 100644 --- a/FirebaseSessions/Tests/Unit/SessionCoordinatorTests.swift +++ b/FirebaseSessions/Tests/Unit/SessionCoordinatorTests.swift @@ -34,6 +34,11 @@ class SessionCoordinatorTests: XCTestCase { ) } + override func tearDown() { + installations.authTokenFinished = false + installations.installationIdFinished = false + } + var defaultSessionInfo: SessionInfo { return SessionInfo( sessionId: "test_session_id", @@ -61,6 +66,9 @@ class SessionCoordinatorTests: XCTestCase { fieldName: "installation_id" ) + XCTAssertTrue(installations.authTokenFinished) + XCTAssertTrue(installations.installationIdFinished) + // We should have logged successfully XCTAssertEqual(fireLogger.loggedEvent, event) XCTAssert(resultSuccess) @@ -82,6 +90,9 @@ class SessionCoordinatorTests: XCTestCase { } } + XCTAssertTrue(installations.authTokenFinished) + XCTAssertTrue(installations.installationIdFinished) + // Make sure we've set the Installation ID assertEqualProtoString( event.proto.session_data.firebase_installation_id, @@ -110,6 +121,8 @@ class SessionCoordinatorTests: XCTestCase { } } + XCTAssertTrue(installations.authTokenFinished) + XCTAssertTrue(installations.installationIdFinished) // We should have logged the event, but with a failed result XCTAssertNotNil(fireLogger.loggedEvent) XCTAssertFalse(resultSuccess) @@ -138,6 +151,9 @@ class SessionCoordinatorTests: XCTestCase { } } + XCTAssertTrue(installations.authTokenFinished) + XCTAssertTrue(installations.installationIdFinished) + // Make sure we've set the Installation ID to empty because the FIID // fetch failed assertEqualProtoString( diff --git a/FirebaseSharedSwift.podspec b/FirebaseSharedSwift.podspec index b146408fd59..c451e29e1d7 100644 --- a/FirebaseSharedSwift.podspec +++ b/FirebaseSharedSwift.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseSharedSwift' - s.version = '10.21.0' + s.version = '10.23.0' s.summary = 'Shared Swift Extensions for Firebase' s.description = <<-DESC @@ -30,7 +30,7 @@ Firebase products. FirebaseSharedSwift is not supported for non-Firebase usage. s.tvos.deployment_target = tvos_deployment_target s.watchos.deployment_target = watchos_deployment_target - s.cocoapods_version = '>= 1.4.0' + s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false s.source_files = [ diff --git a/FirebaseStorage.podspec b/FirebaseStorage.podspec index 081967ae0f5..f042233ada4 100644 --- a/FirebaseStorage.podspec +++ b/FirebaseStorage.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseStorage' - s.version = '10.21.0' + s.version = '10.23.0' s.summary = 'Firebase Storage' s.description = <<-DESC @@ -17,10 +17,10 @@ Firebase Storage provides robust, secure file uploads and downloads from Firebas } s.social_media_url = 'https://twitter.com/Firebase' - ios_deployment_target = '11.0' - osx_deployment_target = '10.13' - tvos_deployment_target = '12.0' - watchos_deployment_target = '6.0' + ios_deployment_target = '13.0' + osx_deployment_target = '10.15' + tvos_deployment_target = '13.0' + watchos_deployment_target = '7.0' s.ios.deployment_target = ios_deployment_target s.osx.deployment_target = osx_deployment_target @@ -29,7 +29,7 @@ Firebase Storage provides robust, secure file uploads and downloads from Firebas s.swift_version = '5.3' - s.cocoapods_version = '>= 1.4.0' + s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false s.source_files = [ diff --git a/FirebaseStorage/Sources/AsyncAwait.swift b/FirebaseStorage/Sources/AsyncAwait.swift index d38c859d818..564a2378aaa 100644 --- a/FirebaseStorage/Sources/AsyncAwait.swift +++ b/FirebaseStorage/Sources/AsyncAwait.swift @@ -24,8 +24,7 @@ public extension StorageReference { /// - Parameters: /// - size: The maximum size in bytes to download. If the download exceeds this size, /// the task will be cancelled and an error will be thrown. - /// - Throws: - /// - An error if the operation failed, for example if the data exceeded `maxSize`. + /// - Throws: An error if the operation failed, for example if the data exceeded `maxSize`. /// - Returns: Data object. func data(maxSize: Int64) async throws -> Data { return try await withCheckedThrowingContinuation { continuation in @@ -45,8 +44,7 @@ public extension StorageReference { /// about the object being uploaded. /// - onProgress: An optional closure function to return a `Progress` instance while the /// upload proceeds. - /// - Throws: - /// - An error if the operation failed, for example if Storage was unreachable. + /// - Throws: An error if the operation failed, for example if Storage was unreachable. /// - Returns: StorageMetadata with additional information about the object being uploaded. func putDataAsync(_ uploadData: Data, metadata: StorageMetadata? = nil, @@ -83,9 +81,8 @@ public extension StorageReference { /// about the object being uploaded. /// - onProgress: An optional closure function to return a `Progress` instance while the /// upload proceeds. - /// - Throws: - /// - An error if the operation failed, for example if no file was present at the specified - /// `url`. + /// - Throws: An error if the operation failed, for example if no file was present at the + /// specified `url`. /// - Returns: `StorageMetadata` with additional information about the object being uploaded. func putFileAsync(from url: URL, metadata: StorageMetadata? = nil, @@ -119,8 +116,7 @@ public extension StorageReference { /// - fileUrl: A URL representing the system file path of the object to be uploaded. /// - onProgress: An optional closure function to return a `Progress` instance while the /// download proceeds. - /// - Throws: - /// - An error if the operation failed, for example if Storage was unreachable + /// - Throws: An error if the operation failed, for example if Storage was unreachable /// or `fileURL` did not reference a valid path on disk. /// - Returns: A `URL` pointing to the file path of the downloaded file. func writeAsync(toFile fileURL: URL, @@ -157,13 +153,11 @@ public extension StorageReference { /// Only available for projects using Firebase Rules Version 2. /// /// - Parameters: - /// - maxResults The maximum number of results to return in a single page. Must be + /// - maxResults: The maximum number of results to return in a single page. Must be /// greater than 0 and at most 1000. - /// - Throws: - /// - An error if the operation failed, for example if Storage was unreachable + /// - Throws: An error if the operation failed, for example if Storage was unreachable /// or the storage reference referenced an invalid path. - /// - Returns: - /// - A `StorageListResult` containing the contents of the storage reference. + /// - Returns: A `StorageListResult` containing the contents of the storage reference. func list(maxResults: Int64) async throws -> StorageListResult { typealias ListContinuation = CheckedContinuation return try await withCheckedThrowingContinuation { (continuation: ListContinuation) in @@ -182,9 +176,9 @@ public extension StorageReference { /// Only available for projects using Firebase Rules Version 2. /// /// - Parameters: - /// - maxResults The maximum number of results to return in a single page. Must be + /// - maxResults: The maximum number of results to return in a single page. Must be /// greater than 0 and at most 1000. - /// - pageToken A page token from a previous call to list. + /// - pageToken: A page token from a previous call to list. /// - Throws: /// - An error if the operation failed, for example if Storage was unreachable /// or the storage reference referenced an invalid path. diff --git a/FirebaseStorage/Sources/Result.swift b/FirebaseStorage/Sources/Result.swift index 7aee82f439c..d1260fe57f9 100644 --- a/FirebaseStorage/Sources/Result.swift +++ b/FirebaseStorage/Sources/Result.swift @@ -38,6 +38,7 @@ private func getResultCallback(completion: @escaping (Result) -> Vo public extension StorageReference { /// Asynchronously retrieves a long lived download URL with a revokable token. + /// /// This can be used to share the file with others, but can be revoked by a developer /// in the Firebase Console. /// @@ -48,6 +49,7 @@ public extension StorageReference { } /// Asynchronously downloads the object at the `StorageReference` to a `Data` object. + /// /// A `Data` of the provided max size will be allocated, so ensure that the device has enough /// memory to complete. For downloading large files, the `write` API may be a better option. @@ -73,6 +75,7 @@ public extension StorageReference { } /// Resumes a previous `list` call, starting after a pagination token. + /// /// Returns the next set of items (files) and prefixes (folders) under this StorageReference. /// /// "/" is treated as a path delimiter. Firebase Storage does not support unsupported object @@ -82,10 +85,10 @@ public extension StorageReference { /// Only available for projects using Firebase Rules Version 2. /// /// - Parameters: - /// - maxResults The maximum number of results to return in a single page. Must be + /// - maxResults: The maximum number of results to return in a single page. Must be /// greater than 0 and at most 1000. - /// - pageToken A page token from a previous call to list. - /// - completion A completion handler that will be invoked with the next items and + /// - pageToken: A page token from a previous call to list. + /// - completion: A completion handler that will be invoked with the next items and /// prefixes under the current StorageReference. It returns a `Result` enum /// with either the list or an `Error`. func list(maxResults: Int64, @@ -105,9 +108,9 @@ public extension StorageReference { /// Only available for projects using Firebase Rules Version 2. /// /// - Parameters: - /// - maxResults The maximum number of results to return in a single page. Must be + /// - maxResults: The maximum number of results to return in a single page. Must be /// greater than 0 and at most 1000. - /// - completion A completion handler that will be invoked with the next items and + /// - completion: A completion handler that will be invoked with the next items and /// prefixes under the current `StorageReference`. It returns a `Result` enum /// with either the list or an `Error`. func list(maxResults: Int64, @@ -125,7 +128,7 @@ public extension StorageReference { /// Only available for projects using Firebase Rules Version 2. /// /// - Parameters: - /// - completion A completion handler that will be invoked with all items and prefixes + /// - completion: A completion handler that will be invoked with all items and prefixes /// under the current StorageReference. It returns a `Result` enum with either the /// list or an `Error`. func listAll(completion: @escaping (Result) -> Void) { @@ -136,10 +139,10 @@ public extension StorageReference { /// This is not recommended for large files, and one should instead upload a file from disk. /// /// - Parameters: - /// - uploadData The `Data` to upload. - /// - metadata `StorageMetadata` containing additional information (MIME type, etc.) + /// - uploadData: The `Data` to upload. + /// - metadata: `StorageMetadata` containing additional information (MIME type, etc.) /// about the object being uploaded. - /// - completion A completion block that returns a `Result` enum with either the + /// - completion: A completion block that returns a `Result` enum with either the /// object metadata or an `Error`. /// /// - Returns: An instance of `StorageUploadTask`, which can be used to monitor or manage @@ -157,10 +160,10 @@ public extension StorageReference { /// Asynchronously uploads a file to the currently specified `StorageReference`. /// /// - Parameters: - /// - from A URL representing the system file path of the object to be uploaded. - /// - metadata `StorageMetadata` containing additional information (MIME type, etc.) + /// - from: A URL representing the system file path of the object to be uploaded. + /// - metadata: `StorageMetadata` containing additional information (MIME type, etc.) /// about the object being uploaded. - /// - completion A completion block that returns a `Result` enum with either the + /// - completion: A completion block that returns a `Result` enum with either the /// object metadata or an `Error`. /// /// - Returns: An instance of `StorageUploadTask`, which can be used to monitor or manage @@ -178,8 +181,8 @@ public extension StorageReference { /// Updates the metadata associated with an object at the current path. /// /// - Parameters: - /// - metadata A `StorageMetadata` object with the metadata to update. - /// - completion A completion block which returns a `Result` enum with either the + /// - metadata: A `StorageMetadata` object with the metadata to update. + /// - completion: A completion block which returns a `Result` enum with either the /// object metadata or an `Error`. func updateMetadata(_ metadata: StorageMetadata, completion: @escaping (Result) -> Void) { @@ -189,8 +192,8 @@ public extension StorageReference { /// Asynchronously downloads the object at the current path to a specified system filepath. /// /// - Parameters: - /// - toFile A file system URL representing the path the object should be downloaded to. - /// - completion A completion block that fires when the file download completes. The + /// - toFile: A file system URL representing the path the object should be downloaded to. + /// - completion: A completion block that fires when the file download completes. The /// block returns a `Result` enum with either an NSURL pointing to the file /// path of the downloaded file or an `Error`. /// diff --git a/FirebaseStorage/Sources/Storage.swift b/FirebaseStorage/Sources/Storage.swift index 197122d05bd..16792fd1d80 100644 --- a/FirebaseStorage/Sources/Storage.swift +++ b/FirebaseStorage/Sources/Storage.swift @@ -26,112 +26,98 @@ import FirebaseCore // Avoids exposing internal FirebaseCore APIs to Swift users. @_implementationOnly import FirebaseCoreExtension -/** - * Firebase Storage is a service that supports uploading and downloading binary objects, - * such as images, videos, and other files to Google Cloud Storage. Instances of `Storage` - * are not thread-safe, but can be accessed from any thread. - * - * If you call `Storage.storage()`, the instance will initialize with the default `FirebaseApp`, - * `FirebaseApp.app()`, and the storage location will come from the provided - * `GoogleService-Info.plist`. - * - * If you provide a custom instance of `FirebaseApp`, - * the storage location will be specified via the `FirebaseOptions.storageBucket` property. - */ +/// Firebase Storage is a service that supports uploading and downloading binary objects, +/// such as images, videos, and other files to Google Cloud Storage. Instances of `Storage` +/// are not thread-safe, but can be accessed from any thread. +/// +/// If you call `Storage.storage()`, the instance will initialize with the default `FirebaseApp`, +/// `FirebaseApp.app()`, and the storage location will come from the provided +/// `GoogleService-Info.plist`. +/// +/// If you provide a custom instance of `FirebaseApp`, +/// the storage location will be specified via the `FirebaseOptions.storageBucket` property. @objc(FIRStorage) open class Storage: NSObject { // MARK: - Public APIs - /** - * The default `Storage` instance. - * - Returns: An instance of `Storage`, configured with the default `FirebaseApp`. - */ + /// The default `Storage` instance. + /// - Returns: An instance of `Storage`, configured with the default `FirebaseApp`. @objc(storage) open class func storage() -> Storage { return storage(app: FirebaseApp.app()!) } - /** - * A method used to create `Storage` instances initialized with a custom storage bucket URL. - * Any `StorageReferences` generated from this instance of `Storage` will reference files - * and directories within the specified bucket. - * - Parameter url The `gs://` URL to your Firebase Storage bucket. - * - Returns: A `Storage` instance, configured with the custom storage bucket. - */ + /// A method used to create `Storage` instances initialized with a custom storage bucket URL. + /// + /// Any `StorageReferences` generated from this instance of `Storage` will reference files + /// and directories within the specified bucket. + /// - Parameter url: The `gs://` URL to your Firebase Storage bucket. + /// - Returns: A `Storage` instance, configured with the custom storage bucket. @objc(storageWithURL:) open class func storage(url: String) -> Storage { return storage(app: FirebaseApp.app()!, url: url) } - /** - * Creates an instance of `Storage`, configured with a custom `FirebaseApp`. `StorageReference`s - * generated from a resulting instance will reference files in the Firebase project - * associated with custom `FirebaseApp`. - * - Parameter app The custom `FirebaseApp` used for initialization. - * - Returns: A `Storage` instance, configured with the custom `FirebaseApp`. - */ + /// Creates an instance of `Storage`, configured with a custom `FirebaseApp`. `StorageReference`s + /// generated from a resulting instance will reference files in the Firebase project + /// associated with custom `FirebaseApp`. + /// - Parameter app: The custom `FirebaseApp` used for initialization. + /// - Returns: A `Storage` instance, configured with the custom `FirebaseApp`. @objc(storageForApp:) open class func storage(app: FirebaseApp) -> Storage { - let provider = ComponentType.instance(for: StorageProvider.self, - in: app.container) + guard let provider = ComponentType.instance(for: StorageProvider.self, + in: app.container) else { + fatalError("No \(StorageProvider.self) instance found for Firebase app: \(app.name)") + } return provider.storage(for: Storage.bucket(for: app)) } - /** - * Creates an instance of `Storage`, configured with a custom `FirebaseApp` and a custom storage - * bucket URL. - * - Parameters: - * - app: The custom `FirebaseApp` used for initialization. - * - url: The `gs://` url to your Firebase Storage bucket. - * - Returns: the `Storage` instance, configured with the custom `FirebaseApp` and storage bucket URL. - */ + /// Creates an instance of `Storage`, configured with a custom `FirebaseApp` and a custom storage + /// bucket URL. + /// - Parameters: + /// - app: The custom `FirebaseApp` used for initialization. + /// - url: The `gs://` url to your Firebase Storage bucket. + /// - Returns: The `Storage` instance, configured with the custom `FirebaseApp` and storage bucket + /// URL. @objc(storageForApp:URL:) open class func storage(app: FirebaseApp, url: String) -> Storage { - let provider = ComponentType.instance(for: StorageProvider.self, - in: app.container) + guard let provider = ComponentType.instance(for: StorageProvider.self, + in: app.container) else { + fatalError("No \(StorageProvider.self) instance found for Firebase app: \(app.name)") + } return provider.storage(for: Storage.bucket(for: app, urlString: url)) } - /** - * The `FirebaseApp` associated with this Storage instance. - */ + /// The `FirebaseApp` associated with this Storage instance. @objc public let app: FirebaseApp - /** - * The maximum time in seconds to retry an upload if a failure occurs. - * Defaults to 10 minutes (600 seconds). - */ + /// The maximum time in seconds to retry an upload if a failure occurs. + /// Defaults to 10 minutes (600 seconds). @objc public var maxUploadRetryTime: TimeInterval { didSet { maxUploadRetryInterval = Storage.computeRetryInterval(fromRetryTime: maxUploadRetryTime) } } - /** - * The maximum time in seconds to retry a download if a failure occurs. - * Defaults to 10 minutes (600 seconds). - */ + /// The maximum time in seconds to retry a download if a failure occurs. + /// Defaults to 10 minutes (600 seconds). @objc public var maxDownloadRetryTime: TimeInterval { didSet { maxDownloadRetryInterval = Storage.computeRetryInterval(fromRetryTime: maxDownloadRetryTime) } } - /** - * The maximum time in seconds to retry operations other than upload and download if a failure occurs. - * Defaults to 2 minutes (120 seconds). - */ + /// The maximum time in seconds to retry operations other than upload and download if a failure + /// occurs. + /// Defaults to 2 minutes (120 seconds). @objc public var maxOperationRetryTime: TimeInterval { didSet { maxOperationRetryInterval = Storage.computeRetryInterval(fromRetryTime: maxOperationRetryTime) } } - /** - * Specify the maximum upload chunk size. Values less than 256K (262144) will be rounded up to 256K. Values - * above 256K will be rounded down to the nearest 256K multiple. The default is no maximum. - */ + /// Specify the maximum upload chunk size. Values less than 256K (262144) will be rounded up to + /// 256K. Values + /// above 256K will be rounded down to the nearest 256K multiple. The default is no maximum. @objc public var uploadChunkSizeBytes: Int64 = .max - /** - * A `DispatchQueue` that all developer callbacks are fired on. Defaults to the main queue. - */ + /// A `DispatchQueue` that all developer callbacks are fired on. Defaults to the main queue. @objc public var callbackQueue: DispatchQueue { get { ensureConfigured() @@ -146,26 +132,25 @@ import FirebaseCore } } - /** - * Creates a `StorageReference` initialized at the root Firebase Storage location. - * - Returns: An instance of `StorageReference` referencing the root of the storage bucket. - */ + /// Creates a `StorageReference` initialized at the root Firebase Storage location. + /// - Returns: An instance of `StorageReference` referencing the root of the storage bucket. @objc open func reference() -> StorageReference { ensureConfigured() let path = StoragePath(with: storageBucket) return StorageReference(storage: self, path: path) } - /** - * Creates a StorageReference given a `gs://`, `http://`, or `https://` URL pointing to a - * Firebase Storage location. For example, you can pass in an `https://` download URL retrieved from - * `StorageReference.downloadURL(completion:)` or the `gs://` URL from - * `StorageReference.description`. - * - Parameter url A gs:// or https:// URL to initialize the reference with. - * - Returns: An instance of StorageReference at the given child path. - * - Throws: Throws a fatal error if `url` is not associated with the `FirebaseApp` used to initialize - * this Storage instance. - */ + /// Creates a StorageReference given a `gs://`, `http://`, or `https://` URL pointing to a + /// Firebase Storage location. + /// + /// For example, you can pass in an `https://` download URL retrieved from + /// `StorageReference.downloadURL(completion:)` or the `gs://` URL from + /// `StorageReference.description`. + /// - Parameter url: A gs:// or https:// URL to initialize the reference with. + /// - Returns: An instance of StorageReference at the given child path. + /// - Throws: Throws a fatal error if `url` is not associated with the `FirebaseApp` used to + /// initialize + /// this Storage instance. @objc open func reference(forURL url: String) -> StorageReference { ensureConfigured() do { @@ -188,16 +173,16 @@ import FirebaseCore } } - /** - * Creates a StorageReference given a `gs://`, `http://`, or `https://` URL pointing to a - * Firebase Storage location. For example, you can pass in an `https://` download URL retrieved from - * `StorageReference.downloadURL(completion:)` or the `gs://` URL from - * `StorageReference.description`. - * - Parameter url A gs:// or https:// URL to initialize the reference with. - * - Returns: An instance of StorageReference at the given child path. - * - Throws: Throws an Error if `url` is not associated with the `FirebaseApp` used to initialize - * this Storage instance. - */ + /// Creates a StorageReference given a `gs://`, `http://`, or `https://` URL pointing to a + /// Firebase Storage location. + /// + /// For example, you can pass in an `https://` download URL retrieved from + /// `StorageReference.downloadURL(completion:)` or the `gs://` URL from + /// `StorageReference.description`. + /// - Parameter url: A gs:// or https:// URL to initialize the reference with. + /// - Returns: An instance of StorageReference at the given child path. + /// - Throws: Throws an Error if `url` is not associated with the `FirebaseApp` used to initialize + /// this Storage instance. open func reference(for url: URL) throws -> StorageReference { ensureConfigured() var path: StoragePath @@ -222,20 +207,20 @@ import FirebaseCore return StorageReference(storage: self, path: path) } - /** - * Creates a `StorageReference` initialized at a location specified by the `path` parameter. - * - Parameter path A relative path from the root of the storage bucket, - * for instance @"path/to/object". - * - Returns: An instance of `StorageReference` pointing to the given path. - */ + /// Creates a `StorageReference` initialized at a location specified by the `path` parameter. + /// - Parameter path: A relative path from the root of the storage bucket, + /// for instance @"path/to/object". + /// - Returns: An instance of `StorageReference` pointing to the given path. @objc(referenceWithPath:) open func reference(withPath path: String) -> StorageReference { return reference().child(path) } - /** - * Configures the Storage SDK to use an emulated backend instead of the default remote backend. - * This method should be called before invoking any other methods on a new instance of `Storage`. - */ + /// Configures the Storage SDK to use an emulated backend instead of the default remote backend. + /// + /// This method should be called before invoking any other methods on a new instance of `Storage`. + /// - Parameter host: A string specifying the host. + /// - Parameter port: The port specified as an `Int`. + @objc open func useEmulator(withHost host: String, port: Int) { guard host.count > 0 else { fatalError("Invalid host argument: Cannot connect to empty host.") @@ -325,8 +310,8 @@ import FirebaseCore private static func initFetcherServiceForApp(_ app: FirebaseApp, _ bucket: String, - _ auth: AuthInterop, - _ appCheck: AppCheckInterop) + _ auth: AuthInterop?, + _ appCheck: AppCheckInterop?) -> GTMSessionFetcherService { objc_sync_enter(fetcherServiceLock) defer { objc_sync_exit(fetcherServiceLock) } @@ -353,8 +338,8 @@ import FirebaseCore return fetcherService! } - private let auth: AuthInterop - private let appCheck: AppCheckInterop + private let auth: AuthInterop? + private let appCheck: AppCheckInterop? private let storageBucket: String private var usesEmulator: Bool = false var host: String @@ -364,14 +349,13 @@ import FirebaseCore var maxOperationRetryInterval: TimeInterval var maxUploadRetryInterval: TimeInterval - /** - * Performs a crude translation of the user provided timeouts to the retry intervals that - * GTMSessionFetcher accepts. GTMSessionFetcher times out operations if the time between individual - * retry attempts exceed a certain threshold, while our API contract looks at the total observed - * time of the operation (i.e. the sum of all retries). - * @param retryTime A timeout that caps the sum of all retry attempts - * @return A timeout that caps the timeout of the last retry attempt - */ + /// Performs a crude translation of the user provided timeouts to the retry intervals that + /// GTMSessionFetcher accepts. GTMSessionFetcher times out operations if the time between + /// individual + /// retry attempts exceed a certain threshold, while our API contract looks at the total observed + /// time of the operation (i.e. the sum of all retries). + /// @param retryTime A timeout that caps the sum of all retry attempts + /// @return A timeout that caps the timeout of the last retry attempt static func computeRetryInterval(fromRetryTime retryTime: TimeInterval) -> TimeInterval { // GTMSessionFetcher's retry starts at 1 second and then doubles every time. We use this // information to compute a best-effort estimate of what to translate the user provided retry @@ -387,9 +371,7 @@ import FirebaseCore return lastInterval } - /** - * Configures the storage instance. Freezes the host setting. - */ + /// Configures the storage instance. Freezes the host setting. private func ensureConfigured() { guard fetcherService == nil else { return diff --git a/FirebaseStorage/Sources/StorageDownloadTask.swift b/FirebaseStorage/Sources/StorageDownloadTask.swift index b37eedc7dfe..62217182c0e 100644 --- a/FirebaseStorage/Sources/StorageDownloadTask.swift +++ b/FirebaseStorage/Sources/StorageDownloadTask.swift @@ -22,10 +22,13 @@ import Foundation /** * `StorageDownloadTask` implements resumable downloads from an object in Firebase Storage. + * * Downloads can be returned on completion with a completion handler, and can be monitored * by attaching observers, or controlled by calling `pause()`, `resume()`, * or `cancel()`. + * * Downloads can currently be returned as `Data` in memory, or as a `URL` to a file on disk. + * * Downloads are performed on a background queue, and callbacks are raised on the developer * specified `callbackQueue` in Storage, or the main queue if left unspecified. */ diff --git a/FirebaseStorage/Sources/StorageListResult.swift b/FirebaseStorage/Sources/StorageListResult.swift index 872db7140f9..b32f62bf0d8 100644 --- a/FirebaseStorage/Sources/StorageListResult.swift +++ b/FirebaseStorage/Sources/StorageListResult.swift @@ -18,23 +18,17 @@ import Foundation @objc(FIRStorageListResult) open class StorageListResult: NSObject { /** * The prefixes (folders) returned by a `list()` operation. - * - * - Returns: A list of prefixes (folders). */ @objc public let prefixes: [StorageReference] /** * The objects (files) returned by a `list()` operation. - * - * - Returns: A page token if more results are available. */ @objc public let items: [StorageReference] /** - * Returns a token that can be used to resume a previous `list()` operation. `nil` + * A token that can be used to resume a previous `list()` operation. `nil` * indicates that there are no more results. - * - * - Returns: A page token if more results are available. */ @objc public let pageToken: String? diff --git a/FirebaseStorage/Sources/StorageMetadata.swift b/FirebaseStorage/Sources/StorageMetadata.swift index 770aeada8f1..0f45ca345a5 100644 --- a/FirebaseStorage/Sources/StorageMetadata.swift +++ b/FirebaseStorage/Sources/StorageMetadata.swift @@ -15,11 +15,12 @@ import Foundation /** - * Class which represents the metadata on an object in Firebase Storage. This metadata is + * Class which represents the metadata on an object in Firebase Storage. + * + * This metadata is * returned on successful operations, and can be used to retrieve download URLs, content types, - * and a Storage reference to the object in question. Full documentation can be found at the GCS - * Objects#resource docs. - * @see https://cloud.google.com/storage/docs/json_api/v1/objects#resource + * and a Storage reference to the object in question. Full documentation can be found in the + * [GCS documentation](https://cloud.google.com/storage/docs/json_api/v1/objects#resource) */ @objc(FIRStorageMetadata) open class StorageMetadata: NSObject { // MARK: - Public APIs @@ -145,6 +146,10 @@ import Foundation // MARK: - Public Initializers + /** + * Creates an empty instance of StorageMetadata. + * @return An empty instance of StorageMetadata. + */ @objc override public convenience init() { self.init(dictionary: [:]) } diff --git a/FirebaseStorage/Sources/StorageObservableTask.swift b/FirebaseStorage/Sources/StorageObservableTask.swift index 33a9667f420..f302fe152be 100644 --- a/FirebaseStorage/Sources/StorageObservableTask.swift +++ b/FirebaseStorage/Sources/StorageObservableTask.swift @@ -23,6 +23,7 @@ import Foundation /** * An extended `StorageTask` providing observable semantics that can be used for responding to changes * in task state. + * * Observers produce a `StorageHandle`, which is used to keep track of and remove specific * observers at a later date. */ @@ -80,7 +81,7 @@ import Foundation /** * Removes the single observer with the provided handle. - * - Parameter handle The handle of the task to remove. + * - Parameter handle: The handle of the task to remove. */ @objc(removeObserverWithHandle:) open func removeObserver(withHandle handle: String) { if let status = handleToStatusMap[handle] { @@ -93,7 +94,7 @@ import Foundation /** * Removes all observers for a single status. - * - Parameter status A `StorageTaskStatus` to remove all listeners for. + * - Parameter status: A `StorageTaskStatus` to remove all listeners for. */ @objc(removeAllObserversForStatus:) open func removeAllObservers(for status: StorageTaskStatus) { diff --git a/FirebaseStorage/Sources/StorageReference.swift b/FirebaseStorage/Sources/StorageReference.swift index d81377d5c7d..98eb595c733 100644 --- a/FirebaseStorage/Sources/StorageReference.swift +++ b/FirebaseStorage/Sources/StorageReference.swift @@ -14,61 +14,49 @@ import Foundation -/** - * `StorageReference` represents a reference to a Google Cloud Storage object. Developers can - * upload and download objects, as well as get/set object metadata, and delete an object at the - * path. See the Cloud docs for more details: https://cloud.google.com/storage/ - */ - +/// `StorageReference` represents a reference to a Google Cloud Storage object. Developers can +/// upload and download objects, as well as get/set object metadata, and delete an object at the +/// path. See the [Cloud docs](https://cloud.google.com/storage/) for more details. @objc(FIRStorageReference) open class StorageReference: NSObject { // MARK: - Public APIs - /** - * The `Storage` service object which created this reference. - */ + /// The `Storage` service object which created this reference. @objc public let storage: Storage - /** - * The name of the Google Cloud Storage bucket associated with this reference. - * For example, in `gs://bucket/path/to/object.txt`, the bucket would be 'bucket'. - */ + /// The name of the Google Cloud Storage bucket associated with this reference. + /// For example, in `gs://bucket/path/to/object.txt`, the bucket would be 'bucket'. @objc public var bucket: String { return path.bucket } - /** - * The full path to this object, not including the Google Cloud Storage bucket. - * In `gs://bucket/path/to/object.txt`, the full path would be: `path/to/object.txt` - */ + /// The full path to this object, not including the Google Cloud Storage bucket. + /// In `gs://bucket/path/to/object.txt`, the full path would be: `path/to/object.txt`. @objc public var fullPath: String { return path.object ?? "" } - /** - * The short name of the object associated with this reference. - * In `gs://bucket/path/to/object.txt`, the name of the object would be `object.txt`. - */ + /// The short name of the object associated with this reference. + /// + /// In `gs://bucket/path/to/object.txt`, the name of the object would be `object.txt`. @objc public var name: String { return (path.object as? NSString)?.lastPathComponent ?? "" } - /** - * Creates a new `StorageReference` pointing to the root object. - * - Returns: A new `StorageReference` pointing to the root object. - */ + /// Creates a new `StorageReference` pointing to the root object. + /// - Returns: A new `StorageReference` pointing to the root object. @objc open func root() -> StorageReference { return StorageReference(storage: storage, path: path.root()) } - /** - * Creates a new `StorageReference` pointing to the parent of the current reference - * or `nil` if this instance references the root location. - * For example: - * path = foo/bar/baz parent = foo/bar - * path = foo parent = (root) - * path = (root) parent = nil - * - Returns: A new `StorageReference` pointing to the parent of the current reference. - */ + /// Creates a new `StorageReference` pointing to the parent of the current reference + /// or `nil` if this instance references the root location. + /// ``` + /// For example: + /// path = foo/bar/baz parent = foo/bar + /// path = foo parent = (root) + /// path = (root) parent = nil + /// ``` + /// - Returns: A new `StorageReference` pointing to the parent of the current reference. @objc open func parent() -> StorageReference? { guard let parentPath = path.parent() else { return nil @@ -76,61 +64,59 @@ import Foundation return StorageReference(storage: storage, path: parentPath) } - /** - * Creates a new `StorageReference` pointing to a child object of the current reference. - * path = foo child = bar newPath = foo/bar - * path = foo/bar child = baz ntask.impl.snapshotwPath = foo/bar/baz - * All leading and trailing slashes will be removed, and consecutive slashes will be - * compressed to single slashes. For example: - * child = /foo/bar newPath = foo/bar - * child = foo/bar/ newPath = foo/bar - * child = foo///bar newPath = foo/bar - * - Parameter path The path to append to the current path. - * - Returns: A new `StorageReference` pointing to a child location of the current reference. - */ + /// Creates a new `StorageReference` pointing to a child object of the current reference. + /// ``` + /// path = foo child = bar newPath = foo/bar + /// path = foo/bar child = baz ntask.impl.snapshotwPath = foo/bar/baz + /// All leading and trailing slashes will be removed, and consecutive slashes will be + /// compressed to single slashes. For example: + /// child = /foo/bar newPath = foo/bar + /// child = foo/bar/ newPath = foo/bar + /// child = foo///bar newPath = foo/bar + /// ``` + /// + /// - Parameter path: The path to append to the current path. + /// - Returns: A new `StorageReference` pointing to a child location of the current reference. @objc(child:) open func child(_ path: String) -> StorageReference { return StorageReference(storage: storage, path: self.path.child(path)) } // MARK: - Uploads - /** - * Asynchronously uploads data to the currently specified `StorageReference`, - * without additional metadata. - * This is not recommended for large files, and one should instead upload a file from disk. - * - Parameters: - * - uploadData: The data to upload. - * - metadata: `StorageMetadata` containing additional information (MIME type, etc.) - * about the object being uploaded. - * - Returns: An instance of `StorageUploadTask`, which can be used to monitor or manage the upload. - */ + /// Asynchronously uploads data to the currently specified `StorageReference`, + /// without additional metadata. + /// This is not recommended for large files, and one should instead upload a file from disk. + /// - Parameters: + /// - uploadData: The data to upload. + /// - metadata: `StorageMetadata` containing additional information (MIME type, etc.) + /// about the object being uploaded. + /// - Returns: An instance of `StorageUploadTask`, which can be used to monitor or manage the + /// upload. @objc(putData:metadata:) @discardableResult open func putData(_ uploadData: Data, metadata: StorageMetadata? = nil) -> StorageUploadTask { return putData(uploadData, metadata: metadata, completion: nil) } - /** - * Asynchronously uploads data to the currently specified `StorageReference`. - * This is not recommended for large files, and one should instead upload a file from disk. - * - Parameter uploadData The data to upload. - * - Returns: An instance of `StorageUploadTask`, which can be used to monitor or manage the upload. - */ + /// Asynchronously uploads data to the currently specified `StorageReference`. + /// This is not recommended for large files, and one should instead upload a file from disk. + /// - Parameter uploadData The data to upload. + /// - Returns: An instance of `StorageUploadTask`, which can be used to monitor or manage the + /// upload. @objc(putData:) @discardableResult open func __putData(_ uploadData: Data) -> StorageUploadTask { return putData(uploadData, metadata: nil, completion: nil) } - /** - * Asynchronously uploads data to the currently specified `StorageReference`. - * This is not recommended for large files, and one should instead upload a file from disk. - * - Parameters: - * - uploadData: The data to upload. - * - metadata: `StorageMetadata` containing additional information (MIME type, etc.) - * about the object being uploaded. - * - completion: A closure that either returns the object metadata on success, - * or an error on failure. - * - Returns: An instance of `StorageUploadTask`, which can be used to monitor or manage the upload. - */ + /// Asynchronously uploads data to the currently specified `StorageReference`. + /// This is not recommended for large files, and one should instead upload a file from disk. + /// - Parameters: + /// - uploadData: The data to upload. + /// - metadata: `StorageMetadata` containing additional information (MIME type, etc.) + /// about the object being uploaded. + /// - completion: A closure that either returns the object metadata on success, + /// or an error on failure. + /// - Returns: An instance of `StorageUploadTask`, which can be used to monitor or manage the + /// upload. @objc(putData:metadata:completion:) @discardableResult open func putData(_ uploadData: Data, metadata: StorageMetadata? = nil, @@ -149,42 +135,38 @@ import Foundation return task } - /** - * Asynchronously uploads a file to the currently specified `StorageReference`. - * `putData` should be used instead of `putFile` in Extensions. - * - Parameters: - * - fileURL: A URL representing the system file path of the object to be uploaded. - * - metadata: `StorageMetadata` containing additional information (MIME type, etc.) - * about the object being uploaded. - * - Returns: An instance of `StorageUploadTask`, which can be used to monitor or manage the upload. - */ + /// Asynchronously uploads a file to the currently specified `StorageReference`. + /// `putData` should be used instead of `putFile` in Extensions. + /// - Parameters: + /// - fileURL: A URL representing the system file path of the object to be uploaded. + /// - metadata: `StorageMetadata` containing additional information (MIME type, etc.) + /// about the object being uploaded. + /// - Returns: An instance of `StorageUploadTask`, which can be used to monitor or manage the + /// upload. @objc(putFile:metadata:) @discardableResult open func putFile(from fileURL: URL, metadata: StorageMetadata? = nil) -> StorageUploadTask { return putFile(from: fileURL, metadata: metadata, completion: nil) } - /** - * Asynchronously uploads a file to the currently specified `StorageReference`, - * without additional metadata. - * `putData` should be used instead of `putFile` in Extensions. - * @param fileURL A URL representing the system file path of the object to be uploaded. - * @return An instance of StorageUploadTask, which can be used to monitor or manage the upload. - */ + /// Asynchronously uploads a file to the currently specified `StorageReference`, + /// without additional metadata. + /// `putData` should be used instead of `putFile` in Extensions. + /// @param fileURL A URL representing the system file path of the object to be uploaded. + /// @return An instance of StorageUploadTask, which can be used to monitor or manage the upload. @objc(putFile:) @discardableResult open func __putFile(from fileURL: URL) -> StorageUploadTask { return putFile(from: fileURL, metadata: nil, completion: nil) } - /** - * Asynchronously uploads a file to the currently specified `StorageReference`. - * `putData` should be used instead of `putFile` in Extensions. - * - Parameters: - * - fileURL: A URL representing the system file path of the object to be uploaded. - * - metadata: `StorageMetadata` containing additional information (MIME type, etc.) - * about the object being uploaded. - * - completion: A completion block that either returns the object metadata on success, - * or an error on failure. - * - Returns: An instance of `StorageUploadTask`, which can be used to monitor or manage the upload. - */ + /// Asynchronously uploads a file to the currently specified `StorageReference`. + /// `putData` should be used instead of `putFile` in Extensions. + /// - Parameters: + /// - fileURL: A URL representing the system file path of the object to be uploaded. + /// - metadata: `StorageMetadata` containing additional information (MIME type, etc.) + /// about the object being uploaded. + /// - completion: A completion block that either returns the object metadata on success, + /// or an error on failure. + /// - Returns: An instance of `StorageUploadTask`, which can be used to monitor or manage the + /// upload. @objc(putFile:metadata:completion:) @discardableResult open func putFile(from fileURL: URL, metadata: StorageMetadata? = nil, @@ -205,17 +187,17 @@ import Foundation // MARK: - Downloads - /** - * Asynchronously downloads the object at the `StorageReference` to a `Data` instance in memory. - * A `Data` buffer of the provided max size will be allocated, so ensure that the device has enough free - * memory to complete the download. For downloading large files, `write(toFile:)` may be a better option. - * - Parameters: - * - maxSize: The maximum size in bytes to download. If the download exceeds this size, - * the task will be cancelled and an error will be returned. - * - completion: A completion block that either returns the object data on success, - * or an error on failure. - * - Returns: An `StorageDownloadTask` that can be used to monitor or manage the download. - */ + /// Asynchronously downloads the object at the `StorageReference` to a `Data` instance in memory. + /// A `Data` buffer of the provided max size will be allocated, so ensure that the device has + /// enough free + /// memory to complete the download. For downloading large files, `write(toFile:)` may be a better + /// option. + /// - Parameters: + /// - maxSize: The maximum size in bytes to download. If the download exceeds this size, + /// the task will be cancelled and an error will be returned. + /// - completion: A completion block that either returns the object data on success, + /// or an error on failure. + /// - Returns: A `StorageDownloadTask` that can be used to monitor or manage the download. @objc(dataWithMaxSize:completion:) @discardableResult open func getData(maxSize: Int64, completion: @escaping ((_: Data?, _: Error?) -> Void)) -> StorageDownloadTask { @@ -253,13 +235,11 @@ import Foundation return task } - /** - * Asynchronously retrieves a long lived download URL with a revokable token. - * This can be used to share the file with others, but can be revoked by a developer - * in the Firebase Console. - * - Parameter completion A completion block that either returns the URL on success, - * or an error on failure. - */ + /// Asynchronously retrieves a long lived download URL with a revokable token. + /// This can be used to share the file with others, but can be revoked by a developer + /// in the Firebase Console. + /// - Parameter completion: A completion block that either returns the URL on success, + /// or an error on failure. @objc(downloadURLWithCompletion:) open func downloadURL(completion: @escaping ((_: URL?, _: Error?) -> Void)) { let fetcherService = storage.fetcherServiceForApp @@ -270,13 +250,11 @@ import Foundation task.enqueue() } - /** - * Asynchronously retrieves a long lived download URL with a revokable token. - * This can be used to share the file with others, but can be revoked by a developer - * in the Firebase Console. - * - Throws: An error if the download URL could not be retrieved. - * - Returns: The URL on success. - */ + /// Asynchronously retrieves a long lived download URL with a revokable token. + /// This can be used to share the file with others, but can be revoked by a developer + /// in the Firebase Console. + /// - Throws: An error if the download URL could not be retrieved. + /// - Returns: The URL on success. @available(iOS 13, tvOS 13, macOS 10.15, watchOS 8, *) open func downloadURL() async throws -> URL { return try await withCheckedThrowingContinuation { continuation in @@ -286,25 +264,22 @@ import Foundation } } - /** - * Asynchronously downloads the object at the current path to a specified system filepath. - * - Parameter fileURL A file system URL representing the path the object should be downloaded to. - * - Returns An `StorageDownloadTask` that can be used to monitor or manage the download. - */ + /// Asynchronously downloads the object at the current path to a specified system filepath. + /// - Parameter fileURL: A file system URL representing the path the object should be downloaded + /// to. + /// - Returns A `StorageDownloadTask` that can be used to monitor or manage the download. @objc(writeToFile:) @discardableResult open func write(toFile fileURL: URL) -> StorageDownloadTask { return write(toFile: fileURL, completion: nil) } - /** - * Asynchronously downloads the object at the current path to a specified system filepath. - * - Parameters: - * - fileURL: A file system URL representing the path the object should be downloaded to. - * - completion: A closure that fires when the file download completes, passed either - * a URL pointing to the file path of the downloaded file on success, - * or an error on failure. - * - Returns: A `StorageDownloadTask` that can be used to monitor or manage the download. - */ + /// Asynchronously downloads the object at the current path to a specified system filepath. + /// - Parameters: + /// - fileURL: A file system URL representing the path the object should be downloaded to. + /// - completion: A closure that fires when the file download completes, passed either + /// a URL pointing to the file path of the downloaded file on success, + /// or an error on failure. + /// - Returns: A `StorageDownloadTask` that can be used to monitor or manage the download. @objc(writeToFile:completion:) @discardableResult open func write(toFile fileURL: URL, completion: ((_: URL?, _: Error?) -> Void)?) -> StorageDownloadTask { @@ -337,18 +312,17 @@ import Foundation // MARK: - List Support - /** - * Lists all items (files) and prefixes (folders) under this `StorageReference`. - * - * This is a helper method for calling `list()` repeatedly until there are no more results. - * Consistency of the result is not guaranteed if objects are inserted or removed while this - * operation is executing. All results are buffered in memory. - * - * `listAll(completion:)` is only available for projects using Firebase Rules Version 2. - * - * - Parameter completion A completion handler that will be invoked with all items and prefixes under - * the current `StorageReference`. - */ + /// Lists all items (files) and prefixes (folders) under this `StorageReference`. + /// + /// This is a helper method for calling `list()` repeatedly until there are no more results. + /// + /// Consistency of the result is not guaranteed if objects are inserted or removed while this + /// operation is executing. All results are buffered in memory. + /// + /// `listAll(completion:)` is only available for projects using Firebase Rules Version 2. + /// - Parameter completion: A completion handler that will be invoked with all items and prefixes + /// under + /// the current `StorageReference`. @objc(listAllWithCompletion:) open func listAll(completion: @escaping ((_: StorageListResult?, _: Error?) -> Void)) { let fetcherService = storage.fetcherServiceForApp @@ -396,18 +370,13 @@ import Foundation task.enqueue() } - /** - * Lists all items (files) and prefixes (folders) under this StorageReference. - * - * This is a helper method for calling list() repeatedly until there are no more results. - * Consistency of the result is not guaranteed if objects are inserted or removed while this - * operation is executing. All results are buffered in memory. - * - * `listAll()` is only available for projects using Firebase Rules Version 2. - * - * - Throws: An error if the list operation failed. - * - Returns: All items and prefixes under the current `StorageReference`. - */ + /// Lists all items (files) and prefixes (folders) under this StorageReference. + /// This is a helper method for calling list() repeatedly until there are no more results. + /// Consistency of the result is not guaranteed if objects are inserted or removed while this + /// operation is executing. All results are buffered in memory. + /// `listAll()` is only available for projects using Firebase Rules Version 2. + /// - Throws: An error if the list operation failed. + /// - Returns: All items and prefixes under the current `StorageReference`. @available(iOS 13, tvOS 13, macOS 10.15, watchOS 8, *) open func listAll() async throws -> StorageListResult { return try await withCheckedThrowingContinuation { continuation in @@ -417,21 +386,18 @@ import Foundation } } - /** - * List up to `maxResults` items (files) and prefixes (folders) under this StorageReference. - * - * "/" is treated as a path delimiter. Firebase Storage does not support unsupported object - * paths that end with "/" or contain two consecutive "/"s. All invalid objects in GCS will be - * filtered. - * - * `list(maxResults:completion:)` is only available for projects using Firebase Rules Version 2. - * - * - Parameters: - * - maxResults: The maximum number of results to return in a single page. Must be greater - * than 0 and at most 1000. - * - completion: A completion handler that will be invoked with up to `maxResults` items and - * prefixes under the current `StorageReference`. - */ + /// List up to `maxResults` items (files) and prefixes (folders) under this StorageReference. + /// + /// "/" is treated as a path delimiter. Firebase Storage does not support unsupported object + /// paths that end with "/" or contain two consecutive "/"s. All invalid objects in GCS will be + /// filtered. + /// + /// Only available for projects using Firebase Rules Version 2. + /// - Parameters: + /// - maxResults: The maximum number of results to return in a single page. Must be + /// greater than 0 and at most 1000. + /// - completion: A completion handler that will be invoked with up to `maxResults` items and + /// prefixes under the current `StorageReference`. @objc(listWithMaxResults:completion:) open func list(maxResults: Int64, completion: @escaping ((_: StorageListResult?, _: Error?) -> Void)) { @@ -453,24 +419,22 @@ import Foundation } } - /** - * Resumes a previous call to `list(maxResults:completion:)`, starting after a pagination token. - * Returns the next set of items (files) and prefixes (folders) under this `StorageReference`. - * - * "/" is treated as a path delimiter. Storage does not support unsupported object - * paths that end with "/" or contain two consecutive "/"s. All invalid objects in GCS will be - * filtered. - * - * `list(maxResults:pageToken:completion:)`is only available for projects using Firebase Rules - * Version 2. - * - * - Parameters: - * - maxResults: The maximum number of results to return in a single page. Must be greater - * than 0 and at most 1000. - * - pageToken: A page token from a previous call to list. - * - completion: A completion handler that will be invoked with the next items and prefixes - * under the current StorageReference. - */ + /// Resumes a previous call to `list(maxResults:completion:)`, starting after a pagination token. + /// + /// Returns the next set of items (files) and prefixes (folders) under this `StorageReference`. + /// + /// "/" is treated as a path delimiter. Storage does not support unsupported object + /// paths that end with "/" or contain two consecutive "/"s. All invalid objects in GCS will be + /// filtered. + /// + /// `list(maxResults:pageToken:completion:)`is only available for projects using Firebase Rules + /// Version 2. + /// - Parameters: + /// - maxResults: The maximum number of results to return in a single page. Must be greater + /// than 0 and at most 1000. + /// - pageToken: A page token from a previous call to list. + /// - completion: A completion handler that will be invoked with the next items and prefixes + /// under the current StorageReference. @objc(listWithMaxResults:pageToken:completion:) open func list(maxResults: Int64, pageToken: String, @@ -495,11 +459,9 @@ import Foundation // MARK: - Metadata Operations - /** - * Retrieves metadata associated with an object at the current path. - * - Parameter completion A completion block which returns the object metadata on success, - * or an error on failure. - */ + /// Retrieves metadata associated with an object at the current path. + /// - Parameter completion: A completion block which returns the object metadata on success, + /// or an error on failure. @objc(metadataWithCompletion:) open func getMetadata(completion: @escaping ((_: StorageMetadata?, _: Error?) -> Void)) { let fetcherService = storage.fetcherServiceForApp @@ -510,11 +472,9 @@ import Foundation task.enqueue() } - /** - * Retrieves metadata associated with an object at the current path. - * - Throws: An error if the object metadata could not be retrieved. - * - Returns: The object metadata on success. - */ + /// Retrieves metadata associated with an object at the current path. + /// - Throws: An error if the object metadata could not be retrieved. + /// - Returns: The object metadata on success. @available(iOS 13, tvOS 13, macOS 10.15, watchOS 8, *) open func getMetadata() async throws -> StorageMetadata { return try await withCheckedThrowingContinuation { continuation in @@ -524,13 +484,11 @@ import Foundation } } - /** - * Updates the metadata associated with an object at the current path. - * - Parameters: - * - metadata: A `StorageMetadata` object with the metadata to update. - * - completion: A completion block which returns the `StorageMetadata` on success, - * or an error on failure. - */ + /// Updates the metadata associated with an object at the current path. + /// - Parameters: + /// - metadata: A `StorageMetadata` object with the metadata to update. + /// - completion: A completion block which returns the `StorageMetadata` on success, + /// or an error on failure. @objc(updateMetadata:completion:) open func updateMetadata(_ metadata: StorageMetadata, completion: ((_: StorageMetadata?, _: Error?) -> Void)?) { @@ -543,12 +501,10 @@ import Foundation task.enqueue() } - /** - * Updates the metadata associated with an object at the current path. - * - Parameter metadata A `StorageMetadata` object with the metadata to update. - * - Throws: An error if the metadata update operation failed. - * - Returns: The object metadata on success. - */ + /// Updates the metadata associated with an object at the current path. + /// - Parameter metadata: A `StorageMetadata` object with the metadata to update. + /// - Throws: An error if the metadata update operation failed. + /// - Returns: The object metadata on success. @available(iOS 13, tvOS 13, macOS 10.15, watchOS 8, *) open func updateMetadata(_ metadata: StorageMetadata) async throws -> StorageMetadata { return try await withCheckedThrowingContinuation { continuation in @@ -560,10 +516,8 @@ import Foundation // MARK: - Delete - /** - * Deletes the object at the current path. - * - Parameter completion A completion block which returns a nonnull error on failure. - */ + /// Deletes the object at the current path. + /// - Parameter completion: A completion block which returns a nonnull error on failure. @objc(deleteWithCompletion:) open func delete(completion: ((_: Error?) -> Void)?) { let fetcherService = storage.fetcherServiceForApp @@ -574,10 +528,8 @@ import Foundation task.enqueue() } - /** - * Deletes the object at the current path. - * - Throws: An error if the delete operation failed. - */ + /// Deletes the object at the current path. + /// - Throws: An error if the delete operation failed. @available(iOS 13, tvOS 13, macOS 10.15, watchOS 8, *) open func delete() async throws { return try await withCheckedThrowingContinuation { continuation in @@ -593,10 +545,12 @@ import Foundation // MARK: - NSObject overrides + /// NSObject override @objc override open func copy() -> Any { return StorageReference(storage: storage, path: path) } + /// NSObject override @objc override open func isEqual(_ object: Any?) -> Bool { guard let ref = object as? StorageReference else { return false @@ -604,19 +558,19 @@ import Foundation return storage == ref.storage && path == ref.path } + /// NSObject override @objc override public var hash: Int { return storage.hash ^ path.bucket.hashValue } + /// NSObject override @objc override public var description: String { return "gs://\(path.bucket)/\(path.object ?? "")" } // MARK: - Internal APIs - /** - * The current path which points to an object in the Google Cloud Storage bucket. - */ + /// The current path which points to an object in the Google Cloud Storage bucket. let path: StoragePath override init() { @@ -630,9 +584,7 @@ import Foundation self.path = path } - /** - * For maxSize API, return an error if the size is exceeded. - */ + /// For maxSize API, return an error if the size is exceeded. private func checkSizeOverflow(task: StorageTask, maxSize: Int64) -> NSError? { if task.progress.totalUnitCount > maxSize || task.progress.completedUnitCount > maxSize { return StorageErrorCode.error(withCode: .downloadSizeExceeded, diff --git a/FirebaseStorage/Sources/StorageTask.swift b/FirebaseStorage/Sources/StorageTask.swift index 521beabeaaa..fe119c05c92 100644 --- a/FirebaseStorage/Sources/StorageTask.swift +++ b/FirebaseStorage/Sources/StorageTask.swift @@ -24,6 +24,7 @@ import Foundation * A superclass to all Storage tasks, including `StorageUploadTask` * and `StorageDownloadTask`, to provide state transitions, event raising, and common storage * for metadata and errors. + * * Callbacks are always fired on the developer-specified callback queue. * If no queue is specified, it defaults to the main queue. * This class is thread-safe. @@ -98,6 +99,7 @@ import Foundation /** * Defines task operations such as pause, resume, cancel, and enqueue for all tasks. + * * All tasks are required to implement enqueue, which begins the task, and may optionally * implement pause, resume, and cancel, which operate on the task to pause, resume, and cancel * operations. diff --git a/FirebaseStorage/Sources/StorageUploadTask.swift b/FirebaseStorage/Sources/StorageUploadTask.swift index 77e01f83cc4..759fd6b47c5 100644 --- a/FirebaseStorage/Sources/StorageUploadTask.swift +++ b/FirebaseStorage/Sources/StorageUploadTask.swift @@ -22,10 +22,13 @@ import Foundation /** * `StorageUploadTask` implements resumable uploads to a file in Firebase Storage. + * * Uploads can be returned on completion with a completion callback, and can be monitored * by attaching observers, or controlled by calling `pause()`, `resume()`, * or `cancel()`. + * * Uploads can be initialized from `Data` in memory, or a URL to a file on disk. + * * Uploads are performed on a background queue, and callbacks are raised on the developer * specified `callbackQueue` in Storage, or the main queue if unspecified. */ diff --git a/FirebaseStorage/Tests/ObjCIntegration/FIRStorageIntegrationTests.m b/FirebaseStorage/Tests/ObjCIntegration/FIRStorageIntegrationTests.m index bb9236d4c7a..bad1bdfa821 100644 --- a/FirebaseStorage/Tests/ObjCIntegration/FIRStorageIntegrationTests.m +++ b/FirebaseStorage/Tests/ObjCIntegration/FIRStorageIntegrationTests.m @@ -14,10 +14,9 @@ #import +@import FirebaseAuth; @import FirebaseStorage; -#import - #import "FirebaseCore/Extension/FirebaseCoreInternal.h" #import "FirebaseStorage/Tests/ObjCIntegration/Credentials.h" diff --git a/FirebaseStorage/Tests/Unit/StorageComponentTests.swift b/FirebaseStorage/Tests/Unit/StorageComponentTests.swift index dce5d75fe0d..a7851fb9479 100644 --- a/FirebaseStorage/Tests/Unit/StorageComponentTests.swift +++ b/FirebaseStorage/Tests/Unit/StorageComponentTests.swift @@ -70,14 +70,14 @@ class StorageComponentTests: StorageTestHelpers { in: container) XCTAssertNotNil(provider) - let storage1 = provider.storage(for: "randomBucket") - let storage2 = provider.storage(for: "randomBucket") + let storage1 = provider?.storage(for: "randomBucket") + let storage2 = provider?.storage(for: "randomBucket") XCTAssertNotNil(storage1) // Ensure they're the same instance. XCTAssert(storage1 === storage2) - let storage3 = provider.storage(for: "differentBucket") + let storage3 = provider?.storage(for: "differentBucket") XCTAssertNotNil(storage3) XCTAssert(storage1 !== storage3) diff --git a/FirebaseTestingSupport/Auth/Sources/FIRPhoneAuthProviderFake.m b/FirebaseTestingSupport/Auth/Sources/FIRPhoneAuthProviderFake.m deleted file mode 100644 index 6a93e51ad27..00000000000 --- a/FirebaseTestingSupport/Auth/Sources/FIRPhoneAuthProviderFake.m +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2021 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 "FirebaseTestingSupport/Auth/Sources/Public/FirebaseAuthTestingSupport/FIRPhoneAuthProviderFake.h" - -@implementation FIRPhoneAuthProviderFake - -- (instancetype)init { - // The object is partially initialized. Make sure the methods used during testing are overridden. - return self; -} - -- (void)verifyPhoneNumber:(NSString *)phoneNumber - UIDelegate:(id)UIDelegate - completion:(FIRVerificationResultCallback)completion { - if (self.verifyPhoneNumberHandler) { - self.verifyPhoneNumberHandler(completion); - } -} - -@end diff --git a/FirebaseTestingSupport/Auth/Sources/Public/FirebaseAuthTestingSupport/FIRPhoneAuthProviderFake.h b/FirebaseTestingSupport/Auth/Sources/PhoneAuthProviderFake.swift similarity index 50% rename from FirebaseTestingSupport/Auth/Sources/Public/FirebaseAuthTestingSupport/FIRPhoneAuthProviderFake.h rename to FirebaseTestingSupport/Auth/Sources/PhoneAuthProviderFake.swift index 3cded28cff8..fa6e9d9aa4f 100644 --- a/FirebaseTestingSupport/Auth/Sources/Public/FirebaseAuthTestingSupport/FIRPhoneAuthProviderFake.h +++ b/FirebaseTestingSupport/Auth/Sources/PhoneAuthProviderFake.swift @@ -1,4 +1,4 @@ -// Copyright 2021 Google LLC +// 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. @@ -12,24 +12,23 @@ // See the License for the specific language governing permissions and // limitations under the License. -#import - -NS_ASSUME_NONNULL_BEGIN - -typedef void (^FIRVerifyPhoneNumberHandler)(FIRVerificationResultCallback completion); +@testable import FirebaseAuth +import Foundation /// A fake object to replace a real `AuthAPNSTokenManager` in tests. -NS_SWIFT_NAME(PhoneAuthProviderFake) -@interface FIRPhoneAuthProviderFake : FIRPhoneAuthProvider - -- (instancetype)init; - -/// The block to be called each time when `verifyPhoneNumber(_:uiDelegate:completion:)` method is -/// called. -@property(nonatomic, nullable, copy) FIRVerifyPhoneNumberHandler verifyPhoneNumberHandler; - -// TODO: Implement other handlers as needed. - -@end - -NS_ASSUME_NONNULL_END +public class PhoneAuthProviderFake: PhoneAuthProvider { + override init(auth: Auth) { + super.init(auth: auth) + } + + var verifyPhoneNumberHandler: (((String?, Error?) -> Void) -> Void)? + + override public func verifyPhoneNumber(_ phoneNumber: String, + uiDelegate: AuthUIDelegate? = nil, + completion: ((_: String?, _: Error?) -> Void)?) { + if let verifyPhoneNumberHandler, + let completion { + verifyPhoneNumberHandler(completion) + } + } +} diff --git a/FirebaseTestingSupport/Auth/Tests/PhoneAuthProviderFakeTests.swift b/FirebaseTestingSupport/Auth/Tests/PhoneAuthProviderFakeTests.swift index 388cd178f41..7cb52267c6e 100644 --- a/FirebaseTestingSupport/Auth/Tests/PhoneAuthProviderFakeTests.swift +++ b/FirebaseTestingSupport/Auth/Tests/PhoneAuthProviderFakeTests.swift @@ -12,31 +12,47 @@ // See the License for the specific language governing permissions and // limitations under the License. +@testable import FirebaseAuth @testable import FirebaseAuthTestingSupport +import FirebaseCore import Foundation import XCTest class PhoneAuthProviderFakeTests: XCTestCase { + var auth: Auth! + static var testNum = 0 + override func setUp() { + super.setUp() + let options = FirebaseOptions(googleAppID: "0:0000000000000:ios:0000000000000000", + gcmSenderID: "00000000000000000-00000000000-000000000") + options.apiKey = "TEST_API_KEY" + options.projectID = "myProjectID" + PhoneAuthProviderFakeTests.testNum = PhoneAuthProviderFakeTests.testNum + 1 + let name = "test-name\(PhoneAuthProviderFakeTests.testNum)" + FirebaseApp.configure(name: name, options: options) + auth = Auth( + app: FirebaseApp.app(name: name)! + ) + } + func testPhoneAuthProviderFakeConstructor() throws { - let fakePhoneAuthProvider = PhoneAuthProviderFake() + let fakePhoneAuthProvider = PhoneAuthProviderFake(auth: auth) XCTAssertNotNil(fakePhoneAuthProvider) - XCTAssertTrue(fakePhoneAuthProvider.isKind(of: PhoneAuthProvider.self)) } func testVerifyPhoneNumberHandler() { - let fakePhoneAuthProvider = PhoneAuthProviderFake() + let fakePhoneAuthProvider = PhoneAuthProviderFake(auth: auth) let handlerExpectation = expectation(description: "Handler called") fakePhoneAuthProvider.verifyPhoneNumberHandler = { completion in handlerExpectation.fulfill() - - completion(nil, nil) + completion("test-id", nil) } let completionExpectation = expectation(description: "Completion called") - fakePhoneAuthProvider.verifyPhoneNumber("", uiDelegate: nil) { verficationID, error in + fakePhoneAuthProvider.verifyPhoneNumber("", uiDelegate: nil) { verificationID, error in completionExpectation.fulfill() - XCTAssertNil(verficationID) + XCTAssertEqual(verificationID, "test-id") XCTAssertNil(error) } diff --git a/Firestore/CHANGELOG.md b/Firestore/CHANGELOG.md index 4e5bc22d8b9..9c2b16d7b47 100644 --- a/Firestore/CHANGELOG.md +++ b/Firestore/CHANGELOG.md @@ -1,4 +1,13 @@ -# Unreleased +# 10.23.0 +- [feature] Enable snapshot listener option to retrieve data from local cache only. (#12370) +- [fixed] Update gRPC dependency to 1.62.* (#12098, #12021) +- [feature] Firestore's binary Swift Package Manager distribution uses + XCFrameworks with code signatures (#12238). + +# 10.22.0 +- [fixed] Fix the flaky offline behaviour when using `arrayRemove` on `Map` object. (#12378) + +# 10.21.0 - Add an error when trying to build Firestore's binary SPM distribution for visionOS (#12279). See Firestore's 10.12.0 release note for a supported workaround. diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj index b0aa3693dd8..99f967e3ccd 100644 --- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj +++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj @@ -205,6 +205,7 @@ 1CC56DCA513B98CE39A6ED45 /* memory_local_store_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F6CA0C5638AB6627CB5B4CF4 /* memory_local_store_test.cc */; }; 1CC9BABDD52B2A1E37E2698D /* mutation_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = C8522DE226C467C54E6788D8 /* mutation_test.cc */; }; 1CEEB0E7FBBB974224BBA557 /* bloom_filter_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = A2E6F09AD1EE0A6A452E9A08 /* bloom_filter_test.cc */; }; + 1CFBD4563960D8A20C4679A3 /* SnapshotListenerSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D65F6E69993611D47DC8E7C /* SnapshotListenerSourceTests.swift */; }; 1D618761796DE311A1707AA2 /* database_id_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB71064B201FA60300344F18 /* database_id_test.cc */; }; 1D71CA6BBA1E3433F243188E /* common.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D221C2DDC800EFB9CC /* common.pb.cc */; }; 1D76DDBE57A4D66C64C00B65 /* FIRFieldValueTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04A202154AA00B64F25 /* FIRFieldValueTests.mm */; }; @@ -238,6 +239,7 @@ 21E588CF29C72813D8A7A0A1 /* FSTExceptionCatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = B8BFD9B37D1029D238BDD71E /* FSTExceptionCatcher.m */; }; 21E66B6A4A00786C3E934EB1 /* query_engine_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B8A853940305237AFDA8050B /* query_engine_test.cc */; }; 224496E752E42E220F809FAC /* resource.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1C3F7302BF4AE6CBC00ECDD0 /* resource.pb.cc */; }; + 2252357505C92A067DAC38B0 /* listen_source_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 4D9E51DA7A275D8B1CAEAEB2 /* listen_source_spec_test.json */; }; 226574601C3F6D14DF14C16B /* recovery_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 9C1AFCC9E616EC33D6E169CF /* recovery_spec_test.json */; }; 227CFA0B2A01884C277E4F1D /* hashing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54511E8D209805F8005BD28F /* hashing_test.cc */; }; 229D1A9381F698D71F229471 /* string_win_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 79507DF8378D3C42F5B36268 /* string_win_test.cc */; }; @@ -508,6 +510,7 @@ 5150E9F256E6E82D6F3CB3F1 /* bundle_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F7FC06E0A47D393DE1759AE1 /* bundle_cache_test.cc */; }; 518BF03D57FBAD7C632D18F8 /* FIRQueryUnitTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = FF73B39D04D1760190E6B84A /* FIRQueryUnitTests.mm */; }; 51A483DE202CC3E9FCD8FF6E /* Validation_BloomFilterTest_MD5_5000_01_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = B0520A41251254B3C24024A3 /* Validation_BloomFilterTest_MD5_5000_01_membership_test_result.json */; }; + 5250AE69A391E7A3310E013B /* listen_source_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 4D9E51DA7A275D8B1CAEAEB2 /* listen_source_spec_test.json */; }; 52967C3DD7896BFA48840488 /* byte_string_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5342CDDB137B4E93E2E85CCA /* byte_string_test.cc */; }; 529AB59F636060FEA21BD4FF /* garbage_collection_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = AAED89D7690E194EF3BA1132 /* garbage_collection_spec_test.json */; }; 5360D52DCAD1069B1E4B0B9D /* testing_hooks_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = A002425BC4FC4E805F4175B6 /* testing_hooks_test.cc */; }; @@ -816,6 +819,7 @@ 6F67601562343B63B8996F7A /* FSTTestingHooks.mm in Sources */ = {isa = PBXBuildFile; fileRef = D85AC18C55650ED230A71B82 /* FSTTestingHooks.mm */; }; 6F914209F46E6552B5A79570 /* async_queue_std_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4681208EA0BE00554BA2 /* async_queue_std_test.cc */; }; 6FAC16B7FBD3B40D11A6A816 /* target.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE7D20B89AAC00B5BCE7 /* target.pb.cc */; }; + 6FB40B88ACB4CFB34917319C /* listen_source_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 4D9E51DA7A275D8B1CAEAEB2 /* listen_source_spec_test.json */; }; 6FC85C48CF8235BA1845E1C8 /* FSTUserDataReaderTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8D9892F204959C50613F16C8 /* FSTUserDataReaderTests.mm */; }; 6FCC64A1937E286E76C294D0 /* logic_utils_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 28B45B2104E2DAFBBF86DBB7 /* logic_utils_test.cc */; }; 6FD2369F24E884A9D767DD80 /* FIRDocumentSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04B202154AA00B64F25 /* FIRDocumentSnapshotTests.mm */; }; @@ -1050,6 +1054,7 @@ 9C86EEDEA131BFD50255EEF1 /* comparison_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 548DB928200D59F600E00ABC /* comparison_test.cc */; }; 9CC32ACF397022BB7DF11B52 /* Validation_BloomFilterTest_MD5_500_0001_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = D22D4C211AC32E4F8B4883DA /* Validation_BloomFilterTest_MD5_500_0001_bloom_filter_proto.json */; }; 9CE07BAAD3D3BC5F069D38FE /* grpc_streaming_reader_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6D964922154AB8F00EB9CFB /* grpc_streaming_reader_test.cc */; }; + 9CFF379C7404F7CE6B26AF29 /* listen_source_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 4D9E51DA7A275D8B1CAEAEB2 /* listen_source_spec_test.json */; }; 9D71628E38D9F64C965DF29E /* FSTAPIHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04E202154AA00B64F25 /* FSTAPIHelpers.mm */; }; 9E1997789F19BF2E9029012E /* FIRCompositeIndexQueryTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 65AF0AB593C3AD81A1F1A57E /* FIRCompositeIndexQueryTests.mm */; }; 9E656F4FE92E8BFB7F625283 /* to_string_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B696858D2214B53900271095 /* to_string_test.cc */; }; @@ -1059,6 +1064,7 @@ 9F9244225BE2EC88AA0CE4EF /* sorted_set_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA4C20A36DBB00BCEB75 /* sorted_set_test.cc */; }; A05BC6BDA2ABE405009211A9 /* target_id_generator_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380CF82019382300D97691 /* target_id_generator_test.cc */; }; A06FBB7367CDD496887B86F8 /* leveldb_opener_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 75860CD13AF47EB1EA39EC2F /* leveldb_opener_test.cc */; }; + A0BC30D482B0ABD1A3A24CDC /* SnapshotListenerSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D65F6E69993611D47DC8E7C /* SnapshotListenerSourceTests.swift */; }; A0C6C658DFEE58314586907B /* offline_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A11F315EE100DD57A1 /* offline_spec_test.json */; }; A0D61250F959BC52CEFF9467 /* Validation_BloomFilterTest_MD5_50000_01_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = C8FB22BCB9F454DA44BA80C8 /* Validation_BloomFilterTest_MD5_50000_01_membership_test_result.json */; }; A0E1C7F5C7093A498F65C5CF /* memory_bundle_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB4AB1388538CD3CB19EB028 /* memory_bundle_cache_test.cc */; }; @@ -1068,6 +1074,7 @@ A17DBC8F24127DA8A381F865 /* testutil.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54A0352820A3B3BD003E0143 /* testutil.cc */; }; A186FECD0257B92FDB0E83B8 /* Validation_BloomFilterTest_MD5_50000_0001_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = 5B96CC29E9946508F022859C /* Validation_BloomFilterTest_MD5_50000_0001_membership_test_result.json */; }; A192648233110B7B8BD65528 /* field_transform_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7515B47C92ABEEC66864B55C /* field_transform_test.cc */; }; + A1A466F55A1ED0AC5EE449BF /* listen_source_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 4D9E51DA7A275D8B1CAEAEB2 /* listen_source_spec_test.json */; }; A1F57CC739211F64F2E9232D /* hard_assert_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 444B7AB3F5A2929070CB1363 /* hard_assert_test.cc */; }; A215078DBFBB5A4F4DADE8A9 /* leveldb_index_manager_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 166CE73C03AB4366AAC5201C /* leveldb_index_manager_test.cc */; }; A21819C437C3C80450D7EEEE /* writer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = BC3C788D290A935C353CEAA1 /* writer_test.cc */; }; @@ -1156,11 +1163,13 @@ AFCA3C24AA751B5B2D3E6FEF /* Validation_BloomFilterTest_MD5_1_01_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = 0D964D4936953635AC7E0834 /* Validation_BloomFilterTest_MD5_1_01_bloom_filter_proto.json */; }; AFE84E7B0C356CD2A113E56E /* status_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 3CAA33F964042646FDDAF9F9 /* status_testing.cc */; }; AFF7D2CF35B51656E4744164 /* bloom_filter_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = A2E6F09AD1EE0A6A452E9A08 /* bloom_filter_test.cc */; }; + B00F8D1819EE20C45B660940 /* SnapshotListenerSourceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D65F6E69993611D47DC8E7C /* SnapshotListenerSourceTests.swift */; }; B03F286F3AEC3781C386C646 /* FIRNumericTransformTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = D5B25E7E7D6873CBA4571841 /* FIRNumericTransformTests.mm */; }; B04E4FE20930384DF3A402F9 /* aggregate_query_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AF924C79F49F793992A84879 /* aggregate_query_test.cc */; }; B0B779769926304268200015 /* query_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 731541602214AFFA0037F4DC /* query_spec_test.json */; }; B0D10C3451EDFB016A6EAF03 /* writer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = BC3C788D290A935C353CEAA1 /* writer_test.cc */; }; B0E745EAC5F37CA61F868F38 /* Validation_BloomFilterTest_MD5_50000_1_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = 4B3E4A77493524333133C5DC /* Validation_BloomFilterTest_MD5_50000_1_bloom_filter_proto.json */; }; + B104B69726EF6A5B41DAFB17 /* listen_source_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 4D9E51DA7A275D8B1CAEAEB2 /* listen_source_spec_test.json */; }; B15D17049414E2F5AE72C9C6 /* memory_local_store_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F6CA0C5638AB6627CB5B4CF4 /* memory_local_store_test.cc */; }; B188D7EC9A100F365DB02490 /* Validation_BloomFilterTest_MD5_500_01_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = DD990FD89C165F4064B4F608 /* Validation_BloomFilterTest_MD5_500_01_membership_test_result.json */; }; B192F30DECA8C28007F9B1D0 /* array_sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54EB764C202277B30088B8F3 /* array_sorted_map_test.cc */; }; @@ -1740,6 +1749,8 @@ 4B59C0A7B2A4548496ED4E7D /* Validation_BloomFilterTest_MD5_1_0001_bloom_filter_proto.json */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.json; name = Validation_BloomFilterTest_MD5_1_0001_bloom_filter_proto.json; path = bloom_filter_golden_test_data/Validation_BloomFilterTest_MD5_1_0001_bloom_filter_proto.json; sourceTree = ""; }; 4BD051DBE754950FEAC7A446 /* Validation_BloomFilterTest_MD5_500_01_bloom_filter_proto.json */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.json; name = Validation_BloomFilterTest_MD5_500_01_bloom_filter_proto.json; path = bloom_filter_golden_test_data/Validation_BloomFilterTest_MD5_500_01_bloom_filter_proto.json; sourceTree = ""; }; 4C73C0CC6F62A90D8573F383 /* string_apple_benchmark.mm */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.objcpp; path = string_apple_benchmark.mm; sourceTree = ""; }; + 4D65F6E69993611D47DC8E7C /* SnapshotListenerSourceTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = SnapshotListenerSourceTests.swift; sourceTree = ""; }; + 4D9E51DA7A275D8B1CAEAEB2 /* listen_source_spec_test.json */ = {isa = PBXFileReference; includeInIndex = 1; path = listen_source_spec_test.json; sourceTree = ""; }; 4F5B96F3ABCD2CA901DB1CD4 /* bundle_builder.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = bundle_builder.cc; sourceTree = ""; }; 526D755F65AC676234F57125 /* target_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = target_test.cc; sourceTree = ""; }; 52756B7624904C36FBB56000 /* fake_target_metadata_provider.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = fake_target_metadata_provider.h; sourceTree = ""; }; @@ -2221,6 +2232,7 @@ 3355BE9391CC4857AF0BDAE3 /* DatabaseTests.swift */, 62E54B832A9E910A003347C8 /* IndexingTests.swift */, 621D620928F9CE7400D2FA26 /* QueryIntegrationTests.swift */, + 4D65F6E69993611D47DC8E7C /* SnapshotListenerSourceTests.swift */, ); path = Integration; sourceTree = ""; @@ -2968,6 +2980,7 @@ 8C7278B604B8799F074F4E8C /* index_spec_test.json */, 54DA129E1F315EE100DD57A1 /* limbo_spec_test.json */, 54DA129F1F315EE100DD57A1 /* limit_spec_test.json */, + 4D9E51DA7A275D8B1CAEAEB2 /* listen_source_spec_test.json */, 54DA12A01F315EE100DD57A1 /* listen_spec_test.json */, 54DA12A11F315EE100DD57A1 /* offline_spec_test.json */, 54DA12A21F315EE100DD57A1 /* orderby_spec_test.json */, @@ -3378,6 +3391,7 @@ BFBE4732E93E38317B110778 /* index_spec_test.json in Resources */, 546877D72248206A005E3DE0 /* limbo_spec_test.json in Resources */, 546877D82248206A005E3DE0 /* limit_spec_test.json in Resources */, + 6FB40B88ACB4CFB34917319C /* listen_source_spec_test.json in Resources */, 546877D92248206A005E3DE0 /* listen_spec_test.json in Resources */, 546877DA2248206A005E3DE0 /* offline_spec_test.json in Resources */, 546877DB2248206A005E3DE0 /* orderby_spec_test.json in Resources */, @@ -3436,6 +3450,7 @@ 604B75044D6BEC2B7515EA1B /* index_spec_test.json in Resources */, 54ACB6CB224C11F400172E69 /* limbo_spec_test.json in Resources */, 54ACB6CC224C11F400172E69 /* limit_spec_test.json in Resources */, + 2252357505C92A067DAC38B0 /* listen_source_spec_test.json in Resources */, 54ACB6CD224C11F400172E69 /* listen_spec_test.json in Resources */, 54ACB6CE224C11F400172E69 /* offline_spec_test.json in Resources */, 54ACB6CF224C11F400172E69 /* orderby_spec_test.json in Resources */, @@ -3484,6 +3499,7 @@ 77C36312F8025EC73991D7DA /* index_spec_test.json in Resources */, F08DA55D31E44CB5B9170CCE /* limbo_spec_test.json in Resources */, 15A5F95DA733FD89A1E4147D /* limit_spec_test.json in Resources */, + 5250AE69A391E7A3310E013B /* listen_source_spec_test.json in Resources */, D73BBA4AB42940AB187169E3 /* listen_spec_test.json in Resources */, C15F5F1E7427738F20C2D789 /* offline_spec_test.json in Resources */, 4781186C01D33E67E07F0D0D /* orderby_spec_test.json in Resources */, @@ -3532,6 +3548,7 @@ 6156C6A837D78D49ED8B8812 /* index_spec_test.json in Resources */, 85BC2AB572A400114BF59255 /* limbo_spec_test.json in Resources */, 9F41D724D9947A89201495AD /* limit_spec_test.json in Resources */, + B104B69726EF6A5B41DAFB17 /* listen_source_spec_test.json in Resources */, 3CFFA6F016231446367E3A69 /* listen_spec_test.json in Resources */, A0C6C658DFEE58314586907B /* offline_spec_test.json in Resources */, D98430EA4FAA357D855FA50F /* orderby_spec_test.json in Resources */, @@ -3599,6 +3616,7 @@ 3783E25DFF9E5C0896D34FEF /* index_spec_test.json in Resources */, 54DA12A81F315EE100DD57A1 /* limbo_spec_test.json in Resources */, 54DA12A91F315EE100DD57A1 /* limit_spec_test.json in Resources */, + 9CFF379C7404F7CE6B26AF29 /* listen_source_spec_test.json in Resources */, 54DA12AA1F315EE100DD57A1 /* listen_spec_test.json in Resources */, 54DA12AB1F315EE100DD57A1 /* offline_spec_test.json in Resources */, 54DA12AC1F315EE100DD57A1 /* orderby_spec_test.json in Resources */, @@ -3665,6 +3683,7 @@ E04607A1E2964684184E8AEA /* index_spec_test.json in Resources */, 2AD8EE91928AE68DF268BEDA /* limbo_spec_test.json in Resources */, BC5AC8890974E0821431267E /* limit_spec_test.json in Resources */, + A1A466F55A1ED0AC5EE449BF /* listen_source_spec_test.json in Resources */, 5B89B1BA0AD400D9BF581420 /* listen_spec_test.json in Resources */, F660788F69B4336AC6CD2720 /* offline_spec_test.json in Resources */, 4F5714D37B6D119CB07ED8AE /* orderby_spec_test.json in Resources */, @@ -4572,6 +4591,7 @@ 3B1E27D951407FD237E64D07 /* FirestoreEncoderTests.swift in Sources */, 62E54B862A9E910B003347C8 /* IndexingTests.swift in Sources */, 621D620C28F9CE7400D2FA26 /* QueryIntegrationTests.swift in Sources */, + 1CFBD4563960D8A20C4679A3 /* SnapshotListenerSourceTests.swift in Sources */, 4D42E5C756229C08560DD731 /* XCTestCase+Await.mm in Sources */, 09BE8C01EC33D1FD82262D5D /* aggregate_query_test.cc in Sources */, 0EC3921AE220410F7394729B /* aggregation_result.pb.cc in Sources */, @@ -4812,6 +4832,7 @@ 5E89B1A5A5430713C79C4854 /* FirestoreEncoderTests.swift in Sources */, 62E54B852A9E910B003347C8 /* IndexingTests.swift in Sources */, 621D620B28F9CE7400D2FA26 /* QueryIntegrationTests.swift in Sources */, + A0BC30D482B0ABD1A3A24CDC /* SnapshotListenerSourceTests.swift in Sources */, 736C4E82689F1CA1859C4A3F /* XCTestCase+Await.mm in Sources */, 412BE974741729A6683C386F /* aggregate_query_test.cc in Sources */, DF983A9C1FBF758AF3AF110D /* aggregation_result.pb.cc in Sources */, @@ -5299,6 +5320,7 @@ 6F45846C159D3C063DBD3CBE /* FirestoreEncoderTests.swift in Sources */, 62E54B842A9E910B003347C8 /* IndexingTests.swift in Sources */, 621D620A28F9CE7400D2FA26 /* QueryIntegrationTests.swift in Sources */, + B00F8D1819EE20C45B660940 /* SnapshotListenerSourceTests.swift in Sources */, 5492E0442021457E00B64F25 /* XCTestCase+Await.mm in Sources */, B04E4FE20930384DF3A402F9 /* aggregate_query_test.cc in Sources */, 1A3D8028303B45FCBB21CAD3 /* aggregation_result.pb.cc in Sources */, diff --git a/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm b/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm index 622b3b527d7..f2b8ca2e4be 100644 --- a/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm +++ b/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm @@ -41,6 +41,7 @@ #include "Firestore/core/src/bundle/bundle_reader.h" #include "Firestore/core/src/bundle/bundle_serializer.h" #include "Firestore/core/src/core/field_filter.h" +#import "Firestore/core/src/core/listen_options.h" #include "Firestore/core/src/credentials/user.h" #include "Firestore/core/src/local/persistence.h" #include "Firestore/core/src/local/target_data.h" @@ -79,10 +80,12 @@ using firebase::firestore::Error; using firebase::firestore::google_firestore_v1_ArrayValue; using firebase::firestore::google_firestore_v1_Value; +using firebase::firestore::api::ListenSource; using firebase::firestore::api::LoadBundleTask; using firebase::firestore::bundle::BundleReader; using firebase::firestore::bundle::BundleSerializer; using firebase::firestore::core::DocumentViewChange; +using firebase::firestore::core::ListenOptions; using firebase::firestore::core::Query; using firebase::firestore::credentials::User; using firebase::firestore::local::Persistence; @@ -385,11 +388,25 @@ - (DocumentViewChange)parseChange:(NSDictionary *)jsonDoc ofType:(DocumentViewCh return DocumentViewChange{std::move(doc), type}; } +- (ListenOptions)parseOptions:(NSDictionary *)optionsSpec { + ListenOptions options = ListenOptions::FromIncludeMetadataChanges(true); + + if (optionsSpec != nil) { + ListenSource source = + [optionsSpec[@"source"] isEqual:@"cache"] ? ListenSource::Cache : ListenSource::Default; + // include_metadata_changes are default to true in spec tests + options = ListenOptions::FromOptions(true, source); + } + + return options; +} + #pragma mark - Methods for doing the steps of the spec test. - (void)doListen:(NSDictionary *)listenSpec { Query query = [self parseQuery:listenSpec[@"query"]]; - TargetId actualID = [self.driver addUserListenerWithQuery:std::move(query)]; + ListenOptions options = [self parseOptions:listenSpec[@"options"]]; + TargetId actualID = [self.driver addUserListenerWithQuery:std::move(query) options:options]; TargetId expectedID = [listenSpec[@"targetId"] intValue]; XCTAssertEqual(actualID, expectedID, @"targetID assigned to listen"); diff --git a/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.h b/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.h index c99836f8406..978ae28a4e5 100644 --- a/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.h +++ b/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.h @@ -146,9 +146,10 @@ typedef std:: * Resulting events are captured and made available via the capturedEventsSinceLastCall method. * * @param query A valid query to execute against the backend. + * @param options A listen option to configure snapshot listener. * @return The target ID assigned by the system to track the query. */ -- (model::TargetId)addUserListenerWithQuery:(core::Query)query; +- (model::TargetId)addUserListenerWithQuery:(core::Query)query options:(core::ListenOptions)options; /** * Removes a listener from the FSTSyncEngine as if the user had removed a listener corresponding diff --git a/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.mm b/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.mm index 41fe884c70f..7fe5dc5d91e 100644 --- a/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.mm +++ b/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.mm @@ -476,10 +476,8 @@ - (FSTOutstandingWrite *)receiveWriteError:(int)errorCode return result; } -- (TargetId)addUserListenerWithQuery:(Query)query { - // TODO(dimond): Allow customizing listen options in spec tests +- (TargetId)addUserListenerWithQuery:(Query)query options:(ListenOptions)options { // TODO(dimond): Change spec tests to verify isFromCache on snapshots - ListenOptions options = ListenOptions::FromIncludeMetadataChanges(true); auto listener = QueryListener::Create( query, options, [self, query](const StatusOr &maybe_snapshot) { FSTQueryEvent *event = [[FSTQueryEvent alloc] init]; diff --git a/Firestore/Example/Tests/SpecTests/json/listen_source_spec_test.json b/Firestore/Example/Tests/SpecTests/json/listen_source_spec_test.json new file mode 100644 index 00000000000..1912afc320f --- /dev/null +++ b/Firestore/Example/Tests/SpecTests/json/listen_source_spec_test.json @@ -0,0 +1,5895 @@ +{ + "Clients can have multiple listeners with different sources": { + "describeName": "Listens source options:", + "itName": "Clients can have multiple listeners with different sources", + "tags": [ + "multi-client" + ], + "config": { + "numClients": 2, + "useEagerGCForMemory": false + }, + "steps": [ + { + "clientIndex": 0, + "drainQueue": true + }, + { + "applyClientState": { + "visibility": "visible" + }, + "clientIndex": 0 + }, + { + "clientIndex": 0, + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "clientIndex": 0, + "watchAck": [ + 2 + ] + }, + { + "clientIndex": 0, + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "clientIndex": 0, + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "clientIndex": 0, + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "clientIndex": 0, + "userListen": { + "options": { + "source": "cache" + }, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "clientIndex": 1, + "drainQueue": true + }, + { + "clientIndex": 1, + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "clientIndex": 1, + "userListen": { + "options": { + "source": "cache" + }, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "clientIndex": 0, + "drainQueue": true + }, + { + "clientIndex": 0, + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 2000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "clientIndex": 0, + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 2000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + }, + { + "added": [ + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 2000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "clientIndex": 1, + "drainQueue": true, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 2000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + }, + { + "added": [ + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 2000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + } + ] + }, + "Contents of query are cleared when listen is removed.": { + "comment": "Explicitly tests eager GC behavior", + "describeName": "Listens source options:", + "itName": "Contents of query are cleared when listen is removed.", + "tags": [ + "eager-gc" + ], + "config": { + "numClients": 1, + "useEagerGCForMemory": true + }, + "steps": [ + { + "userSet": [ + "collection/a", + { + "key": "a" + } + ] + }, + { + "userListen": { + "options": { + "source": "cache" + }, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": true + }, + "value": { + "key": "a" + }, + "version": 0 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": true, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "userUnlisten": [ + 2, + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "writeAck": { + "version": 1000 + }, + "expectedState": { + "userCallbacks": { + "acknowledgedDocs": [ + "collection/a" + ], + "rejectedDocs": [ + ] + } + } + }, + { + "userListen": { + "options": { + "source": "cache" + }, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 4 + }, + "expectedSnapshotEvents": [ + { + "added": [ + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + } + } + } + ] + }, + "Documents are cleared when listen is removed.": { + "describeName": "Listens source options:", + "itName": "Documents are cleared when listen is removed.", + "tags": [ + "eager-gc" + ], + "config": { + "numClients": 1, + "useEagerGCForMemory": true + }, + "steps": [ + { + "userSet": [ + "collection/a", + { + "matches": true + } + ] + }, + { + "userSet": [ + "collection/b", + { + "matches": true + } + ] + }, + { + "userListen": { + "options": { + "source": "cache" + }, + "query": { + "filters": [ + [ + "matches", + "==", + true + ] + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": true + }, + "value": { + "matches": true + }, + "version": 0 + }, + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": true + }, + "value": { + "matches": true + }, + "version": 0 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": true, + "query": { + "filters": [ + [ + "matches", + "==", + true + ] + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "writeAck": { + "version": 1000 + }, + "expectedState": { + "userCallbacks": { + "acknowledgedDocs": [ + "collection/a" + ], + "rejectedDocs": [ + ] + } + } + }, + { + "writeAck": { + "version": 2000 + }, + "expectedState": { + "userCallbacks": { + "acknowledgedDocs": [ + "collection/b" + ], + "rejectedDocs": [ + ] + } + } + }, + { + "userSet": [ + "collection/b", + { + "matches": false + } + ], + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": true, + "query": { + "filters": [ + [ + "matches", + "==", + true + ] + ], + "orderBys": [ + ], + "path": "collection" + }, + "removed": [ + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": true + }, + "value": { + "matches": true + }, + "version": 0 + } + ] + } + ] + }, + { + "userUnlisten": [ + 2, + { + "filters": [ + [ + "matches", + "==", + true + ] + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "writeAck": { + "version": 3000 + }, + "expectedState": { + "userCallbacks": { + "acknowledgedDocs": [ + "collection/b" + ], + "rejectedDocs": [ + ] + } + } + }, + { + "userListen": { + "options": { + "source": "cache" + }, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 4 + }, + "expectedSnapshotEvents": [ + { + "added": [ + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "userUnlisten": [ + 4, + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "expectedState": { + "activeTargets": { + } + } + } + ] + }, + "Doesn't include unknown documents in cached result": { + "describeName": "Listens source options:", + "itName": "Doesn't include unknown documents in cached result", + "tags": [ + ], + "config": { + "numClients": 1, + "useEagerGCForMemory": true + }, + "steps": [ + { + "userSet": [ + "collection/exists", + { + "key": "a" + } + ] + }, + { + "userPatch": [ + "collection/unknown", + { + "key": "b" + } + ] + }, + { + "userListen": { + "options": { + "source": "cache" + }, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/exists", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": true + }, + "value": { + "key": "a" + }, + "version": 0 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": true, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + } + } + } + ] + }, + "Doesn't raise 'hasPendingWrites' for deletes": { + "describeName": "Listens source options:", + "itName": "Doesn't raise 'hasPendingWrites' for deletes", + "tags": [ + ], + "config": { + "numClients": 1, + "useEagerGCForMemory": false + }, + "steps": [ + { + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "userUnlisten": [ + 2, + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "watchRemove": { + "targetIds": [ + 2 + ] + } + }, + { + "userListen": { + "options": { + "source": "cache" + }, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "userDelete": "collection/a", + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "removed": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ] + } + ] + }, + { + "writeAck": { + "version": 2000 + }, + "expectedState": { + "userCallbacks": { + "acknowledgedDocs": [ + "collection/a" + ], + "rejectedDocs": [ + ] + } + } + } + ] + }, + "Empty initial snapshot is raised from cache": { + "describeName": "Listens source options:", + "itName": "Empty initial snapshot is raised from cache", + "tags": [ + ], + "config": { + "numClients": 1, + "useEagerGCForMemory": false + }, + "steps": [ + { + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "userUnlisten": [ + 2, + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "watchRemove": { + "targetIds": [ + 2 + ] + } + }, + { + "userListen": { + "options": { + "source": "cache" + }, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + } + } + } + ] + }, + "Empty-due-to-delete initial snapshot is raised from cache": { + "describeName": "Listens source options:", + "itName": "Empty-due-to-delete initial snapshot is raised from cache", + "tags": [ + ], + "config": { + "numClients": 1, + "useEagerGCForMemory": false + }, + "steps": [ + { + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "userUnlisten": [ + 2, + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "watchRemove": { + "targetIds": [ + 2 + ] + } + }, + { + "userDelete": "collection/a" + }, + { + "userListen": { + "options": { + "source": "cache" + }, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + } + } + } + ] + }, + "Listeners with different source shares watch changes between primary and secondary clients": { + "describeName": "Listens source options:", + "itName": "Listeners with different source shares watch changes between primary and secondary clients", + "tags": [ + "multi-client" + ], + "config": { + "numClients": 3, + "useEagerGCForMemory": false + }, + "steps": [ + { + "clientIndex": 0, + "drainQueue": true + }, + { + "applyClientState": { + "visibility": "visible" + }, + "clientIndex": 0 + }, + { + "clientIndex": 0, + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "clientIndex": 0, + "watchAck": [ + 2 + ] + }, + { + "clientIndex": 0, + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "clientIndex": 0, + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "clientIndex": 0, + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "clientIndex": 1, + "drainQueue": true + }, + { + "clientIndex": 1, + "userListen": { + "options": { + "source": "cache" + }, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "clientIndex": 2, + "drainQueue": true + }, + { + "clientIndex": 2, + "userListen": { + "options": { + "source": "cache" + }, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "clientIndex": 0, + "drainQueue": true + }, + { + "clientIndex": 0, + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 2000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "clientIndex": 0, + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 2000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "clientIndex": 1, + "drainQueue": true, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 2000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "clientIndex": 2, + "drainQueue": true, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 2000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "clientIndex": 0, + "drainQueue": true + }, + { + "clientIndex": 0, + "userUnlisten": [ + 2, + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "expectedState": { + "activeTargets": { + } + } + } + ] + }, + "Local mutations notifies listeners sourced from cache in all tabs": { + "describeName": "Listens source options:", + "itName": "Local mutations notifies listeners sourced from cache in all tabs", + "tags": [ + "multi-client" + ], + "config": { + "numClients": 2, + "useEagerGCForMemory": false + }, + "steps": [ + { + "clientIndex": 0, + "drainQueue": true + }, + { + "applyClientState": { + "visibility": "visible" + }, + "clientIndex": 0 + }, + { + "clientIndex": 0, + "userListen": { + "options": { + "source": "cache" + }, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "added": [ + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "clientIndex": 1, + "drainQueue": true + }, + { + "clientIndex": 1, + "userListen": { + "options": { + "source": "cache" + }, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "added": [ + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "clientIndex": 0, + "drainQueue": true + }, + { + "clientIndex": 0, + "userSet": [ + "collection/a", + { + "key": "a" + } + ], + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": true + }, + "value": { + "key": "a" + }, + "version": 0 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": true, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "clientIndex": 1, + "drainQueue": true, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": true + }, + "value": { + "key": "a" + }, + "version": 0 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": true, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + } + ] + }, + "Mirror queries being listened from different sources while listening to server in primary tab": { + "describeName": "Listens source options:", + "itName": "Mirror queries being listened from different sources while listening to server in primary tab", + "tags": [ + "multi-client" + ], + "config": { + "numClients": 2, + "useEagerGCForMemory": false + }, + "steps": [ + { + "clientIndex": 0, + "drainQueue": true + }, + { + "applyClientState": { + "visibility": "visible" + }, + "clientIndex": 0 + }, + { + "clientIndex": 0, + "userListen": { + "query": { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToFirst", + "orderBys": [ + [ + "sort", + "asc" + ] + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToFirst", + "orderBys": [ + [ + "sort", + "asc" + ] + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "clientIndex": 0, + "watchAck": [ + 2 + ] + }, + { + "clientIndex": 0, + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 0 + }, + "version": 1000 + }, + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 1 + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "clientIndex": 0, + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "clientIndex": 0, + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 0 + }, + "version": 1000 + }, + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 1 + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToFirst", + "orderBys": [ + [ + "sort", + "asc" + ] + ], + "path": "collection" + } + } + ] + }, + { + "clientIndex": 1, + "drainQueue": true + }, + { + "clientIndex": 1, + "userListen": { + "options": { + "source": "cache" + }, + "query": { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToLast", + "orderBys": [ + [ + "sort", + "desc" + ] + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 1 + }, + "version": 1000 + }, + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 0 + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToLast", + "orderBys": [ + [ + "sort", + "desc" + ] + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "clientIndex": 0, + "drainQueue": true + }, + { + "clientIndex": 0, + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": -1 + }, + "version": 2000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "clientIndex": 0, + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": -1 + }, + "version": 2000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToFirst", + "orderBys": [ + [ + "sort", + "asc" + ] + ], + "path": "collection" + }, + "removed": [ + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 1 + }, + "version": 1000 + } + ] + } + ] + }, + { + "clientIndex": 1, + "drainQueue": true, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": -1 + }, + "version": 2000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToLast", + "orderBys": [ + [ + "sort", + "desc" + ] + ], + "path": "collection" + }, + "removed": [ + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 1 + }, + "version": 1000 + } + ] + } + ] + } + ] + }, + "Mirror queries being listened in different clients sourced from cache ": { + "describeName": "Listens source options:", + "itName": "Mirror queries being listened in different clients sourced from cache ", + "tags": [ + "multi-client" + ], + "config": { + "numClients": 2, + "useEagerGCForMemory": false + }, + "steps": [ + { + "clientIndex": 0, + "drainQueue": true + }, + { + "applyClientState": { + "visibility": "visible" + }, + "clientIndex": 0 + }, + { + "clientIndex": 0, + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "clientIndex": 0, + "watchAck": [ + 2 + ] + }, + { + "clientIndex": 0, + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 0 + }, + "version": 1000 + }, + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 1 + }, + "version": 1000 + }, + { + "createTime": 0, + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 1 + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "clientIndex": 0, + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "clientIndex": 0, + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 0 + }, + "version": 1000 + }, + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 1 + }, + "version": 1000 + }, + { + "createTime": 0, + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 1 + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "clientIndex": 0, + "userUnlisten": [ + 2, + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "clientIndex": 0, + "watchRemove": { + "targetIds": [ + 2 + ] + } + }, + { + "clientIndex": 0, + "userListen": { + "options": { + "source": "cache" + }, + "query": { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToFirst", + "orderBys": [ + [ + "sort", + "asc" + ] + ], + "path": "collection" + }, + "targetId": 4 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 0 + }, + "version": 1000 + }, + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 1 + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToFirst", + "orderBys": [ + [ + "sort", + "asc" + ] + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "clientIndex": 1, + "drainQueue": true + }, + { + "clientIndex": 1, + "userListen": { + "options": { + "source": "cache" + }, + "query": { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToLast", + "orderBys": [ + [ + "sort", + "desc" + ] + ], + "path": "collection" + }, + "targetId": 4 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 1 + }, + "version": 1000 + }, + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 0 + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToLast", + "orderBys": [ + [ + "sort", + "desc" + ] + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "clientIndex": 0, + "drainQueue": true + }, + { + "clientIndex": 0, + "userUnlisten": [ + 4, + { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToFirst", + "orderBys": [ + [ + "sort", + "asc" + ] + ], + "path": "collection" + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "clientIndex": 0, + "userSet": [ + "collection/c", + { + "sort": -1 + } + ] + }, + { + "clientIndex": 1, + "drainQueue": true, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": true + }, + "value": { + "sort": -1 + }, + "version": 0 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": true, + "query": { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToLast", + "orderBys": [ + [ + "sort", + "desc" + ] + ], + "path": "collection" + }, + "removed": [ + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 1 + }, + "version": 1000 + } + ] + } + ] + } + ] + }, + "Mirror queries being listened in the same secondary client sourced from cache": { + "describeName": "Listens source options:", + "itName": "Mirror queries being listened in the same secondary client sourced from cache", + "tags": [ + "multi-client" + ], + "config": { + "numClients": 2, + "useEagerGCForMemory": false + }, + "steps": [ + { + "clientIndex": 0, + "drainQueue": true + }, + { + "applyClientState": { + "visibility": "visible" + }, + "clientIndex": 0 + }, + { + "clientIndex": 0, + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "clientIndex": 0, + "watchAck": [ + 2 + ] + }, + { + "clientIndex": 0, + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 0 + }, + "version": 1000 + }, + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 1 + }, + "version": 1000 + }, + { + "createTime": 0, + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 1 + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "clientIndex": 0, + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "clientIndex": 0, + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 0 + }, + "version": 1000 + }, + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 1 + }, + "version": 1000 + }, + { + "createTime": 0, + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 1 + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "clientIndex": 0, + "userUnlisten": [ + 2, + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "clientIndex": 0, + "watchRemove": { + "targetIds": [ + 2 + ] + } + }, + { + "clientIndex": 1, + "drainQueue": true + }, + { + "clientIndex": 1, + "userListen": { + "options": { + "source": "cache" + }, + "query": { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToFirst", + "orderBys": [ + [ + "sort", + "asc" + ] + ], + "path": "collection" + }, + "targetId": 4 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 0 + }, + "version": 1000 + }, + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 1 + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToFirst", + "orderBys": [ + [ + "sort", + "asc" + ] + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "clientIndex": 1, + "userListen": { + "options": { + "source": "cache" + }, + "query": { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToLast", + "orderBys": [ + [ + "sort", + "desc" + ] + ], + "path": "collection" + }, + "targetId": 4 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 1 + }, + "version": 1000 + }, + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 0 + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToLast", + "orderBys": [ + [ + "sort", + "desc" + ] + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "clientIndex": 1, + "userUnlisten": [ + 4, + { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToFirst", + "orderBys": [ + [ + "sort", + "asc" + ] + ], + "path": "collection" + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "clientIndex": 1, + "userSet": [ + "collection/c", + { + "sort": -1 + } + ], + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": true + }, + "value": { + "sort": -1 + }, + "version": 0 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": true, + "query": { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToLast", + "orderBys": [ + [ + "sort", + "desc" + ] + ], + "path": "collection" + }, + "removed": [ + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 1 + }, + "version": 1000 + } + ] + } + ] + } + ] + }, + "Mirror queries from different sources while listening to server in secondary tab": { + "describeName": "Listens source options:", + "itName": "Mirror queries from different sources while listening to server in secondary tab", + "tags": [ + "multi-client" + ], + "config": { + "numClients": 2, + "useEagerGCForMemory": false + }, + "steps": [ + { + "clientIndex": 0, + "drainQueue": true + }, + { + "applyClientState": { + "visibility": "visible" + }, + "clientIndex": 0 + }, + { + "clientIndex": 1, + "drainQueue": true + }, + { + "clientIndex": 1, + "userListen": { + "query": { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToFirst", + "orderBys": [ + [ + "sort", + "asc" + ] + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToFirst", + "orderBys": [ + [ + "sort", + "asc" + ] + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "clientIndex": 0, + "drainQueue": true, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToFirst", + "orderBys": [ + [ + "sort", + "asc" + ] + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "clientIndex": 0, + "watchAck": [ + 2 + ] + }, + { + "clientIndex": 0, + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 0 + }, + "version": 1000 + }, + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 1 + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "clientIndex": 0, + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "clientIndex": 0, + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + } + }, + { + "clientIndex": 1, + "drainQueue": true, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 0 + }, + "version": 1000 + }, + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 1 + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToFirst", + "orderBys": [ + [ + "sort", + "asc" + ] + ], + "path": "collection" + } + } + ] + }, + { + "clientIndex": 0, + "drainQueue": true + }, + { + "clientIndex": 0, + "userListen": { + "options": { + "source": "cache" + }, + "query": { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToLast", + "orderBys": [ + [ + "sort", + "desc" + ] + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 1 + }, + "version": 1000 + }, + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 0 + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToLast", + "orderBys": [ + [ + "sort", + "desc" + ] + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToFirst", + "orderBys": [ + [ + "sort", + "asc" + ] + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "clientIndex": 0, + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": -1 + }, + "version": 2000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "clientIndex": 0, + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": -1 + }, + "version": 2000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToLast", + "orderBys": [ + [ + "sort", + "desc" + ] + ], + "path": "collection" + }, + "removed": [ + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 1 + }, + "version": 1000 + } + ] + } + ] + }, + { + "clientIndex": 1, + "drainQueue": true, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/c", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": -1 + }, + "version": 2000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "limit": 2, + "limitType": "LimitToFirst", + "orderBys": [ + [ + "sort", + "asc" + ] + ], + "path": "collection" + }, + "removed": [ + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "sort": 1 + }, + "version": 1000 + } + ] + } + ] + } + ] + }, + "Newer deleted docs from bundles should delete cached docs": { + "describeName": "Listens source options:", + "itName": "Newer deleted docs from bundles should delete cached docs", + "tags": [ + ], + "config": { + "numClients": 1, + "useEagerGCForMemory": false + }, + "steps": [ + { + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "value": "a" + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "value": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "userUnlisten": [ + 2, + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "watchRemove": { + "targetIds": [ + 2 + ] + } + }, + { + "userListen": { + "options": { + "source": "cache" + }, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "value": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "loadBundle": "127{\"metadata\":{\"id\":\"test-bundle\",\"createTime\":\"1970-01-01T00:00:00.003000000Z\",\"version\":1,\"totalDocuments\":1,\"totalBytes\":158}}155{\"documentMetadata\":{\"name\":\"projects/test-project/databases/(default)/documents/collection/a\",\"readTime\":\"1970-01-01T00:00:00.003000000Z\",\"exists\":false}}", + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "removed": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "value": "a" + }, + "version": 1000 + } + ] + } + ] + } + ] + }, + "Newer docs from bundles should keep not raise snapshot if there are unacknowledged writes": { + "describeName": "Listens source options:", + "itName": "Newer docs from bundles should keep not raise snapshot if there are unacknowledged writes", + "tags": [ + ], + "config": { + "numClients": 1, + "useEagerGCForMemory": false + }, + "steps": [ + { + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "value": "a" + }, + "version": 250 + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-250" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 250 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "value": "a" + }, + "version": 250 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "userUnlisten": [ + 2, + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "watchRemove": { + "targetIds": [ + 2 + ] + } + }, + { + "userListen": { + "options": { + "source": "cache" + }, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "value": "a" + }, + "version": 250 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "userPatch": [ + "collection/a", + { + "value": "patched" + } + ], + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": true, + "modified": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": true + }, + "value": { + "value": "patched" + }, + "version": 0 + } + ], + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "loadBundle": "127{\"metadata\":{\"id\":\"test-bundle\",\"createTime\":\"1970-01-01T00:00:00.001001000Z\",\"version\":1,\"totalDocuments\":1,\"totalBytes\":388}}154{\"documentMetadata\":{\"name\":\"projects/test-project/databases/(default)/documents/collection/a\",\"readTime\":\"1970-01-01T00:00:00.001001000Z\",\"exists\":true}}228{\"document\":{\"name\":\"projects/test-project/databases/(default)/documents/collection/a\",\"createTime\":\"1970-01-01T00:00:00.000250000Z\",\"updateTime\":\"1970-01-01T00:00:00.001001000Z\",\"fields\":{\"value\":{\"stringValue\":\"fromBundle\"}}}}" + } + ] + }, + "Newer docs from bundles should overwrite cache": { + "describeName": "Listens source options:", + "itName": "Newer docs from bundles should overwrite cache", + "tags": [ + ], + "config": { + "numClients": 1, + "useEagerGCForMemory": false + }, + "steps": [ + { + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "value": "a" + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "value": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "userUnlisten": [ + 2, + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "watchRemove": { + "targetIds": [ + 2 + ] + } + }, + { + "userListen": { + "options": { + "source": "cache" + }, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "value": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "loadBundle": "127{\"metadata\":{\"id\":\"test-bundle\",\"createTime\":\"1970-01-01T00:00:00.003000000Z\",\"version\":1,\"totalDocuments\":1,\"totalBytes\":379}}154{\"documentMetadata\":{\"name\":\"projects/test-project/databases/(default)/documents/collection/a\",\"readTime\":\"1970-01-01T00:00:00.003000000Z\",\"exists\":true}}219{\"document\":{\"name\":\"projects/test-project/databases/(default)/documents/collection/a\",\"createTime\":\"1970-01-01T00:00:00.001999000Z\",\"updateTime\":\"1970-01-01T00:00:00.002999000Z\",\"fields\":{\"value\":{\"stringValue\":\"b\"}}}}", + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "modified": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "value": "b" + }, + "version": 2999 + } + ], + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + } + ] + }, + "Newer docs from bundles should raise snapshot only when Watch catches up with acknowledged writes": { + "describeName": "Listens source options:", + "itName": "Newer docs from bundles should raise snapshot only when Watch catches up with acknowledged writes", + "tags": [ + ], + "config": { + "numClients": 1, + "useEagerGCForMemory": false + }, + "steps": [ + { + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "value": "a" + }, + "version": 250 + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-250" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 250 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "value": "a" + }, + "version": 250 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "userUnlisten": [ + 2, + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "watchRemove": { + "targetIds": [ + 2 + ] + } + }, + { + "userListen": { + "options": { + "source": "cache" + }, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "value": "a" + }, + "version": 250 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "userPatch": [ + "collection/a", + { + "value": "patched" + } + ], + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": true, + "modified": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": true + }, + "value": { + "value": "patched" + }, + "version": 0 + } + ], + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "writeAck": { + "version": 1000 + }, + "expectedState": { + "userCallbacks": { + "acknowledgedDocs": [ + "collection/a" + ], + "rejectedDocs": [ + ] + } + } + }, + { + "loadBundle": "127{\"metadata\":{\"id\":\"test-bundle\",\"createTime\":\"1970-01-01T00:00:00.000500000Z\",\"version\":1,\"totalDocuments\":1,\"totalBytes\":379}}154{\"documentMetadata\":{\"name\":\"projects/test-project/databases/(default)/documents/collection/a\",\"readTime\":\"1970-01-01T00:00:00.000500000Z\",\"exists\":true}}219{\"document\":{\"name\":\"projects/test-project/databases/(default)/documents/collection/a\",\"createTime\":\"1970-01-01T00:00:00.000250000Z\",\"updateTime\":\"1970-01-01T00:00:00.000500000Z\",\"fields\":{\"value\":{\"stringValue\":\"b\"}}}}" + }, + { + "loadBundle": "127{\"metadata\":{\"id\":\"test-bundle\",\"createTime\":\"1970-01-01T00:00:00.001001000Z\",\"version\":1,\"totalDocuments\":1,\"totalBytes\":388}}154{\"documentMetadata\":{\"name\":\"projects/test-project/databases/(default)/documents/collection/a\",\"readTime\":\"1970-01-01T00:00:00.001001000Z\",\"exists\":true}}228{\"document\":{\"name\":\"projects/test-project/databases/(default)/documents/collection/a\",\"createTime\":\"1970-01-01T00:00:00.000250000Z\",\"updateTime\":\"1970-01-01T00:00:00.001001000Z\",\"fields\":{\"value\":{\"stringValue\":\"fromBundle\"}}}}", + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "modified": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "value": "fromBundle" + }, + "version": 1001 + } + ], + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + } + ] + }, + "Older deleted docs from bundles should do nothing": { + "describeName": "Listens source options:", + "itName": "Older deleted docs from bundles should do nothing", + "tags": [ + ], + "config": { + "numClients": 1, + "useEagerGCForMemory": false + }, + "steps": [ + { + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "value": "a" + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "value": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "userUnlisten": [ + 2, + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "watchRemove": { + "targetIds": [ + 2 + ] + } + }, + { + "userListen": { + "options": { + "source": "cache" + }, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "value": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "loadBundle": "127{\"metadata\":{\"id\":\"test-bundle\",\"createTime\":\"1970-01-01T00:00:00.000999000Z\",\"version\":1,\"totalDocuments\":1,\"totalBytes\":158}}155{\"documentMetadata\":{\"name\":\"projects/test-project/databases/(default)/documents/collection/a\",\"readTime\":\"1970-01-01T00:00:00.000999000Z\",\"exists\":false}}" + } + ] + }, + "Primary client should not invoke watch request while all clients are listening to cache": { + "describeName": "Listens source options:", + "itName": "Primary client should not invoke watch request while all clients are listening to cache", + "tags": [ + "multi-client" + ], + "config": { + "numClients": 2, + "useEagerGCForMemory": false + }, + "steps": [ + { + "clientIndex": 0, + "drainQueue": true + }, + { + "applyClientState": { + "visibility": "visible" + }, + "clientIndex": 0 + }, + { + "clientIndex": 1, + "drainQueue": true + }, + { + "clientIndex": 1, + "userListen": { + "options": { + "source": "cache" + }, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "added": [ + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "clientIndex": 0, + "drainQueue": true, + "expectedState": { + "activeTargets": { + } + } + }, + { + "clientIndex": 1, + "drainQueue": true + }, + { + "clientIndex": 1, + "userUnlisten": [ + 2, + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "clientIndex": 0, + "drainQueue": true, + "expectedState": { + "activeTargets": { + } + } + } + ] + }, + "Query is executed by primary client even if primary client only has listeners sourced from cache": { + "describeName": "Listens source options:", + "itName": "Query is executed by primary client even if primary client only has listeners sourced from cache", + "tags": [ + "multi-client" + ], + "config": { + "numClients": 2, + "useEagerGCForMemory": false + }, + "steps": [ + { + "clientIndex": 0, + "drainQueue": true + }, + { + "applyClientState": { + "visibility": "visible" + }, + "clientIndex": 0 + }, + { + "clientIndex": 0, + "userListen": { + "options": { + "source": "cache" + }, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "added": [ + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "clientIndex": 1, + "drainQueue": true + }, + { + "clientIndex": 1, + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "clientIndex": 0, + "drainQueue": true, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "clientIndex": 0, + "watchAck": [ + 2 + ] + }, + { + "clientIndex": 0, + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "clientIndex": 0, + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "clientIndex": 1, + "drainQueue": true, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "clientIndex": 0, + "drainQueue": true + }, + { + "clientIndex": 0, + "watchCurrent": [ + [ + 2 + ], + "resume-token-2000" + ] + }, + { + "clientIndex": 0, + "watchSnapshot": { + "targetIds": [ + ], + "version": 2000 + }, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "clientIndex": 1, + "drainQueue": true, + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + } + ] + }, + "Query only raises events in participating clients": { + "describeName": "Listens source options:", + "itName": "Query only raises events in participating clients", + "tags": [ + "multi-client" + ], + "config": { + "numClients": 4, + "useEagerGCForMemory": false + }, + "steps": [ + { + "clientIndex": 0, + "drainQueue": true + }, + { + "applyClientState": { + "visibility": "visible" + }, + "clientIndex": 0 + }, + { + "clientIndex": 1, + "drainQueue": true + }, + { + "clientIndex": 2, + "drainQueue": true + }, + { + "clientIndex": 2, + "userListen": { + "options": { + "source": "cache" + }, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "added": [ + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "clientIndex": 3, + "drainQueue": true + }, + { + "clientIndex": 3, + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "clientIndex": 0, + "drainQueue": true, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "clientIndex": 0, + "watchAck": [ + 2 + ] + }, + { + "clientIndex": 0, + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "clientIndex": 0, + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "clientIndex": 0, + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + } + }, + { + "clientIndex": 1, + "drainQueue": true + }, + { + "clientIndex": 2, + "drainQueue": true, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "clientIndex": 3, + "drainQueue": true, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + } + ] + }, + "Un-listen to listeners from different source": { + "describeName": "Listens source options:", + "itName": "Un-listen to listeners from different source", + "tags": [ + "multi-client" + ], + "config": { + "numClients": 2, + "useEagerGCForMemory": false + }, + "steps": [ + { + "clientIndex": 0, + "drainQueue": true + }, + { + "applyClientState": { + "visibility": "visible" + }, + "clientIndex": 0 + }, + { + "clientIndex": 0, + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "clientIndex": 0, + "watchAck": [ + 2 + ] + }, + { + "clientIndex": 0, + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "clientIndex": 0, + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "clientIndex": 0, + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "clientIndex": 1, + "drainQueue": true + }, + { + "clientIndex": 1, + "userListen": { + "options": { + "source": "cache" + }, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "key": "a" + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "clientIndex": 0, + "drainQueue": true + }, + { + "clientIndex": 0, + "userUnlisten": [ + 2, + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "clientIndex": 0, + "userSet": [ + "collection/b", + { + "key": "b" + } + ] + }, + { + "clientIndex": 1, + "drainQueue": true, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/b", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": true + }, + "value": { + "key": "b" + }, + "version": 0 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": true, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "clientIndex": 1, + "userUnlisten": [ + 2, + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "expectedState": { + "activeTargets": { + } + } + } + ] + }, + "onSnapshotsInSync fires for multiple listeners": { + "describeName": "Listens source options:", + "itName": "onSnapshotsInSync fires for multiple listeners", + "tags": [ + ], + "config": { + "numClients": 1, + "useEagerGCForMemory": false + }, + "steps": [ + { + "userListen": { + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedState": { + "activeTargets": { + "2": { + "queries": [ + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "resumeToken": "" + } + } + } + }, + { + "watchAck": [ + 2 + ] + }, + { + "watchEntity": { + "docs": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + } + ], + "targets": [ + 2 + ] + } + }, + { + "watchCurrent": [ + [ + 2 + ], + "resume-token-1000" + ] + }, + { + "watchSnapshot": { + "targetIds": [ + ], + "version": 1000 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": false, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ] + }, + { + "userUnlisten": [ + 2, + { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "watchRemove": { + "targetIds": [ + 2 + ] + } + }, + { + "userListen": { + "options": { + "source": "cache" + }, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + }, + "targetId": 2 + }, + "expectedSnapshotEvents": [ + { + "added": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": false + }, + "value": { + "v": 1 + }, + "version": 1000 + } + ], + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": false, + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedState": { + "activeTargets": { + } + } + }, + { + "addSnapshotsInSyncListener": true, + "expectedSnapshotsInSyncEvents": 1 + }, + { + "userSet": [ + "collection/a", + { + "v": 2 + } + ], + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": true, + "modified": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": true + }, + "value": { + "v": 2 + }, + "version": 0 + } + ], + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedSnapshotsInSyncEvents": 1 + }, + { + "addSnapshotsInSyncListener": true, + "expectedSnapshotsInSyncEvents": 1 + }, + { + "addSnapshotsInSyncListener": true, + "expectedSnapshotsInSyncEvents": 1 + }, + { + "userSet": [ + "collection/a", + { + "v": 3 + } + ], + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": true, + "modified": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": true + }, + "value": { + "v": 3 + }, + "version": 0 + } + ], + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedSnapshotsInSyncEvents": 3 + }, + { + "removeSnapshotsInSyncListener": true + }, + { + "userSet": [ + "collection/a", + { + "v": 4 + } + ], + "expectedSnapshotEvents": [ + { + "errorCode": 0, + "fromCache": true, + "hasPendingWrites": true, + "modified": [ + { + "createTime": 0, + "key": "collection/a", + "options": { + "hasCommittedMutations": false, + "hasLocalMutations": true + }, + "value": { + "v": 4 + }, + "version": 0 + } + ], + "query": { + "filters": [ + ], + "orderBys": [ + ], + "path": "collection" + } + } + ], + "expectedSnapshotsInSyncEvents": 2 + } + ] + } +} diff --git a/Firestore/Example/Tests/Util/FSTIntegrationTestCase.h b/Firestore/Example/Tests/Util/FSTIntegrationTestCase.h index abd965beee3..a63fd66cd1b 100644 --- a/Firestore/Example/Tests/Util/FSTIntegrationTestCase.h +++ b/Firestore/Example/Tests/Util/FSTIntegrationTestCase.h @@ -35,6 +35,7 @@ @class FIRQuery; @class FIRWriteBatch; @class FSTEventAccumulator; +@class FIRTransaction; NS_ASSUME_NONNULL_BEGIN @@ -113,6 +114,10 @@ extern "C" { - (FIRDocumentReference *)addDocumentRef:(FIRCollectionReference *)ref data:(NSDictionary *)data; +- (void)runTransaction:(FIRFirestore *)db + block:(id _Nullable (^)(FIRTransaction *, NSError **error))block + completion:(nullable void (^)(id _Nullable result, NSError *_Nullable error))completion; + - (void)mergeDocumentRef:(FIRDocumentReference *)ref data:(NSDictionary *)data; - (void)mergeDocumentRef:(FIRDocumentReference *)ref diff --git a/Firestore/Example/Tests/Util/FSTIntegrationTestCase.mm b/Firestore/Example/Tests/Util/FSTIntegrationTestCase.mm index 6633ed03748..f42d10bcb01 100644 --- a/Firestore/Example/Tests/Util/FSTIntegrationTestCase.mm +++ b/Firestore/Example/Tests/Util/FSTIntegrationTestCase.mm @@ -569,6 +569,22 @@ - (FIRDocumentReference *)addDocumentRef:(FIRCollectionReference *)ref return doc; } +- (void)runTransaction:(FIRFirestore *)db + block:(id _Nullable (^)(FIRTransaction *, NSError **error))block + completion: + (nullable void (^)(id _Nullable result, NSError *_Nullable error))completion { + XCTestExpectation *expectation = [self expectationWithDescription:@"runTransaction"]; + [db runTransactionWithOptions:nil + block:block + completion:^(id _Nullable result, NSError *_Nullable error) { + if (completion) { + completion(result, error); + } + [expectation fulfill]; + }]; + [self awaitExpectation:expectation]; +} + - (void)mergeDocumentRef:(FIRDocumentReference *)ref data:(NSDictionary *)data { XCTestExpectation *expectation = [self expectationWithDescription:@"setDataWithMerge"]; [ref setData:data merge:YES completion:[self completionForExpectation:expectation]]; diff --git a/Firestore/Source/API/FIRDocumentReference.mm b/Firestore/Source/API/FIRDocumentReference.mm index b6f326ee8d9..a50a2c7df71 100644 --- a/Firestore/Source/API/FIRDocumentReference.mm +++ b/Firestore/Source/API/FIRDocumentReference.mm @@ -27,6 +27,7 @@ #import "Firestore/Source/API/FIRFirestoreSource+Internal.h" #import "Firestore/Source/API/FIRListenerRegistration+Internal.h" #import "Firestore/Source/API/FSTUserDataReader.h" +#import "Firestore/Source/API/converters.h" #include "Firestore/core/src/api/collection_reference.h" #include "Firestore/core/src/api/document_reference.h" @@ -50,6 +51,7 @@ using firebase::firestore::api::DocumentSnapshotListener; using firebase::firestore::api::Firestore; using firebase::firestore::api::ListenerRegistration; +using firebase::firestore::api::MakeListenSource; using firebase::firestore::api::MakeSource; using firebase::firestore::api::Source; using firebase::firestore::core::EventListener; @@ -212,6 +214,13 @@ - (void)getDocumentWithSource:(FIRFirestoreSource)source return [self addSnapshotListenerInternalWithOptions:options listener:listener]; } +- (id)addSnapshotListenerWithOptions:(FIRSnapshotListenOptions *)options + listener:(FIRDocumentSnapshotBlock)listener { + ListenOptions listenOptions = + ListenOptions::FromOptions(options.includeMetadataChanges, MakeListenSource(options.source)); + return [self addSnapshotListenerInternalWithOptions:listenOptions listener:listener]; +} + - (id)addSnapshotListenerInternalWithOptions:(ListenOptions)internalOptions listener:(FIRDocumentSnapshotBlock) listener { diff --git a/Firestore/Source/API/FIRFirestore.mm b/Firestore/Source/API/FIRFirestore.mm index 29b56fd2a6b..f6dbf9dc059 100644 --- a/Firestore/Source/API/FIRFirestore.mm +++ b/Firestore/Source/API/FIRFirestore.mm @@ -567,12 +567,14 @@ - (void)terminateInternalWithCompletion:(nullable void (^)(NSError *_Nullable er #pragma mark - Force Link Unreferenced Symbols extern void FSTIncludeFSTFirestoreComponent(void); +extern void FSTIncludeFIRSnapshotListenOptions(void); -/// This method forces the linker to include all the Analytics categories without requiring app +/// This method forces the linker to include all Firestore symbols without requiring app /// developers to include the '-ObjC' linker flag in their projects. DO NOT CALL THIS METHOD. + (void)notCalled { NSAssert(NO, @"+notCalled should never be called"); FSTIncludeFSTFirestoreComponent(); + FSTIncludeFIRSnapshotListenOptions(); } @end diff --git a/Firestore/Source/API/FIRQuery.mm b/Firestore/Source/API/FIRQuery.mm index 98518183049..d4185488341 100644 --- a/Firestore/Source/API/FIRQuery.mm +++ b/Firestore/Source/API/FIRQuery.mm @@ -37,6 +37,7 @@ #import "Firestore/Source/API/FIRQuerySnapshot+Internal.h" #import "Firestore/Source/API/FIRSnapshotMetadata+Internal.h" #import "Firestore/Source/API/FSTUserDataReader.h" +#import "Firestore/Source/API/converters.h" #include "Firestore/core/src/api/query_core.h" #include "Firestore/core/src/api/query_listener_registration.h" @@ -69,6 +70,7 @@ using firebase::firestore::google_firestore_v1_Value; using firebase::firestore::google_firestore_v1_Value_fields; using firebase::firestore::api::Firestore; +using firebase::firestore::api::MakeListenSource; using firebase::firestore::api::Query; using firebase::firestore::api::QueryListenerRegistration; using firebase::firestore::api::QuerySnapshot; @@ -191,6 +193,13 @@ - (void)getDocumentsWithSource:(FIRFirestoreSource)publicSource return [self addSnapshotListenerInternalWithOptions:options listener:listener]; } +- (id)addSnapshotListenerWithOptions:(FIRSnapshotListenOptions *)options + listener:(FIRQuerySnapshotBlock)listener { + ListenOptions listenOptions = + ListenOptions::FromOptions(options.includeMetadataChanges, MakeListenSource(options.source)); + return [self addSnapshotListenerInternalWithOptions:listenOptions listener:listener]; +} + - (id)addSnapshotListenerInternalWithOptions:(ListenOptions)internalOptions listener: (FIRQuerySnapshotBlock)listener { diff --git a/Firestore/Source/API/FIRSnapshotListenOptions.mm b/Firestore/Source/API/FIRSnapshotListenOptions.mm new file mode 100644 index 00000000000..c1cb4216d43 --- /dev/null +++ b/Firestore/Source/API/FIRSnapshotListenOptions.mm @@ -0,0 +1,68 @@ +/* + * 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. + */ + +#import "FIRSnapshotListenOptions.h" + +#import + +#include +#include + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRSnapshotListenOptions + +- (instancetype)initPrivate:(FIRListenSource)source + includeMetadataChanges:(BOOL)includeMetadataChanges { + self = [self init]; + if (self) { + _source = source; + _includeMetadataChanges = includeMetadataChanges; + } + return self; +} + +- (instancetype)init { + self = [super init]; + if (self) { + _source = FIRListenSourceDefault; + _includeMetadataChanges = NO; + } + return self; +} + +- (FIRSnapshotListenOptions *)optionsWithIncludeMetadataChanges:(BOOL)includeMetadataChanges { + FIRSnapshotListenOptions *newOptions = + [[FIRSnapshotListenOptions alloc] initPrivate:self.source + includeMetadataChanges:includeMetadataChanges]; + return newOptions; +} + +- (FIRSnapshotListenOptions *)optionsWithSource:(FIRListenSource)source { + FIRSnapshotListenOptions *newOptions = + [[FIRSnapshotListenOptions alloc] initPrivate:source + includeMetadataChanges:self.includeMetadataChanges]; + return newOptions; +} + +/// This function forces the linker to include `FIRSnapshotListenOptions`. +/// See `+[FIRFirestore notCalled]`. +void FSTIncludeFIRSnapshotListenOptions(void) { +} + +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/Firestore/Source/API/FSTFirestoreComponent.mm b/Firestore/Source/API/FSTFirestoreComponent.mm index 286576610b0..39285e138d9 100644 --- a/Firestore/Source/API/FSTFirestoreComponent.mm +++ b/Firestore/Source/API/FSTFirestoreComponent.mm @@ -22,7 +22,7 @@ #include #include -#import "FirebaseAuth/Interop/FIRAuthInterop.h" +#import "FirebaseAuth/Interop/Public/FirebaseAuthInterop/FIRAuthInterop.h" #import "FirebaseCore/Extension/FIRAppInternal.h" #import "FirebaseCore/Extension/FIRComponent.h" #import "FirebaseCore/Extension/FIRComponentContainer.h" diff --git a/Firestore/Source/API/converters.h b/Firestore/Source/API/converters.h index f5d0e4545b8..e777ba81a2c 100644 --- a/Firestore/Source/API/converters.h +++ b/Firestore/Source/API/converters.h @@ -24,6 +24,8 @@ #import #include +#import "FIRSnapshotListenOptions.h" +#import "Firestore/core/src/api/listen_source.h" @class FIRGeoPoint; @class FIRTimestamp; @@ -62,6 +64,8 @@ FIRTimestamp* MakeFIRTimestamp(const Timestamp& timestamp); FIRDocumentReference* MakeFIRDocumentReference(const model::DocumentKey& document_key, std::shared_ptr firestore); +ListenSource MakeListenSource(const FIRListenSource& source); + } // namespace api } // namespace firestore } // namespace firebase diff --git a/Firestore/Source/API/converters.mm b/Firestore/Source/API/converters.mm index 251d3e578bf..8bfd5a07090 100644 --- a/Firestore/Source/API/converters.mm +++ b/Firestore/Source/API/converters.mm @@ -25,6 +25,7 @@ #include "Firestore/core/include/firebase/firestore/geo_point.h" #include "Firestore/core/include/firebase/firestore/timestamp.h" #include "Firestore/core/src/api/firestore.h" +#import "Firestore/core/src/api/listen_source.h" #include "Firestore/core/src/model/document_key.h" NS_ASSUME_NONNULL_BEGIN @@ -61,6 +62,17 @@ Timestamp MakeTimestamp(NSDate* date) { return [[FIRDocumentReference alloc] initWithKey:key firestore:std::move(firestore)]; } +ListenSource MakeListenSource(const FIRListenSource& source) { + switch (source) { + case FIRListenSourceDefault: + return ListenSource::Default; + case FIRListenSourceCache: + return ListenSource::Cache; + default: + return ListenSource::Default; + } +} + } // namespace api } // namespace firestore } // namespace firebase diff --git a/Firestore/Source/Public/FirebaseFirestore/FIRDocumentReference.h b/Firestore/Source/Public/FirebaseFirestore/FIRDocumentReference.h index 70dbd0d02ad..b6f87450cb3 100644 --- a/Firestore/Source/Public/FirebaseFirestore/FIRDocumentReference.h +++ b/Firestore/Source/Public/FirebaseFirestore/FIRDocumentReference.h @@ -18,6 +18,7 @@ #import "FIRFirestoreSource.h" #import "FIRListenerRegistration.h" +#import "FIRSnapshotListenOptions.h" @class FIRCollectionReference; @class FIRDocumentSnapshot; @@ -270,6 +271,22 @@ addSnapshotListenerWithIncludeMetadataChanges:(BOOL)includeMetadataChanges NS_SWIFT_NAME(addSnapshotListener(includeMetadataChanges:listener:)); // clang-format on +/** + * Attaches a listener for `DocumentSnapshot` events. + * + * @param options Sets snapshot listener options, including whether metadata-only changes should + * trigger snapshot events, the source to listen to, the executor to use to call the + * listener, or the activity to scope the listener to. + * @param listener The listener to attach. + * + * @return A `ListenerRegistration` that can be used to remove this listener. + */ +- (id) + addSnapshotListenerWithOptions:(FIRSnapshotListenOptions *)options + listener:(void (^)(FIRDocumentSnapshot *_Nullable snapshot, + NSError *_Nullable error))listener + NS_SWIFT_NAME(addSnapshotListener(options:listener:)); + @end NS_ASSUME_NONNULL_END diff --git a/Firestore/Source/Public/FirebaseFirestore/FIRQuery.h b/Firestore/Source/Public/FirebaseFirestore/FIRQuery.h index bde102c212a..c75952876a2 100644 --- a/Firestore/Source/Public/FirebaseFirestore/FIRQuery.h +++ b/Firestore/Source/Public/FirebaseFirestore/FIRQuery.h @@ -18,6 +18,7 @@ #import "FIRFirestoreSource.h" #import "FIRListenerRegistration.h" +#import "FIRSnapshotListenOptions.h" @class FIRAggregateQuery; @class FIRAggregateField; @@ -104,6 +105,21 @@ NS_SWIFT_NAME(Query) NSError *_Nullable error))listener NS_SWIFT_NAME(addSnapshotListener(includeMetadataChanges:listener:)); +/** + * Attaches a listener for `QuerySnapshot` events. + * @param options Sets snapshot listener options, including whether metadata-only changes should + * trigger snapshot events, the source to listen to, the executor to use to call the + * listener, or the activity to scope the listener to. + * @param listener The listener to attach. + * + * @return A `ListenerRegistration` that can be used to remove this listener. + */ +- (id) + addSnapshotListenerWithOptions:(FIRSnapshotListenOptions *)options + listener:(void (^)(FIRQuerySnapshot *_Nullable snapshot, + NSError *_Nullable error))listener + NS_SWIFT_NAME(addSnapshotListener(options:listener:)); + #pragma mark - Filtering Data /** * Creates and returns a new Query with the additional filter. diff --git a/Firestore/Source/Public/FirebaseFirestore/FIRSnapshotListenOptions.h b/Firestore/Source/Public/FirebaseFirestore/FIRSnapshotListenOptions.h new file mode 100644 index 00000000000..c1ef6a3a3a1 --- /dev/null +++ b/Firestore/Source/Public/FirebaseFirestore/FIRSnapshotListenOptions.h @@ -0,0 +1,83 @@ +/* + * 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. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * The source the snapshot listener retrieves data from. + */ +typedef NS_ENUM(NSUInteger, FIRListenSource) { + /** + * The default behavior. The listener attempts to return initial snapshot from cache and retrieve + * up-to-date snapshots from the Firestore server. Snapshot events will be triggered on local + * mutations and server-side updates. + */ + FIRListenSourceDefault, + /** + * The listener retrieves data and listens to updates from the local Firestore cache without + * attempting to send the query to the server. If some documents gets updated as a result from + * other queries, they will be picked up by listeners using the cache. + * + * Note that the data might be stale if the cache hasn't synchronized with recent server-side + * changes. + */ + FIRListenSourceCache +} NS_SWIFT_NAME(ListenSource); + +/** + * Options to configure the behavior of `Firestore.addSnapshotListenerWithOptions()`. Instances + * of this class control settings like whether metadata-only changes trigger events and the + * preferred data source. + */ +NS_SWIFT_NAME(SnapshotListenOptions) +@interface FIRSnapshotListenOptions : NSObject + +/** The source the snapshot listener retrieves data from. */ +@property(nonatomic, readonly) FIRListenSource source; +/** Indicates whether metadata-only changes should trigger snapshot events. */ +@property(nonatomic, readonly) BOOL includeMetadataChanges; + +/** + * Creates and returns a new `SnapshotListenOptions` object with all properties initialized to their + * default values. + * + * @return The created `SnapshotListenOptions` object. + */ +- (instancetype)init NS_DESIGNATED_INITIALIZER; + +/** + * Creates and returns a new `SnapshotListenOptions` object with all properties of the current + * `SnapshotListenOptions` object plus the new property specifying whether metadata-only changes + * should trigger snapshot events + * + * @return The created `SnapshotListenOptions` object. + */ +- (FIRSnapshotListenOptions *)optionsWithIncludeMetadataChanges:(BOOL)includeMetadataChanges; + +/** + * Creates and returns a new `SnapshotListenOptions` object with all properties of the current + * `SnapshotListenOptions` object plus the new property specifying the source that the snapshot + * listener listens to. + * + * @return The created `SnapshotListenOptions` object. + */ +- (FIRSnapshotListenOptions *)optionsWithSource:(FIRListenSource)source; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firestore/Source/Public/FirebaseFirestore/FIRTimestamp.h b/Firestore/Source/Public/FirebaseFirestore/FIRTimestamp.h index 8ba13a3a128..375f697338a 100644 --- a/Firestore/Source/Public/FirebaseFirestore/FIRTimestamp.h +++ b/Firestore/Source/Public/FirebaseFirestore/FIRTimestamp.h @@ -27,7 +27,7 @@ NS_ASSUME_NONNULL_BEGIN * 9999-12-31T23:59:59.999999999Z. By restricting to that range, we ensure that we can convert to * and from RFC 3339 date strings. * - * @see https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto for the + * @see https://github.com/google/protobuf/blob/main/src/google/protobuf/timestamp.proto for the * reference timestamp definition. */ NS_SWIFT_NAME(Timestamp) diff --git a/Firestore/Source/Public/FirebaseFirestore/FirebaseFirestore.h b/Firestore/Source/Public/FirebaseFirestore/FirebaseFirestore.h index 6746aa489e8..9a0574a8207 100644 --- a/Firestore/Source/Public/FirebaseFirestore/FirebaseFirestore.h +++ b/Firestore/Source/Public/FirebaseFirestore/FirebaseFirestore.h @@ -34,6 +34,7 @@ #import "FIRLocalCacheSettings.h" #import "FIRQuery.h" #import "FIRQuerySnapshot.h" +#import "FIRSnapshotListenOptions.h" #import "FIRSnapshotMetadata.h" #import "FIRTimestamp.h" #import "FIRTransaction.h" diff --git a/Firestore/Source/Resources/PrivacyInfo.xcprivacy b/Firestore/Source/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 00000000000..1e83fa67acd --- /dev/null +++ b/Firestore/Source/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,30 @@ + + + + + NSPrivacyTracking + + NSPrivacyTrackingDomains + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeOtherDiagnosticData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAnalytics + + + + NSPrivacyAccessedAPITypes + + + + + diff --git a/Firestore/Swift/Source/Codable/DocumentReference+ReadDecodable.swift b/Firestore/Swift/Source/Codable/DocumentReference+ReadDecodable.swift index cc6f6f6a6b6..2d3af7a9dfa 100644 --- a/Firestore/Swift/Source/Codable/DocumentReference+ReadDecodable.swift +++ b/Firestore/Swift/Source/Codable/DocumentReference+ReadDecodable.swift @@ -48,14 +48,18 @@ public extension DocumentReference { /// not yet been set to their final value are returned from the snapshot. /// - decoder: The decoder to use to convert the document. Defaults to use /// the default decoder. + /// - source: Indicates whether the results should be fetched from the cache only + /// (`Source.cache`), the server only (`Source.server`), or to attempt the + /// server and fall back to the cache (`Source.default`). /// - completion: The closure to call when the document snapshot has been /// fetched and decoded. func getDocument(as type: T.Type, with serverTimestampBehavior: ServerTimestampBehavior = .none, decoder: Firestore.Decoder = .init(), + source: FirestoreSource = .default, completion: @escaping (Result) -> Void) { - getDocument { snapshot, error in + getDocument(source: source) { snapshot, error in guard let snapshot = snapshot else { /** * Force unwrapping here is fine since this logic corresponds to the auto-synthesized @@ -101,13 +105,17 @@ public extension DocumentReference { /// snapshot. /// - decoder: The decoder to use to convert the document. Defaults to use /// the default decoder. + /// - source: Indicates whether the results should be fetched from the cache only + /// (`Source.cache`), the server only (`Source.server`), or to attempt the + /// server and fall back to the cache (`Source.default`). /// - Returns: This instance of the supplied `Decodable` type `T`. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) func getDocument(as type: T.Type, with serverTimestampBehavior: ServerTimestampBehavior = .none, - decoder: Firestore.Decoder = .init()) async throws -> T { - let snapshot = try await getDocument() + decoder: Firestore.Decoder = .init(), + source: FirestoreSource = .default) async throws -> T { + let snapshot = try await getDocument(source: source) return try snapshot.data(as: T.self, with: serverTimestampBehavior, decoder: decoder) diff --git a/Firestore/Swift/Source/Resources/PrivacyInfo.xcprivacy b/Firestore/Swift/Source/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 00000000000..1e83fa67acd --- /dev/null +++ b/Firestore/Swift/Source/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,30 @@ + + + + + NSPrivacyTracking + + NSPrivacyTrackingDomains + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + NSPrivacyCollectedDataTypeOtherDiagnosticData + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + NSPrivacyCollectedDataTypePurposeAnalytics + + + + NSPrivacyAccessedAPITypes + + + + + diff --git a/Firestore/Swift/Tests/BridgingHeader.h b/Firestore/Swift/Tests/BridgingHeader.h index 5bfed83adf4..58165c82944 100644 --- a/Firestore/Swift/Tests/BridgingHeader.h +++ b/Firestore/Swift/Tests/BridgingHeader.h @@ -18,6 +18,7 @@ #define FIRESTORE_SWIFT_TESTS_BRIDGINGHEADER_H_ #import "Firestore/Example/Tests/API/FSTAPIHelpers.h" +#import "Firestore/Example/Tests/Util/FSTEventAccumulator.h" #import "Firestore/Example/Tests/Util/FSTExceptionCatcher.h" #import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h" diff --git a/Firestore/Swift/Tests/Integration/SnapshotListenerSourceTests.swift b/Firestore/Swift/Tests/Integration/SnapshotListenerSourceTests.swift new file mode 100644 index 00000000000..22c69679224 --- /dev/null +++ b/Firestore/Swift/Tests/Integration/SnapshotListenerSourceTests.swift @@ -0,0 +1,684 @@ +/* + * 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. + */ + +import FirebaseFirestore +import FirebaseFirestoreSwift +import Foundation + +@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) +class SnapshotListenerSourceTests: FSTIntegrationTestCase { + func assertQuerySnapshotDataEquals(_ snapshot: Any, + _ expectedData: [[String: Any]]) throws { + let extractedData = FIRQuerySnapshotGetData(snapshot as! QuerySnapshot) + guard extractedData.count == expectedData.count else { + XCTFail( + "Result count mismatch: Expected \(expectedData.count), got \(extractedData.count)" + ) + return + } + for index in 0 ..< extractedData.count { + XCTAssertTrue(areDictionariesEqual(extractedData[index], expectedData[index])) + } + } + + // TODO(swift testing): update the function to be able to check other value types as well. + func areDictionariesEqual(_ dict1: [String: Any], _ dict2: [String: Any]) -> Bool { + guard dict1.count == dict2.count + else { return false } // Check if the number of elements matches + + for (key, value1) in dict1 { + guard let value2 = dict2[key] else { return false } + + // Value Checks (Assuming consistent types after the type check) + if let str1 = value1 as? String, let str2 = value2 as? String { + if str1 != str2 { return false } + } else if let int1 = value1 as? Int, let int2 = value2 as? Int { + if int1 != int2 { return false } + } else { + // Handle other potential types or return false for mismatch + return false + } + } + return true + } + + func testCanRaiseSnapshotFromCacheForQuery() throws { + let collRef = collectionRef(withDocuments: ["a": ["k": "a"]]) + readDocumentSet(forRef: collRef) // populate the cache. + + let options = SnapshotListenOptions().withSource(ListenSource.cache) + let registration = collRef.addSnapshotListener( + options: options, + listener: eventAccumulator.valueEventHandler + ) + + let querySnap = eventAccumulator.awaitEvent(withName: "snapshot") + try assertQuerySnapshotDataEquals(querySnap, [["k": "a"]]) + XCTAssertEqual(querySnap.metadata.isFromCache, true) + + eventAccumulator.assertNoAdditionalEvents() + registration.remove() + } + + func testCanRaiseSnapshotFromCacheForDocumentReference() throws { + let docRef = documentRef() + docRef.setData(["k": "a"]) + readDocument(forRef: docRef) // populate the cache. + + let options = SnapshotListenOptions().withSource(ListenSource.cache) + let registration = docRef.addSnapshotListener( + options: options, + listener: eventAccumulator.valueEventHandler + ) + + let docSnap = eventAccumulator.awaitEvent(withName: "snapshot") as! DocumentSnapshot + XCTAssertEqual(docSnap.data() as! [String: String], ["k": "a"]) + XCTAssertEqual(docSnap.metadata.isFromCache, true) + + eventAccumulator.assertNoAdditionalEvents() + registration.remove() + } + + func testListenToCacheShouldNotBeAffectedByOnlineStatusChange() throws { + let collRef = collectionRef(withDocuments: ["a": ["k": "a"]]) + readDocumentSet(forRef: collRef) // populate the cache. + + let options = SnapshotListenOptions().withSource(ListenSource.cache) + .withIncludeMetadataChanges(true) + let registration = collRef.addSnapshotListener( + options: options, + listener: eventAccumulator.valueEventHandler + ) + + let querySnap = eventAccumulator.awaitEvent(withName: "snapshot") + try assertQuerySnapshotDataEquals(querySnap, [["k": "a"]]) + XCTAssertEqual(querySnap.metadata.isFromCache, true) + + disableNetwork() + enableNetwork() + + eventAccumulator.assertNoAdditionalEvents() + registration.remove() + } + + func testMultipleListenersSourcedFromCacheCanWorkIndependently() throws { + let collRef = collectionRef(withDocuments: [ + "a": ["k": "a", "sort": 0], + "b": ["k": "b", "sort": 1], + ]) + readDocumentSet(forRef: collRef) // populate the cache. + + let query = collRef.whereField("sort", isGreaterThan: 0).order(by: "sort") + + let options = SnapshotListenOptions().withSource(ListenSource.cache) + let registration1 = query.addSnapshotListener( + options: options, + listener: eventAccumulator.valueEventHandler + ) + let registration2 = query.addSnapshotListener( + options: options, + listener: eventAccumulator.valueEventHandler + ) + + var expected = [["k": "b", "sort": 1]] + var querySnap = eventAccumulator.awaitEvent(withName: "snapshot") + try assertQuerySnapshotDataEquals(querySnap, expected) + XCTAssertEqual(querySnap.metadata.isFromCache, true) + querySnap = eventAccumulator.awaitEvent(withName: "snapshot") + try assertQuerySnapshotDataEquals(querySnap, expected) + XCTAssertEqual(querySnap.metadata.isFromCache, true) + + // Do a local mutation + addDocumentRef(collRef, data: ["k": "c", "sort": 2]) + + expected = [["k": "b", "sort": 1], ["k": "c", "sort": 2]] + querySnap = eventAccumulator.awaitEvent(withName: "snapshot") + try assertQuerySnapshotDataEquals(querySnap, expected) + XCTAssertEqual(querySnap.metadata.isFromCache, true) + querySnap = eventAccumulator.awaitEvent(withName: "snapshot") + try assertQuerySnapshotDataEquals(querySnap, expected) + XCTAssertEqual(querySnap.metadata.isFromCache, true) + + // Detach one listener, and do a local mutation. The other listener + // should not be affected. + registration1.remove() + addDocumentRef(collRef, data: ["k": "d", "sort": 3]) + + expected = [["k": "b", "sort": 1], ["k": "c", "sort": 2], ["k": "d", "sort": 3]] + querySnap = eventAccumulator.awaitEvent(withName: "snapshot") + try assertQuerySnapshotDataEquals(querySnap, expected) + XCTAssertEqual(querySnap.metadata.isFromCache, true) + + eventAccumulator.assertNoAdditionalEvents() + registration2.remove() + } + + // Two queries that mapped to the same target ID are referred to as + // "mirror queries". An example for a mirror query is a limitToLast() + // query and a limit() query that share the same backend Target ID. + // Since limitToLast() queries are sent to the backend with a modified + // orderBy() clause, they can map to the same target representation as + // limit() query, even if both queries appear separate to the user. + func testListenUnlistenRelistenToMirrorQueriesFromCache() throws { + let collRef = collectionRef(withDocuments: [ + "a": ["k": "a", "sort": 0], + "b": ["k": "b", "sort": 1], + "c": ["k": "c", "sort": 1], + ]) + readDocumentSet(forRef: collRef) // populate the cache. + let options = SnapshotListenOptions().withSource(ListenSource.cache) + + // Setup a `limit` query. + let limit = collRef.order(by: "sort", descending: false).limit(to: 2) + let limitAccumulator = FSTEventAccumulator.init(forTest: self) + var limitRegistration = limit.addSnapshotListener( + options: options, + listener: limitAccumulator.valueEventHandler + ) + // Setup a mirroring `limitToLast` query. + let limitToLast = collRef.order(by: "sort", descending: true).limit(toLast: 2) + let limitToLastAccumulator = FSTEventAccumulator + .init(forTest: self) + var limitToLastRegistration = limitToLast.addSnapshotListener( + options: options, + listener: limitToLastAccumulator.valueEventHandler + ) + + // Verify both queries get expected result. + var querySnap = limitAccumulator.awaitEvent(withName: "snapshot") + try assertQuerySnapshotDataEquals(querySnap, [["k": "a", "sort": 0], ["k": "b", "sort": 1]]) + XCTAssertEqual(querySnap.metadata.isFromCache, true) + + querySnap = limitToLastAccumulator.awaitEvent(withName: "snapshot") + try assertQuerySnapshotDataEquals(querySnap, [["k": "b", "sort": 1], ["k": "a", "sort": 0]]) + XCTAssertEqual(querySnap.metadata.isFromCache, true) + + // Un-listen then re-listen to the limit query. + limitRegistration.remove() + limitRegistration = limit.addSnapshotListener( + options: options, + listener: limitAccumulator.valueEventHandler + ) + querySnap = limitAccumulator.awaitEvent(withName: "snapshot") + try assertQuerySnapshotDataEquals( + querySnap, + [["k": "a", "sort": 0], ["k": "b", "sort": 1]] + ) + XCTAssertEqual(querySnap.metadata.isFromCache, true) + + // Add a document that would change the result set. + addDocumentRef(collRef, data: ["k": "d", "sort": -1]) + + // Verify both queries get expected result. + querySnap = limitAccumulator.awaitEvent(withName: "snapshot") + try assertQuerySnapshotDataEquals(querySnap, [["k": "d", "sort": -1], ["k": "a", "sort": 0]]) + XCTAssertEqual(querySnap.metadata.isFromCache, true) + XCTAssertEqual(querySnap.metadata.hasPendingWrites, true) + querySnap = limitToLastAccumulator.awaitEvent(withName: "snapshot") + try assertQuerySnapshotDataEquals(querySnap, [["k": "a", "sort": 0], ["k": "d", "sort": -1]]) + XCTAssertEqual(querySnap.metadata.isFromCache, true) + XCTAssertEqual(querySnap.metadata.hasPendingWrites, true) + + // Un-listen to limitToLast, update a doc, then re-listen to limitToLast + limitToLastRegistration.remove() + updateDocumentRef(collRef.document("a"), data: ["k": "a", "sort": -2]) + limitToLastRegistration = limitToLast.addSnapshotListener( + options: options, + listener: limitToLastAccumulator.valueEventHandler + ) + + // Verify both queries get expected result. + querySnap = limitAccumulator.awaitEvent(withName: "snapshot") + try assertQuerySnapshotDataEquals(querySnap, [["k": "a", "sort": -2], ["k": "d", "sort": -1]]) + XCTAssertEqual(querySnap.metadata.isFromCache, true) + XCTAssertEqual(querySnap.metadata.hasPendingWrites, true) + querySnap = limitToLastAccumulator.awaitEvent(withName: "snapshot") + try assertQuerySnapshotDataEquals(querySnap, [["k": "d", "sort": -1], ["k": "a", "sort": -2]]) + XCTAssertEqual(querySnap.metadata.isFromCache, true) + // We listened to LimitToLast query after the doc update. + XCTAssertEqual(querySnap.metadata.hasPendingWrites, false) + } + + func testCanListenToDefaultSourceFirstAndThenCache() throws { + let collRef = collectionRef(withDocuments: [ + "a": ["k": "a", "sort": 0], + "b": ["k": "b", "sort": 1], + ]) + let query = collRef.whereField("sort", isGreaterThanOrEqualTo: 1).order(by: "sort") + + // Listen to the query with default options, which will also populates the cache + let defaultAccumulator = FSTEventAccumulator.init(forTest: self) + let defaultRegistration = query.addSnapshotListener(defaultAccumulator.valueEventHandler) + + var querySnap = defaultAccumulator.awaitEvent(withName: "snapshot") + try assertQuerySnapshotDataEquals(querySnap, [["k": "b", "sort": 1]]) + XCTAssertEqual(querySnap.metadata.isFromCache, false) + + // Listen to the same query from cache + let cacheAccumulator = FSTEventAccumulator + .init(forTest: self) + let options = SnapshotListenOptions().withSource(ListenSource.cache) + let cacheRegistration = query.addSnapshotListener( + options: options, + listener: cacheAccumulator.valueEventHandler + ) + querySnap = cacheAccumulator.awaitEvent(withName: "snapshot") + try assertQuerySnapshotDataEquals(querySnap, [["k": "b", "sort": 1]]) + // The metadata is sync with server due to the default listener + XCTAssertEqual(querySnap.metadata.isFromCache, false) + + defaultAccumulator.assertNoAdditionalEvents() + cacheAccumulator.assertNoAdditionalEvents() + defaultRegistration.remove() + cacheRegistration.remove() + } + + func testCanListenToCacheSourceFirstAndThenDefault() throws { + let collRef = collectionRef(withDocuments: [ + "a": ["k": "a", "sort": 0], + "b": ["k": "b", "sort": 1], + ]) + let query = collRef.whereField("sort", isNotEqualTo: 0).order(by: "sort") + + // Listen to the cache + let cacheAccumulator = FSTEventAccumulator + .init(forTest: self) + let options = SnapshotListenOptions().withSource(ListenSource.cache) + let cacheRegistration = query.addSnapshotListener( + options: options, + listener: cacheAccumulator.valueEventHandler + ) + var querySnap = cacheAccumulator.awaitEvent(withName: "snapshot") + // Cache is empty + try assertQuerySnapshotDataEquals(querySnap, []) + XCTAssertEqual(querySnap.metadata.isFromCache, true) + + // Listen to the same query from server + let defaultAccumulator = FSTEventAccumulator.init(forTest: self) + let defaultRegistration = query.addSnapshotListener(defaultAccumulator.valueEventHandler) + querySnap = defaultAccumulator.awaitEvent(withName: "snapshot") + try assertQuerySnapshotDataEquals(querySnap, [["k": "b", "sort": 1]]) + XCTAssertEqual(querySnap.metadata.isFromCache, false) + + // Default listener updates the cache, whish triggers cache listener to raise snapshot. + querySnap = cacheAccumulator.awaitEvent(withName: "snapshot") + try assertQuerySnapshotDataEquals(querySnap, [["k": "b", "sort": 1]]) + // The metadata is sync with server due to the default listener + XCTAssertEqual(querySnap.metadata.isFromCache, false) + + defaultAccumulator.assertNoAdditionalEvents() + cacheAccumulator.assertNoAdditionalEvents() + defaultRegistration.remove() + cacheRegistration.remove() + } + + func testWillNotGetMetadataOnlyUpdatesIfListeningToCacheOnly() throws { + let collRef = collectionRef(withDocuments: [ + "a": ["k": "a", "sort": 0], + "b": ["k": "b", "sort": 1], + ]) + readDocumentSet(forRef: collRef) // populate the cache. + + let query = collRef.whereField("sort", isNotEqualTo: 0).order(by: "sort") + let options = SnapshotListenOptions().withSource(ListenSource.cache) + + let registration = query.addSnapshotListener( + options: options, + listener: eventAccumulator.valueEventHandler + ) + + var querySnap = eventAccumulator.awaitEvent(withName: "snapshot") + try assertQuerySnapshotDataEquals(querySnap, [["k": "b", "sort": 1]]) + XCTAssertEqual(querySnap.metadata.isFromCache, true) + + // Do a local mutation + addDocumentRef(collRef, data: ["k": "c", "sort": 2]) + + querySnap = eventAccumulator.awaitEvent(withName: "snapshot") + try assertQuerySnapshotDataEquals(querySnap, [["k": "b", "sort": 1], ["k": "c", "sort": 2]]) + XCTAssertEqual(querySnap.metadata.isFromCache, true) + XCTAssertEqual(querySnap.metadata?.hasPendingWrites, true) + + // As we are not listening to server, the listener will not get notified + // when local mutation is acknowledged by server. + eventAccumulator.assertNoAdditionalEvents() + registration.remove() + } + + func testWillHaveSynceMetadataUpdatesWhenListeningToBothCacheAndDefaultSource() throws { + let collRef = collectionRef(withDocuments: [ + "a": ["k": "a", "sort": 0], + "b": ["k": "b", "sort": 1], + ]) + readDocumentSet(forRef: collRef) // populate the cache. + let query = collRef.whereField("sort", isNotEqualTo: 0).order(by: "sort") + + // Listen to the cache + let cacheAccumulator = FSTEventAccumulator.init(forTest: self) + let options = SnapshotListenOptions().withSource(ListenSource.cache) + .withIncludeMetadataChanges(true) + let cacheRegistration = query.addSnapshotListener( + options: options, + listener: cacheAccumulator.valueEventHandler + ) + var querySnap = cacheAccumulator.awaitEvent(withName: "snapshot") + var expected = [["k": "b", "sort": 1]] + try assertQuerySnapshotDataEquals(querySnap, expected) + XCTAssertEqual(querySnap.metadata.isFromCache, true) + + // Listen to the same query from server + let defaultAccumulator = FSTEventAccumulator.init(forTest: self) + let defaultRegistration = query.addSnapshotListener( + includeMetadataChanges: true, + listener: defaultAccumulator.valueEventHandler + ) + + querySnap = defaultAccumulator.awaitEvent(withName: "snapshot") + try assertQuerySnapshotDataEquals(querySnap, expected) + // First snapshot will be raised from cache. + XCTAssertEqual(querySnap.metadata.isFromCache, true) + querySnap = defaultAccumulator.awaitEvent(withName: "snapshot") + // Second snapshot will be raised from server result + XCTAssertEqual(querySnap.metadata.isFromCache, false) + + // As listening to metadata changes, the cache listener also gets triggered and synced + // with default listener. + querySnap = cacheAccumulator.awaitEvent(withName: "snapshot") + // The metadata is sync with server due to the default listener + XCTAssertEqual(querySnap.metadata.isFromCache, false) + + // Do a local mutation + addDocumentRef(collRef, data: ["k": "c", "sort": 2]) + + // snapshot gets triggered by local mutation + expected = [["k": "b", "sort": 1], ["k": "c", "sort": 2]] + querySnap = defaultAccumulator.awaitEvent(withName: "snapshot") + try assertQuerySnapshotDataEquals(querySnap, expected) + XCTAssertEqual(querySnap.metadata.hasPendingWrites, true) + XCTAssertEqual(querySnap.metadata.isFromCache, false) + querySnap = cacheAccumulator.awaitEvent(withName: "snapshot") + try assertQuerySnapshotDataEquals(querySnap, expected) + XCTAssertEqual(querySnap.metadata.hasPendingWrites, true) + XCTAssertEqual(querySnap.metadata.isFromCache, false) + + // Local mutation gets acknowledged by the server + querySnap = defaultAccumulator.awaitEvent(withName: "snapshot") + XCTAssertEqual(querySnap.metadata.hasPendingWrites, false) + XCTAssertEqual(querySnap.metadata.isFromCache, false) + querySnap = cacheAccumulator.awaitEvent(withName: "snapshot") + XCTAssertEqual(querySnap.metadata.hasPendingWrites, false) + XCTAssertEqual(querySnap.metadata.isFromCache, false) + + defaultAccumulator.assertNoAdditionalEvents() + cacheAccumulator.assertNoAdditionalEvents() + defaultRegistration.remove() + cacheRegistration.remove() + } + + func testCanUnlistenToDefaultSourceWhileStillListeningToCache() throws { + let collRef = collectionRef(withDocuments: [ + "a": ["k": "a", "sort": 0], + "b": ["k": "b", "sort": 1], + ]) + let query = collRef.whereField("sort", isNotEqualTo: 0).order(by: "sort") + + // Listen to the query with both source options + let defaultAccumulator = FSTEventAccumulator.init(forTest: self) + let defaultRegistration = query.addSnapshotListener(defaultAccumulator.valueEventHandler) + defaultAccumulator.awaitEvent(withName: "snapshot") + let cacheAccumulator = FSTEventAccumulator + .init(forTest: self) + let options = SnapshotListenOptions().withSource(ListenSource.cache) + let cacheRegistration = query.addSnapshotListener( + options: options, + listener: cacheAccumulator.valueEventHandler + ) + cacheAccumulator.awaitEvent(withName: "snapshot") + + // Un-listen to the default listener. + defaultRegistration.remove() + + // Add a document and verify listener to cache works as expected + addDocumentRef(collRef, data: ["k": "c", "sort": -1]) + defaultAccumulator.assertNoAdditionalEvents() + + let querySnap = cacheAccumulator.awaitEvent(withName: "snapshot") + try assertQuerySnapshotDataEquals( + querySnap, + [["k": "c", "sort": -1], ["k": "b", "sort": 1]] + ) + + cacheAccumulator.assertNoAdditionalEvents() + cacheRegistration.remove() + } + + func testCanUnlistenToCacheSourceWhileStillListeningToServer() throws { + let collRef = collectionRef(withDocuments: [ + "a": ["k": "a", "sort": 0], + "b": ["k": "b", "sort": 1], + ]) + let query = collRef.whereField("sort", isNotEqualTo: 0).order(by: "sort") + + // Listen to the query with both source options + let defaultAccumulator = FSTEventAccumulator.init(forTest: self) + let defaultRegistration = query.addSnapshotListener(defaultAccumulator.valueEventHandler) + defaultAccumulator.awaitEvent(withName: "snapshot") + let cacheAccumulator = FSTEventAccumulator + .init(forTest: self) + let options = SnapshotListenOptions().withSource(ListenSource.cache) + let cacheRegistration = query.addSnapshotListener( + options: options, + listener: cacheAccumulator.valueEventHandler + ) + cacheAccumulator.awaitEvent(withName: "snapshot") + + // Un-listen to cache. + cacheRegistration.remove() + + // Add a document and verify listener to server works as expected. + addDocumentRef(collRef, data: ["k": "c", "sort": -1]) + cacheAccumulator.assertNoAdditionalEvents() + + let querySnap = defaultAccumulator.awaitEvent(withName: "snapshot") + try assertQuerySnapshotDataEquals( + querySnap, + [["k": "c", "sort": -1], ["k": "b", "sort": 1]] + ) + + defaultAccumulator.assertNoAdditionalEvents() + defaultRegistration.remove() + } + + func testCanListenUnlistenRelistenToSameQueryWithDifferentSourceOptions() throws { + let collRef = collectionRef(withDocuments: [ + "a": ["k": "a", "sort": 0], + "b": ["k": "b", "sort": 1], + ]) + let query = collRef.whereField("sort", isGreaterThan: 0).order(by: "sort") + + // Listen to the query with default options, which will also populates the cache + let defaultAccumulator = FSTEventAccumulator.init(forTest: self) + var defaultRegistration = query.addSnapshotListener(defaultAccumulator.valueEventHandler) + var querySnap = defaultAccumulator.awaitEvent(withName: "snapshot") + var expected = [["k": "b", "sort": 1]] + try assertQuerySnapshotDataEquals(querySnap, expected) + + // Listen to the same query from cache + let cacheAccumulator = FSTEventAccumulator + .init(forTest: self) + let options = SnapshotListenOptions().withSource(ListenSource.cache) + var cacheRegistration = query.addSnapshotListener( + options: options, + listener: cacheAccumulator.valueEventHandler + ) + querySnap = cacheAccumulator.awaitEvent(withName: "snapshot") + try assertQuerySnapshotDataEquals(querySnap, expected) + + // Un-listen to the default listener, add a doc and re-listen. + defaultRegistration.remove() + addDocumentRef(collRef, data: ["k": "c", "sort": 2]) + + expected = [["k": "b", "sort": 1], ["k": "c", "sort": 2]] + querySnap = cacheAccumulator.awaitEvent(withName: "snapshot") + try assertQuerySnapshotDataEquals(querySnap, expected) + + defaultRegistration = query.addSnapshotListener(defaultAccumulator.valueEventHandler) + querySnap = defaultAccumulator.awaitEvent(withName: "snapshot") + try assertQuerySnapshotDataEquals(querySnap, expected) + + // Un-listen to cache, update a doc, then re-listen to cache. + cacheRegistration.remove() + updateDocumentRef(collRef.document("b"), data: ["k": "b", "sort": 3]) + + expected = [["k": "c", "sort": 2], ["k": "b", "sort": 3]] + querySnap = defaultAccumulator.awaitEvent(withName: "snapshot") + try assertQuerySnapshotDataEquals( + querySnap, expected + ) + + cacheRegistration = query.addSnapshotListener( + options: options, + listener: cacheAccumulator.valueEventHandler + ) + querySnap = cacheAccumulator.awaitEvent(withName: "snapshot") + try assertQuerySnapshotDataEquals( + querySnap, expected + ) + + defaultAccumulator.assertNoAdditionalEvents() + cacheAccumulator.assertNoAdditionalEvents() + defaultRegistration.remove() + cacheRegistration.remove() + } + + func testCanListenToCompositeIndexQueriesFromCache() throws { + let collRef = collectionRef(withDocuments: [ + "a": ["k": "a", "sort": 0], + "b": ["k": "b", "sort": 1], + ]) + readDocumentSet(forRef: collRef) // populate the cache. + + let query = collRef.whereField("k", isLessThanOrEqualTo: "a") + .whereField("sort", isGreaterThanOrEqualTo: 0) + + let options = SnapshotListenOptions().withSource(ListenSource.cache) + let registration = query.addSnapshotListener( + options: options, + listener: eventAccumulator.valueEventHandler + ) + + let querySnap = eventAccumulator.awaitEvent(withName: "snapshot") + try assertQuerySnapshotDataEquals(querySnap, [["k": "a", "sort": 0]]) + + eventAccumulator.assertNoAdditionalEvents() + registration.remove() + } + + func testCanRaiseInitialSnapshotFromCachedEmptyResults() throws { + let collRef = collectionRef() + + // Populate the cache with empty query result. + var querySnap = readDocumentSet(forRef: collRef) + try assertQuerySnapshotDataEquals(querySnap, []) + + // Add a snapshot listener whose first event should be raised from cache. + let options = SnapshotListenOptions().withSource(ListenSource.cache) + let registration = collRef.addSnapshotListener( + options: options, + listener: eventAccumulator.valueEventHandler + ) + + querySnap = eventAccumulator.awaitEvent(withName: "initial event") as! QuerySnapshot + try assertQuerySnapshotDataEquals(querySnap, []) + XCTAssertEqual(querySnap.metadata.isFromCache, true) + + eventAccumulator.assertNoAdditionalEvents() + registration.remove() + } + + func testWillNotBeTriggeredByTransactionsWhileListeningToCache() throws { + let collRef = collectionRef() + + // Add a snapshot listener whose first event should be raised from cache. + let options = SnapshotListenOptions().withSource(ListenSource.cache) + let registration = collRef.addSnapshotListener( + options: options, + listener: eventAccumulator.valueEventHandler + ) + let querySnap = eventAccumulator.awaitEvent(withName: "initial event") + try assertQuerySnapshotDataEquals(querySnap, []) + + let docRef = documentRef() + // Use a transaction to perform a write without triggering any local events. + runTransaction(docRef.firestore, block: { transaction, errorPointer -> Any? in + transaction.updateData(["K": "a"], forDocument: docRef) + return nil + }) + + // There should be no events raised + eventAccumulator.assertNoAdditionalEvents() + registration.remove() + } + + func testSharesServerSideUpdatesWhenListeningToBothCacheAndDefault() throws { + let collRef = collectionRef(withDocuments: [ + "a": ["k": "a", "sort": 0], + "b": ["k": "b", "sort": 1], + ]) + let query = collRef.whereField("sort", isGreaterThan: 0).order(by: "sort") + + // Listen to the query with default options, which will also populates the cache + let defaultAccumulator = FSTEventAccumulator.init(forTest: self) + let defaultRegistration = query.addSnapshotListener(defaultAccumulator.valueEventHandler) + var querySnap = defaultAccumulator.awaitEvent(withName: "snapshot") + var expected = [["k": "b", "sort": 1]] + try assertQuerySnapshotDataEquals(querySnap, expected) + + // Listen to the same query from cache + let cacheAccumulator = FSTEventAccumulator + .init(forTest: self) + let options = SnapshotListenOptions().withSource(ListenSource.cache) + let cacheRegistration = query.addSnapshotListener( + options: options, + listener: cacheAccumulator.valueEventHandler + ) + querySnap = cacheAccumulator.awaitEvent(withName: "snapshot") + try assertQuerySnapshotDataEquals(querySnap, expected) + + // Use a transaction to mock server side updates + let docRef = collRef.document() + runTransaction(docRef.firestore, block: { transaction, errorPointer -> Any? in + transaction.setData(["k": "c", "sort": 2], forDocument: docRef) + return nil + }) + + // Default listener receives the server update + querySnap = defaultAccumulator.awaitEvent(withName: "snapshot") + expected = [["k": "b", "sort": 1], ["k": "c", "sort": 2]] + try assertQuerySnapshotDataEquals(querySnap, expected) + XCTAssertEqual(querySnap.metadata.isFromCache, false) + + // Cache listener raises snapshot as well + querySnap = cacheAccumulator.awaitEvent(withName: "snapshot") + try assertQuerySnapshotDataEquals(querySnap, expected) + XCTAssertEqual(querySnap.metadata.isFromCache, false) + + defaultAccumulator.assertNoAdditionalEvents() + cacheAccumulator.assertNoAdditionalEvents() + defaultRegistration.remove() + cacheRegistration.remove() + } +} diff --git a/Firestore/core/include/firebase/firestore/timestamp.h b/Firestore/core/include/firebase/firestore/timestamp.h index 286da1149f1..0ab818532f8 100644 --- a/Firestore/core/include/firebase/firestore/timestamp.h +++ b/Firestore/core/include/firebase/firestore/timestamp.h @@ -38,7 +38,7 @@ namespace firebase { * from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. * * @see - * https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto + * https://github.com/google/protobuf/blob/main/src/google/protobuf/timestamp.proto */ class Timestamp { public: diff --git a/Firestore/core/src/api/listen_source.h b/Firestore/core/src/api/listen_source.h new file mode 100644 index 00000000000..2053a92df39 --- /dev/null +++ b/Firestore/core/src/api/listen_source.h @@ -0,0 +1,37 @@ +/* + * Copyright 2024 Google + * + * 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. + */ + +#ifndef FIRESTORE_CORE_SRC_API_LISTEN_SOURCE_H_ +#define FIRESTORE_CORE_SRC_API_LISTEN_SOURCE_H_ + +namespace firebase { +namespace firestore { +namespace api { + +/** + * An enum that configures the snapshot listener data source. Using this enum, + * specify whether snapshot events are triggered by local cache changes + * only, or from both local cache and watch changes(which is the default). + * + * See `FIRFirestoreListenSource` for more details. + */ +enum class ListenSource { Default, Cache }; + +} // namespace api +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_API_LISTEN_SOURCE_H_ diff --git a/Firestore/core/src/bundle/bundle_serializer.cc b/Firestore/core/src/bundle/bundle_serializer.cc index 9cc6f8e7b55..86942236791 100644 --- a/Firestore/core/src/bundle/bundle_serializer.cc +++ b/Firestore/core/src/bundle/bundle_serializer.cc @@ -584,10 +584,16 @@ Message BundleSerializer::DecodeValue( Message BundleSerializer::DecodeMapValue( JsonReader& reader, const json& map_json) const { - if (!map_json.is_object() || !map_json.contains("fields")) { - reader.Fail("mapValue is not a valid map"); + if (!map_json.is_object()) { + reader.Fail("mapValue is not a valid object"); return {}; } + + // Empty map doesn't have `fields` field. + if (!map_json.contains("fields")) { + return {}; + } + const auto& fields = map_json.at("fields"); if (!fields.is_object()) { reader.Fail("mapValue's 'field' is not a valid map"); diff --git a/Firestore/core/src/core/event_manager.cc b/Firestore/core/src/core/event_manager.cc index 283a8341076..d5c3f3542b9 100644 --- a/Firestore/core/src/core/event_manager.cc +++ b/Firestore/core/src/core/event_manager.cc @@ -37,11 +37,27 @@ EventManager::EventManager(QueryEventSource* query_event_source) model::TargetId EventManager::AddQueryListener( std::shared_ptr listener) { const Query& query = listener->query(); + ListenerSetupAction listener_action = + ListenerSetupAction::NoSetupActionRequired; auto inserted = queries_.emplace(query, QueryListenersInfo{}); + // If successfully inserted, it means we haven't listened to this query + // before. bool first_listen = inserted.second; QueryListenersInfo& query_info = inserted.first->second; + if (first_listen) { + listener_action = listener->listens_to_remote_store() + ? ListenerSetupAction:: + InitializeLocalListenAndRequireWatchConnection + : ListenerSetupAction::InitializeLocalListenOnly; + } else if (!query_info.has_remote_listeners() && + listener->listens_to_remote_store()) { + // Query has been listening to local cache, and tries to add a new listener + // sourced from watch. + listener_action = ListenerSetupAction::RequireWatchConnectionOnly; + } + query_info.listeners.push_back(listener); bool raised_event = listener->OnOnlineStateChanged(online_state_); @@ -56,8 +72,20 @@ model::TargetId EventManager::AddQueryListener( } } - if (first_listen) { - query_info.target_id = query_event_source_->Listen(query); + switch (listener_action) { + case ListenerSetupAction::InitializeLocalListenAndRequireWatchConnection: + query_info.target_id = query_event_source_->Listen( + query, /** should_listen_to_remote= */ true); + break; + case ListenerSetupAction::InitializeLocalListenOnly: + query_info.target_id = query_event_source_->Listen( + query, /** should_listen_to_remote= */ false); + break; + case ListenerSetupAction::RequireWatchConnectionOnly: + query_event_source_->ListenToRemoteStore(query); + break; + default: + break; } return query_info.target_id; } @@ -65,18 +93,41 @@ model::TargetId EventManager::AddQueryListener( void EventManager::RemoveQueryListener( std::shared_ptr listener) { const Query& query = listener->query(); - bool last_listen = false; + ListenerRemovalAction listener_action = + ListenerRemovalAction::NoRemovalActionRequired; auto found_iter = queries_.find(query); if (found_iter != queries_.end()) { QueryListenersInfo& query_info = found_iter->second; query_info.Erase(listener); - last_listen = query_info.listeners.empty(); + + if (query_info.listeners.empty()) { + listener_action = + listener->listens_to_remote_store() + ? ListenerRemovalAction:: + TerminateLocalListenAndRequireWatchDisconnection + : ListenerRemovalAction::TerminateLocalListenOnly; + } else if (!query_info.has_remote_listeners() && + listener->listens_to_remote_store()) { + // The removed listener is the last one that sourced from watch. + listener_action = ListenerRemovalAction::RequireWatchDisconnectionOnly; + } } - if (last_listen) { - queries_.erase(found_iter); - query_event_source_->StopListening(query); + switch (listener_action) { + case ListenerRemovalAction:: + TerminateLocalListenAndRequireWatchDisconnection: + queries_.erase(found_iter); + return query_event_source_->StopListening( + query, /** should_stop_remote_listening= */ true); + case ListenerRemovalAction::TerminateLocalListenOnly: + queries_.erase(found_iter); + return query_event_source_->StopListening( + query, /** should_stop_remote_listening= */ false); + case ListenerRemovalAction::RequireWatchDisconnectionOnly: + return query_event_source_->StopListeningToRemoteStoreOnly(query); + default: + return; } } diff --git a/Firestore/core/src/core/event_manager.h b/Firestore/core/src/core/event_manager.h index ba4f2ee56e9..9ee783a85bd 100644 --- a/Firestore/core/src/core/event_manager.h +++ b/Firestore/core/src/core/event_manager.h @@ -23,6 +23,7 @@ #include #include "Firestore/core/src/core/query.h" +#include "Firestore/core/src/core/query_listener.h" #include "Firestore/core/src/core/sync_engine_callback.h" #include "Firestore/core/src/core/view_snapshot.h" #include "Firestore/core/src/model/model_fwd.h" @@ -35,7 +36,6 @@ namespace firestore { namespace core { class QueryEventSource; -class QueryListener; /** * EventManager is responsible for mapping queries to query event listeners. @@ -97,12 +97,35 @@ class EventManager : public SyncEngineCallback { snapshot_ = snapshot; } + bool has_remote_listeners() { + for (const auto& listener : listeners) { + if (listener->listens_to_remote_store()) { + return true; + } + } + return false; + } + private: // Other members are public in this struct, ensure that any reads are // copies by requiring reads to go through a const getter. absl::optional snapshot_; }; + enum ListenerSetupAction { + InitializeLocalListenAndRequireWatchConnection = 0, + InitializeLocalListenOnly = 1, + RequireWatchConnectionOnly = 2, + NoSetupActionRequired = 3 + }; + + enum ListenerRemovalAction { + TerminateLocalListenAndRequireWatchDisconnection = 0, + TerminateLocalListenOnly = 1, + RequireWatchDisconnectionOnly = 2, + NoRemovalActionRequired = 3 + }; + QueryEventSource* query_event_source_ = nullptr; model::OnlineState online_state_ = model::OnlineState::Unknown; std::unordered_map queries_; diff --git a/Firestore/core/src/core/listen_options.h b/Firestore/core/src/core/listen_options.h index cf76d872878..2499b75e224 100644 --- a/Firestore/core/src/core/listen_options.h +++ b/Firestore/core/src/core/listen_options.h @@ -17,10 +17,14 @@ #ifndef FIRESTORE_CORE_SRC_CORE_LISTEN_OPTIONS_H_ #define FIRESTORE_CORE_SRC_CORE_LISTEN_OPTIONS_H_ +#include +#include "Firestore/core/src/api/listen_source.h" namespace firebase { namespace firestore { namespace core { +using api::ListenSource; + class ListenOptions { public: ListenOptions() = default; @@ -44,14 +48,36 @@ class ListenOptions { } /** - * Creates a default ListenOptions, with metadata changes and - * wait_for_sync_when_online disabled. + * Creates a new ListenOptions. + * + * @param include_query_metadata_changes Raise events when only metadata of + * the query changes. + * @param include_document_metadata_changes Raise events when only metadata of + * documents changes. + * @param wait_for_sync_when_online Wait for a sync with the server when + * online, but still raise events while offline. + * @param source sets the source a snapshot listener listens to. + */ + ListenOptions(bool include_query_metadata_changes, + bool include_document_metadata_changes, + bool wait_for_sync_when_online, + ListenSource source) + : include_query_metadata_changes_(include_query_metadata_changes), + include_document_metadata_changes_(include_document_metadata_changes), + wait_for_sync_when_online_(wait_for_sync_when_online), + source_(std::move(source)) { + } + + /** + * Creates a default ListenOptions, with metadata changes, + * wait_for_sync_when_online disabled, and listen source set to default. */ static ListenOptions DefaultOptions() { return ListenOptions( /*include_query_metadata_changes=*/false, /*include_document_metadata_changes=*/false, - /*wait_for_sync_when_online=*/false); + /*wait_for_sync_when_online=*/false, + /*source=*/ListenSource::Default); } /** @@ -63,7 +89,19 @@ class ListenOptions { return ListenOptions( /*include_query_metadata_changes=*/include_metadata_changes, /*include_document_metadata_changes=*/include_metadata_changes, - /*wait_for_sync_when_online=*/false); + /*wait_for_sync_when_online=*/false, + /*source=*/ListenSource::Default); + } + + /** + * Creates a ListenOptions which sets the source snapshot listener listens to. + */ + static ListenOptions FromOptions(bool include_metadata_changes, + ListenSource source) { + return ListenOptions( + /*include_query_metadata_changes=*/include_metadata_changes, + /*include_document_metadata_changes=*/include_metadata_changes, + /*wait_for_sync_when_online=*/false, std::move(source)); } bool include_query_metadata_changes() const { @@ -78,10 +116,15 @@ class ListenOptions { return wait_for_sync_when_online_; } + ListenSource source() const { + return source_; + } + private: bool include_query_metadata_changes_ = false; bool include_document_metadata_changes_ = false; bool wait_for_sync_when_online_ = false; + ListenSource source_ = ListenSource::Default; }; } // namespace core diff --git a/Firestore/core/src/core/query_listener.cc b/Firestore/core/src/core/query_listener.cc index 3e0cbb2cceb..c0ec38e752a 100644 --- a/Firestore/core/src/core/query_listener.cc +++ b/Firestore/core/src/core/query_listener.cc @@ -136,6 +136,11 @@ bool QueryListener::ShouldRaiseInitialEvent(const ViewSnapshot& snapshot, return true; } + // Always raise first event if listening to cache + if (!listens_to_remote_store()) { + return true; + } + // NOTE: We consider OnlineState::Unknown as online (it should become Offline // or Online if we wait long enough). bool maybe_online = online_state != OnlineState::Offline; diff --git a/Firestore/core/src/core/query_listener.h b/Firestore/core/src/core/query_listener.h index eec5ada73b8..6b934a0de59 100644 --- a/Firestore/core/src/core/query_listener.h +++ b/Firestore/core/src/core/query_listener.h @@ -63,6 +63,10 @@ class QueryListener { return query_; } + bool listens_to_remote_store() const { + return options_.source() != ListenSource::Cache; + } + /** The last received view snapshot. */ const absl::optional& snapshot() const { return snapshot_; diff --git a/Firestore/core/src/core/sync_engine.cc b/Firestore/core/src/core/sync_engine.cc index 24a4c3f7803..77223cb1fed 100644 --- a/Firestore/core/src/core/sync_engine.cc +++ b/Firestore/core/src/core/sync_engine.cc @@ -104,7 +104,7 @@ void SyncEngine::AssertCallbackExists(absl::string_view source) { "Tried to call '%s' before callback was registered.", source); } -TargetId SyncEngine::Listen(Query query) { +TargetId SyncEngine::Listen(Query query, bool should_listen_to_remote) { AssertCallbackExists("Listen"); HARD_ASSERT(query_views_by_query_.find(query) == query_views_by_query_.end(), @@ -121,7 +121,9 @@ TargetId SyncEngine::Listen(Query query) { snapshots.push_back(std::move(view_snapshot)); sync_engine_callback_->OnViewSnapshots(std::move(snapshots)); - remote_store_->Listen(std::move(target_data)); + if (should_listen_to_remote) { + remote_store_->Listen(std::move(target_data)); + } return target_id; } @@ -161,22 +163,48 @@ ViewSnapshot SyncEngine::InitializeViewAndComputeSnapshot( return view_change.snapshot().value(); } -void SyncEngine::StopListening(const Query& query) { +void SyncEngine::ListenToRemoteStore(Query query) { + AssertCallbackExists("ListenToRemoteStore"); + TargetData target_data = local_store_->AllocateTarget(query.ToTarget()); + remote_store_->Listen(std::move(target_data)); +} + +void SyncEngine::StopListening(const Query& query, + bool should_stop_remote_listening) { AssertCallbackExists("StopListening"); + StopListeningAndReleaseTarget(query, /** last_listen= */ true, + should_stop_remote_listening); +} + +void SyncEngine::StopListeningToRemoteStoreOnly(const Query& query) { + AssertCallbackExists("StopListeningToRemoteStoreOnly"); + StopListeningAndReleaseTarget(query, /** last_listen= */ false, + /** should_stop_remote_listening= */ true); +} +void SyncEngine::StopListeningAndReleaseTarget( + const Query& query, bool last_listen, bool should_stop_remote_listening) { auto query_view = query_views_by_query_[query]; HARD_ASSERT(query_view, "Trying to stop listening to a query not found"); - query_views_by_query_.erase(query); + if (last_listen) { + query_views_by_query_.erase(query); + } + // One target could have multiple queries mapped to it. TargetId target_id = query_view->target_id(); auto& queries = queries_by_target_[target_id]; queries.erase(std::remove(queries.begin(), queries.end(), query), queries.end()); - if (queries.empty()) { - local_store_->ReleaseTarget(target_id); + if (!queries.empty()) return; + + if (should_stop_remote_listening) { remote_store_->StopListening(target_id); + } + + if (last_listen) { + local_store_->ReleaseTarget(target_id); RemoveAndCleanupTarget(target_id, Status::OK()); } } diff --git a/Firestore/core/src/core/sync_engine.h b/Firestore/core/src/core/sync_engine.h index e8bfc028d02..bcf930fdd0c 100644 --- a/Firestore/core/src/core/sync_engine.h +++ b/Firestore/core/src/core/sync_engine.h @@ -70,16 +70,33 @@ class QueryEventSource { /** * Initiates a new listen. The LocalStore will be queried for initial data - * and the listen will be sent to the `RemoteStore` to get remote data. The - * registered SyncEngineCallback will be notified of resulting view + * and the listen will be sent to the RemoteStore if the query is listening to + * watch. The registered SyncEngineCallback will be notified of resulting view * snapshots and/or listen errors. * * @return the target ID assigned to the query. */ - virtual model::TargetId Listen(Query query) = 0; + virtual model::TargetId Listen(Query query, bool should_listen_to_remote) = 0; - /** Stops listening to a query previously listened to via `Listen`. */ - virtual void StopListening(const Query& query) = 0; + /** + * Sends the listen to the RemoteStore to get remote data. Invoked when a + * Query starts listening to the remote store, while already listening to the + * cache. + */ + virtual void ListenToRemoteStore(Query query) = 0; + + /** + * Stops listening to a query previously listened to via `Listen`. Un-listen + * to remote store if there is a watch connection established and stayed open. + */ + virtual void StopListening(const Query& query, + bool should_stop_remote_listening) = 0; + + /** + * Stops listening to a query from watch. Invoked when a Query stops listening + * to the remote store, while still listening to the cache. + */ + virtual void StopListeningToRemoteStoreOnly(const Query& query) = 0; }; /** @@ -107,8 +124,12 @@ class SyncEngine : public remote::RemoteStoreCallback, public QueryEventSource { void SetCallback(SyncEngineCallback* callback) override { sync_engine_callback_ = callback; } - model::TargetId Listen(Query query) override; - void StopListening(const Query& query) override; + model::TargetId Listen(Query query, + bool should_listen_to_remote = true) override; + void ListenToRemoteStore(Query query) override; + void StopListening(const Query& query, + bool should_stop_remote_listening = true) override; + void StopListeningToRemoteStoreOnly(const Query& query) override; /** * Initiates the write of local mutation batch which involves adding the @@ -244,6 +265,9 @@ class SyncEngine : public remote::RemoteStoreCallback, public QueryEventSource { nanopb::ByteString resume_token); void RemoveAndCleanupTarget(model::TargetId target_id, util::Status status); + void StopListeningAndReleaseTarget(const Query& query, + bool should_stop_remote_listening, + bool last_listen); void RemoveLimboTarget(const model::DocumentKey& key); diff --git a/Firestore/core/src/credentials/firebase_auth_credentials_provider_apple.mm b/Firestore/core/src/credentials/firebase_auth_credentials_provider_apple.mm index c4452025e87..0760e524cb9 100644 --- a/Firestore/core/src/credentials/firebase_auth_credentials_provider_apple.mm +++ b/Firestore/core/src/credentials/firebase_auth_credentials_provider_apple.mm @@ -18,7 +18,7 @@ #import "FirebaseCore/Extension/FIRAppInternal.h" -#import "FirebaseAuth/Interop/FIRAuthInterop.h" +#import "FirebaseAuth/Interop/Public/FirebaseAuthInterop/FIRAuthInterop.h" #include "Firestore/core/src/util/error_apple.h" #include "Firestore/core/src/util/hard_assert.h" diff --git a/Firestore/core/src/immutable/keys_view.h b/Firestore/core/src/immutable/keys_view.h index fad8510cd59..c866ba5ef6c 100644 --- a/Firestore/core/src/immutable/keys_view.h +++ b/Firestore/core/src/immutable/keys_view.h @@ -47,8 +47,8 @@ auto KeysView(const Range& range) -> KeysRange { } template -auto KeysViewFrom(const Range& range, const K& key) - -> KeysRange { +auto KeysViewFrom(const Range& range, + const K& key) -> KeysRange { auto keys_begin = util::make_iterator_first(range.lower_bound(key)); auto keys_end = util::make_iterator_first(std::end(range)); return util::make_range(keys_begin, keys_end); diff --git a/Firestore/core/src/local/index_backfiller.cc b/Firestore/core/src/local/index_backfiller.cc index b0be8be8fdf..1991031d4c6 100644 --- a/Firestore/core/src/local/index_backfiller.cc +++ b/Firestore/core/src/local/index_backfiller.cc @@ -13,6 +13,7 @@ // limitations under the License. #include +#include #include #include @@ -44,7 +45,7 @@ IndexBackfiller::IndexBackfiller() { max_documents_to_process_ = kMaxDocumentsToProcess; } -int IndexBackfiller::WriteIndexEntries(const LocalStore* local_store) { +size_t IndexBackfiller::WriteIndexEntries(const LocalStore* local_store) { IndexManager* index_manager = local_store->index_manager(); std::unordered_set processed_collection_groups; size_t documents_remaining = max_documents_to_process_; @@ -64,10 +65,10 @@ int IndexBackfiller::WriteIndexEntries(const LocalStore* local_store) { return max_documents_to_process_ - documents_remaining; } -int IndexBackfiller::WriteEntriesForCollectionGroup( +size_t IndexBackfiller::WriteEntriesForCollectionGroup( const LocalStore* local_store, const std::string& collection_group, - int documents_remaining_under_cap) const { + size_t documents_remaining_under_cap) const { IndexManager* index_manager = local_store->index_manager(); const auto* const local_documents_view = local_store->local_documents(); diff --git a/Firestore/core/src/local/index_backfiller.h b/Firestore/core/src/local/index_backfiller.h index 5ebd3aa8014..46c3ef1468d 100644 --- a/Firestore/core/src/local/index_backfiller.h +++ b/Firestore/core/src/local/index_backfiller.h @@ -15,6 +15,7 @@ #ifndef FIRESTORE_CORE_SRC_LOCAL_INDEX_BACKFILLER_H_ #define FIRESTORE_CORE_SRC_LOCAL_INDEX_BACKFILLER_H_ +#include #include namespace firebase { @@ -43,7 +44,7 @@ class IndexBackfiller { * Writes index entries until the cap is reached. Returns the number of * documents processed. */ - int WriteIndexEntries(const LocalStore* local_store); + size_t WriteIndexEntries(const LocalStore* local_store); private: friend class IndexBackfillerTest; @@ -53,9 +54,10 @@ class IndexBackfiller { * Writes entries for the provided collection group. Returns the number of * documents processed. */ - int WriteEntriesForCollectionGroup(const LocalStore* local_store, - const std::string& collection_group, - int documents_remaining_under_cap) const; + size_t WriteEntriesForCollectionGroup( + const LocalStore* local_store, + const std::string& collection_group, + size_t documents_remaining_under_cap) const; /** Returns the next offset based on the provided documents. */ model::IndexOffset GetNewOffset(const model::IndexOffset& existing_offset, diff --git a/Firestore/core/src/local/local_documents_view.cc b/Firestore/core/src/local/local_documents_view.cc index b4e2cd78807..d3812e42a5f 100644 --- a/Firestore/core/src/local/local_documents_view.cc +++ b/Firestore/core/src/local/local_documents_view.cc @@ -134,7 +134,7 @@ model::DocumentMap LocalDocumentsView::GetDocumentsMatchingCollectionGroupQuery( LocalWriteResult LocalDocumentsView::GetNextDocuments( const std::string& collection_group, const IndexOffset& offset, - int count) const { + size_t count) const { auto docs = remote_document_cache_->GetAll(collection_group, offset, count); auto overlays = count - docs.size() > 0 ? document_overlay_cache_->GetOverlays( diff --git a/Firestore/core/src/local/local_documents_view.h b/Firestore/core/src/local/local_documents_view.h index 7fc12b378fa..549656dc44e 100644 --- a/Firestore/core/src/local/local_documents_view.h +++ b/Firestore/core/src/local/local_documents_view.h @@ -17,6 +17,7 @@ #ifndef FIRESTORE_CORE_SRC_LOCAL_LOCAL_DOCUMENTS_VIEW_H_ #define FIRESTORE_CORE_SRC_LOCAL_LOCAL_DOCUMENTS_VIEW_H_ +#include #include #include #include @@ -98,7 +99,7 @@ class LocalDocumentsView { */ local::LocalWriteResult GetNextDocuments(const std::string& collection_group, const model::IndexOffset& offset, - int count) const; + size_t count) const; /** * Similar to `GetDocuments`, but creates the local view from the given diff --git a/Firestore/core/src/model/value_util.cc b/Firestore/core/src/model/value_util.cc index 012c36955a7..61c4a8c865f 100644 --- a/Firestore/core/src/model/value_util.cc +++ b/Firestore/core/src/model/value_util.cc @@ -109,19 +109,22 @@ void SortFields(google_firestore_v1_ArrayValue& value) { } } +void SortFields(google_firestore_v1_MapValue& value) { + std::sort(value.fields, value.fields + value.fields_count, + [](const google_firestore_v1_MapValue_FieldsEntry& lhs, + const google_firestore_v1_MapValue_FieldsEntry& rhs) { + return nanopb::MakeStringView(lhs.key) < + nanopb::MakeStringView(rhs.key); + }); + + for (pb_size_t i = 0; i < value.fields_count; ++i) { + SortFields(value.fields[i].value); + } +} + void SortFields(google_firestore_v1_Value& value) { if (IsMap(value)) { - google_firestore_v1_MapValue& map_value = value.map_value; - std::sort(map_value.fields, map_value.fields + map_value.fields_count, - [](const google_firestore_v1_MapValue_FieldsEntry& lhs, - const google_firestore_v1_MapValue_FieldsEntry& rhs) { - return nanopb::MakeStringView(lhs.key) < - nanopb::MakeStringView(rhs.key); - }); - - for (pb_size_t i = 0; i < map_value.fields_count; ++i) { - SortFields(map_value.fields[i].value); - } + SortFields(value.map_value); } else if (IsArray(value)) { SortFields(value.array_value); } @@ -223,30 +226,31 @@ ComparisonResult CompareArrays(const google_firestore_v1_Value& left, right.array_value.values_count); } -ComparisonResult CompareObjects(const google_firestore_v1_Value& left, - const google_firestore_v1_Value& right) { - google_firestore_v1_MapValue left_map = left.map_value; - google_firestore_v1_MapValue right_map = right.map_value; +ComparisonResult CompareMaps(const google_firestore_v1_MapValue& left, + const google_firestore_v1_MapValue& right) { + // Sort the given MapValues + auto left_map = DeepClone(left); + auto right_map = DeepClone(right); + SortFields(*left_map); + SortFields(*right_map); - // Porting Note: MapValues in iOS are always kept in sorted order. We - // therefore do no need to sort them before comparing. - for (pb_size_t i = 0; i < left_map.fields_count && i < right_map.fields_count; - ++i) { - ComparisonResult key_cmp = - util::Compare(nanopb::MakeStringView(left_map.fields[i].key), - nanopb::MakeStringView(right_map.fields[i].key)); + for (pb_size_t i = 0; + i < left_map->fields_count && i < right_map->fields_count; ++i) { + const ComparisonResult key_cmp = + util::Compare(nanopb::MakeStringView(left_map->fields[i].key), + nanopb::MakeStringView(right_map->fields[i].key)); if (key_cmp != ComparisonResult::Same) { return key_cmp; } - ComparisonResult value_cmp = - Compare(left_map.fields[i].value, right.map_value.fields[i].value); + const ComparisonResult value_cmp = + Compare(left_map->fields[i].value, right_map->fields[i].value); if (value_cmp != ComparisonResult::Same) { return value_cmp; } } - return util::Compare(left_map.fields_count, right_map.fields_count); + return util::Compare(left_map->fields_count, right_map->fields_count); } ComparisonResult Compare(const google_firestore_v1_Value& left, @@ -291,7 +295,7 @@ ComparisonResult Compare(const google_firestore_v1_Value& left, return CompareArrays(left, right); case TypeOrder::kMap: - return CompareObjects(left, right); + return CompareMaps(left.map_value, right.map_value); case TypeOrder::kMaxValue: return util::ComparisonResult::Same; @@ -366,26 +370,12 @@ bool ArrayEquals(const google_firestore_v1_ArrayValue& left, return true; } -bool ObjectEquals(const google_firestore_v1_MapValue& left, - const google_firestore_v1_MapValue& right) { +bool MapValueEquals(const google_firestore_v1_MapValue& left, + const google_firestore_v1_MapValue& right) { if (left.fields_count != right.fields_count) { return false; } - - // Porting Note: MapValues in iOS are always kept in sorted order. We - // therefore do no need to sort them before comparing. - for (size_t i = 0; i < right.fields_count; ++i) { - if (nanopb::MakeStringView(left.fields[i].key) != - nanopb::MakeStringView(right.fields[i].key)) { - return false; - } - - if (left.fields[i].value != right.fields[i].value) { - return false; - } - } - - return true; + return CompareMaps(left, right) == ComparisonResult::Same; } bool Equals(const google_firestore_v1_Value& lhs, @@ -436,10 +426,10 @@ bool Equals(const google_firestore_v1_Value& lhs, return ArrayEquals(lhs.array_value, rhs.array_value); case TypeOrder::kMap: - return ObjectEquals(lhs.map_value, rhs.map_value); + return MapValueEquals(lhs.map_value, rhs.map_value); case TypeOrder::kMaxValue: - return ObjectEquals(lhs.map_value, rhs.map_value); + return MapValueEquals(lhs.map_value, rhs.map_value); default: HARD_FAIL("Invalid type value: %s", left_type); @@ -794,27 +784,11 @@ Message DeepClone( break; case google_firestore_v1_Value_array_value_tag: - target->array_value.values_count = source.array_value.values_count; - target->array_value.values = nanopb::MakeArray( - source.array_value.values_count); - for (pb_size_t i = 0; i < source.array_value.values_count; ++i) { - target->array_value.values[i] = - *DeepClone(source.array_value.values[i]).release(); - } + target->array_value = *DeepClone(source.array_value).release(); break; case google_firestore_v1_Value_map_value_tag: - target->map_value.fields_count = source.map_value.fields_count; - target->map_value.fields = - nanopb::MakeArray( - source.map_value.fields_count); - for (pb_size_t i = 0; i < source.map_value.fields_count; ++i) { - target->map_value.fields[i].key = - nanopb::MakeBytesArray(source.map_value.fields[i].key->bytes, - source.map_value.fields[i].key->size); - target->map_value.fields[i].value = - *DeepClone(source.map_value.fields[i].value).release(); - } + target->map_value = *DeepClone(source.map_value).release(); break; } return target; @@ -832,6 +806,20 @@ Message DeepClone( return target; } +Message DeepClone( + const google_firestore_v1_MapValue& source) { + Message target{source}; + target->fields_count = source.fields_count; + target->fields = nanopb::MakeArray( + source.fields_count); + for (pb_size_t i = 0; i < source.fields_count; ++i) { + target->fields[i].key = nanopb::MakeBytesArray(source.fields[i].key->bytes, + source.fields[i].key->size); + target->fields[i].value = *DeepClone(source.fields[i].value).release(); + } + return target; +} + } // namespace model } // namespace firestore } // namespace firebase diff --git a/Firestore/core/src/model/value_util.h b/Firestore/core/src/model/value_util.h index c39fc8da39b..91e26a21ebb 100644 --- a/Firestore/core/src/model/value_util.h +++ b/Firestore/core/src/model/value_util.h @@ -183,6 +183,10 @@ nanopb::Message DeepClone( nanopb::Message DeepClone( const google_firestore_v1_ArrayValue& source); +/** Creates a copy of the contents of the MapValue proto. */ +nanopb::Message DeepClone( + const google_firestore_v1_MapValue& source); + /** Returns true if `value` is a INTEGER_VALUE. */ inline bool IsInteger(const absl::optional& value) { return value && diff --git a/Firestore/core/src/remote/bloom_filter.cc b/Firestore/core/src/remote/bloom_filter.cc index ddf84b866e9..0e4c8e04f34 100644 --- a/Firestore/core/src/remote/bloom_filter.cc +++ b/Firestore/core/src/remote/bloom_filter.cc @@ -78,7 +78,7 @@ int32_t BloomFilter::GetBitIndex(const Hash& hash, int32_t hash_index) const { uint64_t bit_index = combined_hash % bit_count_uint64; HARD_ASSERT(bit_index <= INT32_MAX); - return bit_index; + return static_cast(bit_index); } bool BloomFilter::IsBitSet(int32_t index) const { diff --git a/Firestore/core/src/util/filesystem_apple.mm b/Firestore/core/src/util/filesystem_apple.mm index 761e37e0f1d..9802017a6f8 100644 --- a/Firestore/core/src/util/filesystem_apple.mm +++ b/Firestore/core/src/util/filesystem_apple.mm @@ -109,6 +109,37 @@ return Status::OK(); } +StatusOr Filesystem::FileSize(const Path& path) { + NSFileManager* file_manager = NSFileManager.defaultManager; + NSString* ns_path_str = path.ToNSString(); + NSError* error = nil; + + NSDictionary* attributes = [file_manager attributesOfItemAtPath:ns_path_str + error:&error]; + + if (attributes == nil) { + if ([error.domain isEqualToString:NSCocoaErrorDomain]) { + switch (error.code) { + case NSFileReadNoSuchFileError: + case NSFileNoSuchFileError: + return Status{Error::kErrorNotFound, path.ToUtf8String()}.CausedBy( + Status::FromNSError(error)); + } + } + + return Status{Error::kErrorInternal, + StringFormat("attributesOfItemAtPath failed for %s", + path.ToUtf8String())} + .CausedBy(Status::FromNSError(error)); + } + + NSNumber* fileSizeNumber = [attributes objectForKey:NSFileSize]; + + // Use brace initialization of the in64_t return value so that compilation + // will fail if the conversion from long long is narrowing. + return {[fileSizeNumber longLongValue]}; +} + } // namespace util } // namespace firestore } // namespace firebase diff --git a/Firestore/core/src/util/filesystem_posix.cc b/Firestore/core/src/util/filesystem_posix.cc index 56c008a8d06..8688ce5897c 100644 --- a/Firestore/core/src/util/filesystem_posix.cc +++ b/Firestore/core/src/util/filesystem_posix.cc @@ -166,7 +166,6 @@ Status Filesystem::IsDirectory(const Path& path) { return Status::OK(); } -#endif // !__APPLE__ StatusOr Filesystem::FileSize(const Path& path) { struct stat st {}; @@ -177,6 +176,7 @@ StatusOr Filesystem::FileSize(const Path& path) { errno, StringFormat("Failed to stat file: %s", path.ToUtf8String())); } } +#endif // !__APPLE__ Status Filesystem::CreateDir(const Path& path) { if (::mkdir(path.c_str(), 0777)) { diff --git a/Firestore/core/src/util/hashing.h b/Firestore/core/src/util/hashing.h index 7286c44cc32..33375f03a63 100644 --- a/Firestore/core/src/util/hashing.h +++ b/Firestore/core/src/util/hashing.h @@ -190,8 +190,8 @@ auto RankedInvokeHash(const Range& range, HashChoice<3>) * value can itself be hashed. */ template -auto RankedInvokeHash(const absl::optional& option, HashChoice<4>) - -> decltype(InvokeHash(*option)) { +auto RankedInvokeHash(const absl::optional& option, + HashChoice<4>) -> decltype(InvokeHash(*option)) { return option ? InvokeHash(*option) : -1171; } @@ -202,8 +202,8 @@ size_t RankedInvokeHash(K value, HashChoice<5>) { } template -auto RankedInvokeHash(const std::unique_ptr& ptr, HashChoice<6>) - -> decltype(InvokeHash(*ptr)) { +auto RankedInvokeHash(const std::unique_ptr& ptr, + HashChoice<6>) -> decltype(InvokeHash(*ptr)) { return ptr ? InvokeHash(*ptr) : 23631; } diff --git a/Firestore/core/test/unit/FSTGoogleTestTests.mm b/Firestore/core/test/unit/FSTGoogleTestTests.mm index 244875a1614..f0120857d76 100644 --- a/Firestore/core/test/unit/FSTGoogleTestTests.mm +++ b/Firestore/core/test/unit/FSTGoogleTestTests.mm @@ -139,7 +139,7 @@ @interface GoogleTests : XCTestCase * These members are then joined with a ":" as googletest requires. * * @see - * https://github.com/google/googletest/blob/master/googletest/docs/AdvancedGuide.md + * https://github.com/google/googletest/blob/main/docs/advanced.md */ NSString* CreateTestFiltersFromTestsToRun(NSSet* testsToRun) { NSMutableString* result = [[NSMutableString alloc] init]; diff --git a/Firestore/core/test/unit/bundle/bundle_reader_test.cc b/Firestore/core/test/unit/bundle/bundle_reader_test.cc index 54ddfc9f1d9..61b3f744845 100644 --- a/Firestore/core/test/unit/bundle/bundle_reader_test.cc +++ b/Firestore/core/test/unit/bundle/bundle_reader_test.cc @@ -231,10 +231,13 @@ class BundleReaderTest : public ::testing::Test { value3.set_null_value(google::protobuf::NULL_VALUE); ProtoValue value4; value4.mutable_array_value(); + ProtoValue value5; + value5.mutable_map_value(); document.mutable_fields()->insert({"\0\ud7ff\ue000\uffff\"", value1}); document.mutable_fields()->insert({"\"(╯°□°)╯︵ ┻━┻\"", value2}); document.mutable_fields()->insert({"nValue", value3}); document.mutable_fields()->insert({"emptyArray", value4}); + document.mutable_fields()->insert({"emptyMap", value5}); return document; } diff --git a/Firestore/core/test/unit/core/event_manager_test.cc b/Firestore/core/test/unit/core/event_manager_test.cc index 9229dae919e..5d48a9aae0c 100644 --- a/Firestore/core/test/unit/core/event_manager_test.cc +++ b/Firestore/core/test/unit/core/event_manager_test.cc @@ -57,11 +57,21 @@ std::shared_ptr NoopQueryListener(core::Query query) { NoopViewSnapshotHandler()); } +std::shared_ptr NoopQueryCacheListener(core::Query query) { + return QueryListener::Create( + std::move(query), + ListenOptions::FromOptions(/** include_metadata_changes= */ false, + ListenSource::Cache), + NoopViewSnapshotHandler()); +} + class MockEventSource : public core::QueryEventSource { public: MOCK_METHOD1(SetCallback, void(core::SyncEngineCallback*)); - MOCK_METHOD1(Listen, model::TargetId(core::Query)); - MOCK_METHOD1(StopListening, void(const core::Query&)); + MOCK_METHOD2(Listen, model::TargetId(core::Query, bool)); + MOCK_METHOD1(ListenToRemoteStore, void(core::Query)); + MOCK_METHOD2(StopListening, void(const core::Query&, bool)); + MOCK_METHOD1(StopListeningToRemoteStoreOnly, void(const core::Query&)); }; TEST(EventManagerTest, HandlesManyListnersPerQuery) { @@ -73,14 +83,34 @@ TEST(EventManagerTest, HandlesManyListnersPerQuery) { EXPECT_CALL(mock_event_source, SetCallback(_)); EventManager event_manager(&mock_event_source); - EXPECT_CALL(mock_event_source, Listen(query)); + EXPECT_CALL(mock_event_source, Listen(query, true)); + event_manager.AddQueryListener(listener1); + + // Expecting no activity from mock_event_source. + event_manager.AddQueryListener(listener2); + event_manager.RemoveQueryListener(listener2); + + EXPECT_CALL(mock_event_source, StopListening(query, true)); + event_manager.RemoveQueryListener(listener1); +} + +TEST(EventManagerTest, HandlesManyCacheListnersPerQuery) { + core::Query query = Query("foo/bar"); + auto listener1 = NoopQueryCacheListener(query); + auto listener2 = NoopQueryCacheListener(query); + + StrictMock mock_event_source; + EXPECT_CALL(mock_event_source, SetCallback(_)); + EventManager event_manager(&mock_event_source); + + EXPECT_CALL(mock_event_source, Listen(query, false)); event_manager.AddQueryListener(listener1); // Expecting no activity from mock_event_source. event_manager.AddQueryListener(listener2); event_manager.RemoveQueryListener(listener2); - EXPECT_CALL(mock_event_source, StopListening(query)); + EXPECT_CALL(mock_event_source, StopListening(query, false)); event_manager.RemoveQueryListener(listener1); } @@ -91,7 +121,7 @@ TEST(EventManagerTest, HandlesUnlistenOnUnknownListenerGracefully) { MockEventSource mock_event_source; EventManager event_manager(&mock_event_source); - EXPECT_CALL(mock_event_source, StopListening(_)).Times(0); + EXPECT_CALL(mock_event_source, StopListening(_, true)).Times(0); event_manager.RemoveQueryListener(listener); } @@ -128,10 +158,10 @@ TEST(EventManagerTest, NotifiesListenersInTheRightOrder) { MockEventSource mock_event_source; EventManager event_manager(&mock_event_source); - EXPECT_CALL(mock_event_source, Listen(query1)); + EXPECT_CALL(mock_event_source, Listen(query1, true)); event_manager.AddQueryListener(listener1); - EXPECT_CALL(mock_event_source, Listen(query2)); + EXPECT_CALL(mock_event_source, Listen(query2, true)); event_manager.AddQueryListener(listener2); event_manager.AddQueryListener(listener3); diff --git a/Firestore/core/test/unit/credentials/firebase_auth_credentials_provider_test.mm b/Firestore/core/test/unit/credentials/firebase_auth_credentials_provider_test.mm index c1e190f439e..4c93cd51163 100644 --- a/Firestore/core/test/unit/credentials/firebase_auth_credentials_provider_test.mm +++ b/Firestore/core/test/unit/credentials/firebase_auth_credentials_provider_test.mm @@ -20,7 +20,7 @@ #include // NOLINT(build/c++11) #include -#import "FirebaseAuth/Interop/FIRAuthInterop.h" +#import "FirebaseAuth/Interop/Public/FirebaseAuthInterop/FIRAuthInterop.h" #include "Firestore/core/src/util/statusor.h" #include "Firestore/core/src/util/string_apple.h" diff --git a/Firestore/core/test/unit/model/value_util_test.cc b/Firestore/core/test/unit/model/value_util_test.cc index 50ee1b1add2..d4db43dfe20 100644 --- a/Firestore/core/test/unit/model/value_util_test.cc +++ b/Firestore/core/test/unit/model/value_util_test.cc @@ -545,6 +545,24 @@ TEST_F(ValueUtilTest, DeepClone) { VerifyDeepClone(Map("a", Array("b", Map("c", GeoPoint(30, 60))))); } +TEST_F(ValueUtilTest, CompareMaps) { + auto left_1 = Map("a", 7, "b", 0); + auto right_1 = Map("a", 7, "b", 0); + EXPECT_EQ(model::Compare(*left_1, *right_1), ComparisonResult::Same); + + auto left_2 = Map("a", 3, "b", 5); + auto right_2 = Map("b", 5, "a", 3); + EXPECT_EQ(model::Compare(*left_2, *right_2), ComparisonResult::Same); + + auto left_3 = Map("a", 8, "b", 10, "c", 5); + auto right_3 = Map("a", 8, "b", 10); + EXPECT_EQ(model::Compare(*left_3, *right_3), ComparisonResult::Descending); + + auto left_4 = Map("a", 7, "b", 0); + auto right_4 = Map("a", 7, "b", 10); + EXPECT_EQ(model::Compare(*left_4, *right_4), ComparisonResult::Ascending); +} + } // namespace } // namespace model diff --git a/Firestore/core/test/unit/nanopb/message_test.cc b/Firestore/core/test/unit/nanopb/message_test.cc index 78489edbbad..77549e80857 100644 --- a/Firestore/core/test/unit/nanopb/message_test.cc +++ b/Firestore/core/test/unit/nanopb/message_test.cc @@ -25,7 +25,6 @@ #include "Firestore/core/src/nanopb/writer.h" #include "Firestore/core/src/remote/grpc_nanopb.h" #include "Firestore/core/test/unit/testutil/status_testing.h" -#include "grpcpp/impl/codegen/grpc_library.h" #include "grpcpp/support/byte_buffer.h" #include "gtest/gtest.h" @@ -60,12 +59,6 @@ class MessageTest : public testing::Test { grpc::ByteBuffer BadProto() const { return {}; } - - private: - // Note: gRPC slice will crash upon destruction if gRPC library hasn't been - // initialized, which is normally done by inheriting from this class (which - // does initialization in its constructor). - grpc::GrpcLibraryCodegen grpc_initializer_; }; #if !__clang_analyzer__ diff --git a/Firestore/core/test/unit/util/to_string_test.cc b/Firestore/core/test/unit/util/to_string_test.cc index a5b38d177c9..9f4a6f58a37 100644 --- a/Firestore/core/test/unit/util/to_string_test.cc +++ b/Firestore/core/test/unit/util/to_string_test.cc @@ -49,8 +49,9 @@ TEST(ToStringTest, SimpleTypes) { EXPECT_EQ(ToString(nullptr), "null"); - void* ptr = reinterpret_cast(0xBAAAAAAD); - EXPECT_EQ(ToString(ptr), "baaaaaad"); + // TODO(b/326402002): Below no longer passes after abseil upgrade + // to 1.20240116.1 void* ptr = reinterpret_cast(0xBAAAAAAD); + // EXPECT_EQ(ToString(ptr), "baaaaaad"); } TEST(ToStringTest, CustomToString) { diff --git a/Gemfile b/Gemfile index 77136b400b9..662939268b3 100644 --- a/Gemfile +++ b/Gemfile @@ -12,6 +12,6 @@ source 'https://rubygems.org' # gem 'cocoapods-core', git: "https://github.com/CocoaPods/Core.git", ref: "f7cf05720eab935d7d50e35224d263952176fb53" # gem 'xcodeproj', git: "https://github.com/CocoaPods/Xcodeproj.git", ref: "eeccae7275645753cbaf45d96fc4b23e4b8b3b9f" -gem 'cocoapods', '1.14.3' +gem 'cocoapods', '1.15.2' gem 'cocoapods-generate', '2.2.5' gem 'danger', '8.4.5' diff --git a/Gemfile.lock b/Gemfile.lock index 3d7e1067b67..d09fdd38cce 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,7 +3,7 @@ GEM specs: CFPropertyList (3.0.6) rexml - activesupport (7.1.2) + activesupport (7.1.3) base64 bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) @@ -20,16 +20,16 @@ GEM json (>= 1.5.1) atomos (0.1.3) base64 (0.2.0) - bigdecimal (3.1.4) + bigdecimal (3.1.6) claide (1.0.3) claide-plugins (0.9.2) cork nap open4 (~> 1.3) - cocoapods (1.14.3) + cocoapods (1.15.2) addressable (~> 2.8) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.14.3) + cocoapods-core (= 1.15.2) cocoapods-deintegrate (>= 1.0.3, < 2.0) cocoapods-downloader (>= 2.1, < 3.0) cocoapods-plugins (>= 1.0.0, < 2.0) @@ -44,7 +44,7 @@ GEM nap (~> 1.0) ruby-macho (>= 2.3.0, < 3.0) xcodeproj (>= 1.23.0, < 2.0) - cocoapods-core (1.14.3) + cocoapods-core (1.15.2) activesupport (>= 5.0, < 8) addressable (~> 2.8) algoliasearch (~> 1.0) @@ -67,7 +67,7 @@ GEM netrc (~> 0.11) cocoapods-try (1.2.0) colored2 (3.1.2) - concurrent-ruby (1.2.2) + concurrent-ruby (1.2.3) connection_pool (2.4.1) cork (0.3.0) colored2 (~> 3.1) @@ -124,12 +124,12 @@ GEM httpclient (2.8.3) i18n (1.14.1) concurrent-ruby (~> 1.0) - json (2.6.3) + json (2.7.1) kramdown (2.3.1) rexml kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) - minitest (5.20.0) + minitest (5.22.0) molinillo (0.8.0) multipart-post (2.1.1) mutex_m (0.2.0) @@ -156,7 +156,7 @@ GEM tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (2.1.0) - xcodeproj (1.23.0) + xcodeproj (1.24.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) @@ -168,7 +168,7 @@ PLATFORMS ruby DEPENDENCIES - cocoapods (= 1.14.3) + cocoapods (= 1.15.2) cocoapods-generate (= 2.2.5) danger (= 8.4.5) diff --git a/GoogleAppMeasurement.podspec b/GoogleAppMeasurement.podspec index f152f72c0b0..2a3eec8af1e 100644 --- a/GoogleAppMeasurement.podspec +++ b/GoogleAppMeasurement.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GoogleAppMeasurement' - s.version = '10.21.0' + s.version = '10.23.0' s.summary = 'Shared measurement methods for Google libraries. Not intended for direct use.' s.description = <<-DESC @@ -16,10 +16,10 @@ Pod::Spec.new do |s| s.authors = 'Google, Inc.' s.source = { - :http => 'https://dl.google.com/firebase/ios/analytics/3fcc7b954e5d5458/GoogleAppMeasurement-10.20.0.tar.gz' + :http => 'https://dl.google.com/firebase/ios/analytics/0019598badd9f3d1/GoogleAppMeasurement-10.23.0.tar.gz' } - s.cocoapods_version = '>= 1.10.2' + s.cocoapods_version = '>= 1.12.0' s.ios.deployment_target = '10.0' s.osx.deployment_target = '10.13' @@ -32,12 +32,12 @@ Pod::Spec.new do |s| s.dependency 'GoogleUtilities/MethodSwizzler', '~> 7.11' s.dependency 'GoogleUtilities/NSData+zlib', '~> 7.11' s.dependency 'GoogleUtilities/Network', '~> 7.11' - s.dependency 'nanopb', '>= 2.30908.0', '< 2.30910.0' + s.dependency 'nanopb', '>= 2.30908.0', '< 2.30911.0' s.default_subspecs = 'AdIdSupport' s.subspec 'AdIdSupport' do |ss| - ss.dependency 'GoogleAppMeasurement/WithoutAdIdSupport', '10.21.0' + ss.dependency 'GoogleAppMeasurement/WithoutAdIdSupport', '10.23.0' ss.vendored_frameworks = 'Frameworks/GoogleAppMeasurementIdentitySupport.xcframework' end diff --git a/GoogleAppMeasurementOnDeviceConversion.podspec b/GoogleAppMeasurementOnDeviceConversion.podspec index 7cf396083be..717e14fb888 100644 --- a/GoogleAppMeasurementOnDeviceConversion.podspec +++ b/GoogleAppMeasurementOnDeviceConversion.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'GoogleAppMeasurementOnDeviceConversion' - s.version = '10.21.0' + s.version = '10.23.0' s.summary = <<-SUMMARY On device conversion measurement plugin for Google App Measurement. Not intended for direct use. @@ -17,10 +17,10 @@ Pod::Spec.new do |s| s.authors = 'Google, Inc.' s.source = { - :http => 'https://dl.google.com/firebase/ios/analytics/4ab453c686c6aac4/GoogleAppMeasurementOnDeviceConversion-10.20.0.tar.gz' + :http => 'https://dl.google.com/firebase/ios/analytics/c696679c30b9561e/GoogleAppMeasurementOnDeviceConversion-10.22.1.tar.gz' } - s.cocoapods_version = '>= 1.10.2' + s.cocoapods_version = '>= 1.12.0' s.ios.deployment_target = '10.0' diff --git a/GoogleUtilitiesComponents.podspec b/GoogleUtilitiesComponents.podspec index 218e4e3fafb..b52edba3ff5 100644 --- a/GoogleUtilitiesComponents.podspec +++ b/GoogleUtilitiesComponents.podspec @@ -22,7 +22,7 @@ Not intended for direct public usage. s.osx.deployment_target = '10.13' s.tvos.deployment_target = '12.0' - s.cocoapods_version = '>= 1.4.0' + s.cocoapods_version = '>= 1.12.0' s.prefix_header_file = false s.static_framework = true diff --git a/IntegrationTesting/ClientApp/ClientApp.xcodeproj/project.pbxproj b/IntegrationTesting/ClientApp/ClientApp.xcodeproj/project.pbxproj index 2f426d10b8d..4fbcd304dab 100644 --- a/IntegrationTesting/ClientApp/ClientApp.xcodeproj/project.pbxproj +++ b/IntegrationTesting/ClientApp/ClientApp.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + DE305B702B7BE0B5000595B3 /* FirebaseStorage in Frameworks */ = {isa = PBXBuildFile; productRef = DE305B6F2B7BE0B5000595B3 /* FirebaseStorage */; }; DE99626B2B44C96C0038ED6B /* objc-module-import-test.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9962682B44C96B0038ED6B /* objc-module-import-test.m */; }; DE99626C2B44C96C0038ED6B /* objc-module-import-test.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9962682B44C96B0038ED6B /* objc-module-import-test.m */; }; DE99626D2B44C96C0038ED6B /* objcxx-header-import-test.mm in Sources */ = {isa = PBXBuildFile; fileRef = DE9962692B44C96B0038ED6B /* objcxx-header-import-test.mm */; }; @@ -50,7 +51,6 @@ EA7DF5AF29EF3328005664A7 /* FirebasePerformance in Frameworks */ = {isa = PBXBuildFile; platformFilters = (ios, tvos, ); productRef = EA7DF5AE29EF3328005664A7 /* FirebasePerformance */; }; EA7DF5B129EF3328005664A7 /* FirebaseRemoteConfig in Frameworks */ = {isa = PBXBuildFile; productRef = EA7DF5B029EF3328005664A7 /* FirebaseRemoteConfig */; }; EA7DF5B329EF3328005664A7 /* FirebaseRemoteConfigSwift in Frameworks */ = {isa = PBXBuildFile; productRef = EA7DF5B229EF3328005664A7 /* FirebaseRemoteConfigSwift */; }; - EA7DF5B529EF3328005664A7 /* FirebaseStorage in Frameworks */ = {isa = PBXBuildFile; productRef = EA7DF5B429EF3328005664A7 /* FirebaseStorage */; }; EA7DF5B729EF3328005664A7 /* FirebaseStorageCombine-Community in Frameworks */ = {isa = PBXBuildFile; productRef = EA7DF5B629EF3328005664A7 /* FirebaseStorageCombine-Community */; }; EAA0A99A2AD8495000C28FCD /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EAA0A9992AD8495000C28FCD /* Preview Assets.xcassets */; }; EAA0A9A52AD849E600C28FCD /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA1269B329EDF98800D79E66 /* AppDelegate.swift */; }; @@ -119,7 +119,6 @@ EA7DF5A129EF3327005664A7 /* FirebaseFunctions in Frameworks */, EA7DF58D29EF3326005664A7 /* FirebaseAppDistribution-Beta in Frameworks */, EA7DF5AF29EF3328005664A7 /* FirebasePerformance in Frameworks */, - EA7DF5B529EF3328005664A7 /* FirebaseStorage in Frameworks */, EA7DF5A729EF3327005664A7 /* FirebaseInAppMessagingSwift-Beta in Frameworks */, EA7DF59929EF3326005664A7 /* FirebaseDynamicLinks in Frameworks */, EA7DF59D29EF3326005664A7 /* FirebaseFirestoreCombine-Community in Frameworks */, @@ -149,6 +148,7 @@ EABBCF6F2B45B46500232BAF /* FirebaseAuthCombine-Community in Frameworks */, EABBCF6D2B45B44100232BAF /* FirebaseAuth in Frameworks */, EAA0A9C32AD84E5600C28FCD /* FirebaseInAppMessagingSwift-Beta in Frameworks */, + DE305B702B7BE0B5000595B3 /* FirebaseStorage in Frameworks */, EAA0A9C12AD84E5600C28FCD /* FirebaseInAppMessaging-Beta in Frameworks */, EAA0A9C52AD84E5D00C28FCD /* FirebaseAnalytics in Frameworks */, ); @@ -157,6 +157,13 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + DE305B6E2B7BE0B5000595B3 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; EA1269A729EDF98800D79E66 = { isa = PBXGroup; children = ( @@ -169,7 +176,7 @@ EAA0A9902AD8494F00C28FCD /* ClientApp-CocoaPods-iOS13 */, EAA0A9B22AD84E0800C28FCD /* ClientApp-iOS13 */, EA1269B129EDF98800D79E66 /* Products */, - EABBCF6B2B45B44100232BAF /* Frameworks */, + DE305B6E2B7BE0B5000595B3 /* Frameworks */, ); sourceTree = ""; }; @@ -291,13 +298,6 @@ path = "Preview Content"; sourceTree = ""; }; - EABBCF6B2B45B44100232BAF /* Frameworks */ = { - isa = PBXGroup; - children = ( - ); - name = Frameworks; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -337,7 +337,6 @@ EA7DF5AE29EF3328005664A7 /* FirebasePerformance */, EA7DF5B029EF3328005664A7 /* FirebaseRemoteConfig */, EA7DF5B229EF3328005664A7 /* FirebaseRemoteConfigSwift */, - EA7DF5B429EF3328005664A7 /* FirebaseStorage */, EA7DF5B629EF3328005664A7 /* FirebaseStorageCombine-Community */, EA0BC0FE29F06D5B005B8AEE /* FirebaseAnalyticsOnDeviceConversion */, ); @@ -399,6 +398,7 @@ EAA0A9C62AD84E5D00C28FCD /* FirebaseAnalyticsSwift */, EABBCF6C2B45B44100232BAF /* FirebaseAuth */, EABBCF6E2B45B46500232BAF /* FirebaseAuthCombine-Community */, + DE305B6F2B7BE0B5000595B3 /* FirebaseStorage */, ); productName = "ClientApp-iOS13"; productReference = EAA0A9B12AD84E0800C28FCD /* ClientApp-iOS13.app */; @@ -1026,6 +1026,10 @@ /* End XCConfigurationList section */ /* Begin XCSwiftPackageProductDependency section */ + DE305B6F2B7BE0B5000595B3 /* FirebaseStorage */ = { + isa = XCSwiftPackageProductDependency; + productName = FirebaseStorage; + }; EA0BC0FE29F06D5B005B8AEE /* FirebaseAnalyticsOnDeviceConversion */ = { isa = XCSwiftPackageProductDependency; productName = FirebaseAnalyticsOnDeviceConversion; @@ -1118,10 +1122,6 @@ isa = XCSwiftPackageProductDependency; productName = FirebaseRemoteConfigSwift; }; - EA7DF5B429EF3328005664A7 /* FirebaseStorage */ = { - isa = XCSwiftPackageProductDependency; - productName = FirebaseStorage; - }; EA7DF5B629EF3328005664A7 /* FirebaseStorageCombine-Community */ = { isa = XCSwiftPackageProductDependency; productName = "FirebaseStorageCombine-Community"; diff --git a/IntegrationTesting/ClientApp/Podfile b/IntegrationTesting/ClientApp/Podfile index 0f00ce5c80a..bf3bb89c9c6 100644 --- a/IntegrationTesting/ClientApp/Podfile +++ b/IntegrationTesting/ClientApp/Podfile @@ -8,6 +8,8 @@ target 'ClientApp-CocoaPods' do use_frameworks! pod 'FirebaseCore', :path => '../../' + pod 'FirebaseCoreInternal', :path => '../../' + pod 'FirebaseCoreExtension', :path => '../../' pod 'FirebaseInstallations', :path => '../../' pod 'FirebaseAnalytics' # Binary pods don't work with `:path`. pod 'FirebaseAnalyticsOnDeviceConversion', :path => '../../' @@ -15,6 +17,7 @@ target 'ClientApp-CocoaPods' do pod 'FirebaseAppCheck', :path => '../../' pod 'FirebaseRemoteConfig', :path => '../../' pod 'FirebaseRemoteConfigSwift', :path => '../../' + pod 'FirebaseRemoteConfigInterop', :path => '../../' pod 'FirebaseAppDistribution', :path => '../../' pod 'FirebaseCrashlytics', :path => '../../' pod 'FirebaseDatabase', :path => '../../' @@ -26,7 +29,6 @@ target 'ClientApp-CocoaPods' do pod 'FirebaseInAppMessaging', :path => '../../' pod 'FirebaseMessaging', :path => '../../' pod 'FirebasePerformance', :path => '../../' - pod 'FirebaseStorage', :path => '../../' pod 'FirebaseMLModelDownloader', :path => '../../' pod 'Firebase', :path => '../../' end @@ -39,7 +41,8 @@ target 'ClientApp-CocoaPods-iOS13' do pod 'FirebaseAnalytics' # Binary pods don't work with `:path`. pod 'FirebaseAnalyticsSwift', :path => '../../' # Requires iOS 13.0+ pod 'FirebaseAuth', :path => '../../' # Requires iOS 13.0+ + pod 'FirebaseAuthInterop', :path => '../../' pod 'FirebaseInAppMessaging', :path => '../../' pod 'FirebaseInAppMessagingSwift', :path => '../../' # Requires iOS 13.0+ - + pod 'FirebaseStorage', :path => '../../' end diff --git a/IntegrationTesting/ClientApp/Shared-iOS12+/objc-module-import-test.m b/IntegrationTesting/ClientApp/Shared-iOS12+/objc-module-import-test.m index 4339ad22252..2d7baf881a7 100644 --- a/IntegrationTesting/ClientApp/Shared-iOS12+/objc-module-import-test.m +++ b/IntegrationTesting/ClientApp/Shared-iOS12+/objc-module-import-test.m @@ -39,4 +39,3 @@ @import FirebaseInAppMessaging; #endif @import FirebaseRemoteConfig; -@import FirebaseStorage; diff --git a/IntegrationTesting/ClientApp/Shared-iOS12+/swift-import-test.swift b/IntegrationTesting/ClientApp/Shared-iOS12+/swift-import-test.swift index 0c5dc24395f..35eacac276f 100644 --- a/IntegrationTesting/ClientApp/Shared-iOS12+/swift-import-test.swift +++ b/IntegrationTesting/ClientApp/Shared-iOS12+/swift-import-test.swift @@ -50,7 +50,6 @@ import FirebaseMLModelDownloader #endif import FirebaseRemoteConfig import FirebaseRemoteConfigSwift -import FirebaseStorage #if SWIFT_PACKAGE import FirebaseStorageCombineSwift #endif // SWIFT_PACKAGE diff --git a/IntegrationTesting/ClientApp/Shared-iOS13+/objc-header-import-test.m b/IntegrationTesting/ClientApp/Shared-iOS13+/objc-header-import-test.m index c0a1b20a26e..cea08137cea 100644 --- a/IntegrationTesting/ClientApp/Shared-iOS13+/objc-header-import-test.m +++ b/IntegrationTesting/ClientApp/Shared-iOS13+/objc-header-import-test.m @@ -31,3 +31,15 @@ #import #import "FirebaseInAppMessaging/FirebaseInAppMessaging.h" #endif +#ifdef COCOAPODS +#import "FirebaseStorage/FIRStorageTypedefs.h" + +@interface TestImports : NSObject +@end + +@implementation TestImports +- (FIRAuth *)testImports { + return [FIRAuth auth]; +} +@end +#endif diff --git a/IntegrationTesting/ClientApp/Shared-iOS13+/objc-module-import-test.m b/IntegrationTesting/ClientApp/Shared-iOS13+/objc-module-import-test.m index 908313f9651..7aea74bd865 100644 --- a/IntegrationTesting/ClientApp/Shared-iOS13+/objc-module-import-test.m +++ b/IntegrationTesting/ClientApp/Shared-iOS13+/objc-module-import-test.m @@ -25,3 +25,4 @@ #if (TARGET_OS_IOS && !TARGET_OS_MACCATALYST) || TARGET_OS_TV @import FirebaseInAppMessaging; #endif +@import FirebaseStorage; diff --git a/IntegrationTesting/ClientApp/Shared-iOS13+/objcxx-header-import-test.mm b/IntegrationTesting/ClientApp/Shared-iOS13+/objcxx-header-import-test.mm index 13415ff9e52..0f073b2e12a 100644 --- a/IntegrationTesting/ClientApp/Shared-iOS13+/objcxx-header-import-test.mm +++ b/IntegrationTesting/ClientApp/Shared-iOS13+/objcxx-header-import-test.mm @@ -31,3 +31,16 @@ #import #import "FirebaseInAppMessaging/FirebaseInAppMessaging.h" #endif + +#ifdef COCOAPODS +#import "FirebaseStorage/FIRStorageTypedefs.h" + +@interface TestImportsCxx : NSObject +@end + +@implementation TestImportsCxx +- (FIRAuth *)testImports { + return [FIRAuth auth]; +} +@end +#endif diff --git a/IntegrationTesting/ClientApp/Shared-iOS13+/swift-import-test.swift b/IntegrationTesting/ClientApp/Shared-iOS13+/swift-import-test.swift index 2007967ea16..df63dbf94b6 100644 --- a/IntegrationTesting/ClientApp/Shared-iOS13+/swift-import-test.swift +++ b/IntegrationTesting/ClientApp/Shared-iOS13+/swift-import-test.swift @@ -22,3 +22,4 @@ import FirebaseAuth import FirebaseInAppMessaging import FirebaseInAppMessagingSwift #endif +import FirebaseStorage diff --git a/IntegrationTesting/CocoapodsIntegrationTest/TestEnvironments/Cocoapods_multiprojects_frameworks/Gemfile b/IntegrationTesting/CocoapodsIntegrationTest/TestEnvironments/Cocoapods_multiprojects_frameworks/Gemfile index 428f6565baf..e54b6b9c765 100644 --- a/IntegrationTesting/CocoapodsIntegrationTest/TestEnvironments/Cocoapods_multiprojects_frameworks/Gemfile +++ b/IntegrationTesting/CocoapodsIntegrationTest/TestEnvironments/Cocoapods_multiprojects_frameworks/Gemfile @@ -2,4 +2,4 @@ source "https://rubygems.org" -gem 'cocoapods', '1.10.0.rc.1' +gem 'cocoapods', '1.14.3' diff --git a/IntegrationTesting/CocoapodsIntegrationTest/TestEnvironments/Cocoapods_multiprojects_frameworks/Podfile b/IntegrationTesting/CocoapodsIntegrationTest/TestEnvironments/Cocoapods_multiprojects_frameworks/Podfile index 03bfe2e2d04..e45e8cd4908 100644 --- a/IntegrationTesting/CocoapodsIntegrationTest/TestEnvironments/Cocoapods_multiprojects_frameworks/Podfile +++ b/IntegrationTesting/CocoapodsIntegrationTest/TestEnvironments/Cocoapods_multiprojects_frameworks/Podfile @@ -25,6 +25,7 @@ target 'CocoapodsIntegrationTest' do pod 'FirebaseInstallations', :path => '../../' pod 'FirebaseMessaging', :path => '../../' pod 'FirebaseMessagingInterop', :path => '../../' + pod 'FirebaseRemoteConfigInterop', :path => '../../' pod 'FirebasePerformance', :path => '../../' pod 'FirebaseStorage', :path => '../../' end diff --git a/Package.swift b/Package.swift index 651db23da74..382662a728b 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.6 +// swift-tools-version:5.7.1 // The swift-tools-version declares the minimum version of Swift required to // build this package. @@ -19,7 +19,7 @@ import class Foundation.ProcessInfo import PackageDescription -let firebaseVersion = "10.21.0" +let firebaseVersion = "10.23.0" let package = Package( name: "Firebase", @@ -162,7 +162,7 @@ let package = Package( ), .package( url: "https://github.com/firebase/nanopb.git", - "2.30909.0" ..< "2.30910.0" + "2.30909.0" ..< "2.30911.0" ), abseilDependency(), grpcDependency(), @@ -199,6 +199,7 @@ let package = Package( .product(name: "GULLogger", package: "GoogleUtilities"), ], path: "FirebaseCore/Sources", + resources: [.process("Resources/PrivacyInfo.xcprivacy")], publicHeadersPath: "Public", cSettings: [ .headerSearchPath("../.."), @@ -232,6 +233,7 @@ let package = Package( .target( name: "FirebaseCoreExtension", path: "FirebaseCore/Extension", + resources: [.process("Resources/PrivacyInfo.xcprivacy")], publicHeadersPath: ".", cSettings: [ .headerSearchPath("../../"), @@ -246,7 +248,8 @@ let package = Package( dependencies: [ .product(name: "GULNSData", package: "GoogleUtilities"), ], - path: "FirebaseCore/Internal/Sources" + path: "FirebaseCore/Internal/Sources", + resources: [.process("Resources/PrivacyInfo.xcprivacy")] ), .testTarget( name: "FirebaseCoreInternalTests", @@ -260,6 +263,7 @@ let package = Package( name: "FirebaseABTesting", dependencies: ["FirebaseCore"], path: "FirebaseABTesting/Sources", + resources: [.process("Resources/PrivacyInfo.xcprivacy")], publicHeadersPath: "Public", cSettings: [ .headerSearchPath("../../"), @@ -310,8 +314,8 @@ let package = Package( ), .binaryTarget( name: "FirebaseAnalytics", - url: "https://dl.google.com/firebase/ios/swiftpm/10.20.0/FirebaseAnalytics.zip", - checksum: "169e9983be26e31bff373ea3ae5b56559a49f6bf14986f8813be5af03c00b251" + url: "https://dl.google.com/firebase/ios/swiftpm/10.23.0/FirebaseAnalytics.zip", + checksum: "3fb2f7a91480cffd1dfe9c8f212626cf67860f2686f4ec74122df0e9b411ec53" ), .target( name: "FirebaseAnalyticsSwiftTarget", @@ -434,6 +438,7 @@ let package = Package( .product(name: "RecaptchaInterop", package: "interop-ios-for-google-sdks"), ], path: "FirebaseAuth/Sources/Swift", + resources: [.process("Resources/PrivacyInfo.xcprivacy")], linkerSettings: [ .linkedFramework("Security"), .linkedFramework("SafariServices", .when(platforms: [.iOS])), @@ -457,7 +462,7 @@ let package = Package( exclude: [ "CMakeLists.txt", ], - publicHeadersPath: ".", + publicHeadersPath: "Public", cSettings: [ .headerSearchPath("../../"), ] @@ -498,11 +503,17 @@ let package = Package( ), .target( name: "FirebaseCrashlytics", - dependencies: ["FirebaseCore", "FirebaseInstallations", "FirebaseSessions", - .product(name: "GoogleDataTransport", package: "GoogleDataTransport"), - .product(name: "GULEnvironment", package: "GoogleUtilities"), - .product(name: "FBLPromises", package: "Promises"), - .product(name: "nanopb", package: "nanopb")], + dependencies: [ + "FirebaseCore", + "FirebaseInstallations", + "FirebaseSessions", + "FirebaseRemoteConfigInterop", + "FirebaseCrashlyticsSwift", + .product(name: "GoogleDataTransport", package: "GoogleDataTransport"), + .product(name: "GULEnvironment", package: "GoogleUtilities"), + .product(name: "FBLPromises", package: "Promises"), + .product(name: "nanopb", package: "nanopb"), + ], path: "Crashlytics", exclude: [ "run", @@ -515,6 +526,7 @@ let package = Package( "upload-symbols", "CrashlyticsInputFiles.xcfilelist", "third_party/libunwind/LICENSE", + "Crashlytics/Rollouts/", ], sources: [ "Crashlytics/", @@ -522,6 +534,7 @@ let package = Package( "Shared/", "third_party/libunwind/dwarf.h", ], + resources: [.process("Resources/PrivacyInfo.xcprivacy")], publicHeadersPath: "Crashlytics/Public", cSettings: [ .headerSearchPath(".."), @@ -543,6 +556,19 @@ let package = Package( .linkedFramework("SystemConfiguration", .when(platforms: [.iOS, .macOS, .tvOS])), ] ), + .target( + name: "FirebaseCrashlyticsSwift", + dependencies: ["FirebaseRemoteConfigInterop"], + path: "Crashlytics", + sources: [ + "Crashlytics/Rollouts/", + ] + ), + .testTarget( + name: "FirebaseCrashlyticsSwiftUnit", + dependencies: ["FirebaseCrashlyticsSwift"], + path: "Crashlytics/UnitTestsSwift/" + ), .testTarget( name: "FirebaseCrashlyticsUnit", dependencies: ["FirebaseCrashlytics", .product(name: "OCMock", package: "ocmock")], @@ -654,6 +680,7 @@ let package = Package( name: "FirebaseDynamicLinks", dependencies: ["FirebaseCore"], path: "FirebaseDynamicLinks/Sources", + resources: [.process("Resources/PrivacyInfo.xcprivacy")], publicHeadersPath: "Public", cSettings: [ .headerSearchPath("../../"), @@ -801,6 +828,7 @@ let package = Package( .product(name: "GULUserDefaults", package: "GoogleUtilities"), ], path: "FirebaseInstallations/Source/Library", + resources: [.process("Resources/PrivacyInfo.xcprivacy")], publicHeadersPath: "Public", cSettings: [ .headerSearchPath("../../../"), @@ -846,6 +874,7 @@ let package = Package( .product(name: "nanopb", package: "nanopb"), ], path: "FirebaseMessaging/Sources", + resources: [.process("Resources/PrivacyInfo.xcprivacy")], publicHeadersPath: "Public", cSettings: [ .headerSearchPath("../../"), @@ -965,6 +994,7 @@ let package = Package( "FirebaseCore", "FirebaseABTesting", "FirebaseInstallations", + "FirebaseRemoteConfigInterop", .product(name: "GULNSData", package: "GoogleUtilities"), ], path: "FirebaseRemoteConfig/Sources", @@ -994,13 +1024,22 @@ let package = Package( .headerSearchPath("../../.."), ] ), + .testTarget( + name: "RemoteConfigSwiftUnit", + dependencies: ["FirebaseRemoteConfigInternal"], + path: "FirebaseRemoteConfig/Tests/SwiftUnit", + cSettings: [ + .headerSearchPath("../../.."), + ] + ), .target( name: "FirebaseRemoteConfig", dependencies: [ "FirebaseRemoteConfigInternal", "FirebaseSharedSwift", ], - path: "FirebaseRemoteConfig/Swift" + path: "FirebaseRemoteConfig/Swift", + resources: [.process("Resources/PrivacyInfo.xcprivacy")] ), .target( name: "FirebaseRemoteConfigSwift", @@ -1036,6 +1075,15 @@ let package = Package( .headerSearchPath("../../../"), ] ), + // Internal headers only for consuming from other SDK. + .target( + name: "FirebaseRemoteConfigInterop", + path: "FirebaseRemoteConfig/Interop", + publicHeadersPath: ".", + cSettings: [ + .headerSearchPath("../../"), + ] + ), // MARK: - Firebase Sessions @@ -1073,6 +1121,8 @@ let package = Package( .target( name: "FirebaseSessionsObjC", dependencies: [ + "FirebaseCore", + "FirebaseCoreExtension", .product(name: "GULEnvironment", package: "GoogleUtilities"), .product(name: "nanopb", package: "nanopb"), ], @@ -1319,7 +1369,7 @@ func googleAppMeasurementDependency() -> Package.Dependency { return .package(url: appMeasurementURL, branch: "main") } - return .package(url: appMeasurementURL, exact: "10.20.0") + return .package(url: appMeasurementURL, exact: "10.23.0") } func abseilDependency() -> Package.Dependency { @@ -1330,12 +1380,12 @@ func abseilDependency() -> Package.Dependency { if ProcessInfo.processInfo.environment["FIREBASE_SOURCE_FIRESTORE"] != nil { packageInfo = ( "https://github.com/firebase/abseil-cpp-SwiftPM.git", - "0.20220623.0" ..< "0.20220624.0" + "0.20240116.1" ..< "0.20240117.0" ) } else { packageInfo = ( "https://github.com/google/abseil-cpp-binary.git", - "1.2022062300.0" ..< "1.2022062400.0" + "1.2024011601.0" ..< "1.2024011700.0" ) } @@ -1348,9 +1398,9 @@ func grpcDependency() -> Package.Dependency { // If building Firestore from source, abseil will need to be built as source // as the headers in the binary version of abseil are unusable. if ProcessInfo.processInfo.environment["FIREBASE_SOURCE_FIRESTORE"] != nil { - packageInfo = ("https://github.com/grpc/grpc-ios.git", "1.49.1" ..< "1.50.0") + packageInfo = ("https://github.com/grpc/grpc-ios.git", "1.62.3" ..< "1.63.0") } else { - packageInfo = ("https://github.com/google/grpc-binary.git", "1.49.1" ..< "1.50.0") + packageInfo = ("https://github.com/google/grpc-binary.git", "1.62.1" ..< "1.63.0") } return .package(url: packageInfo.url, packageInfo.range) @@ -1472,7 +1522,8 @@ func firestoreTargets() -> [Target] { ], sources: [ "Swift/Source/", - ] + ], + resources: [.process("Source/Resources/PrivacyInfo.xcprivacy")] ), ] } @@ -1488,8 +1539,8 @@ func firestoreTargets() -> [Target] { } else { return .binaryTarget( name: "FirebaseFirestoreInternal", - url: "https://dl.google.com/firebase/ios/bin/firestore/10.20.0/FirebaseFirestoreInternal.zip", - checksum: "7fe8f913d35e257979eddc8e2df0fedd3b89735c7030494307079747f03279c7" + url: "https://dl.google.com/firebase/ios/bin/firestore/10.23.0/rc1/FirebaseFirestoreInternal.zip", + checksum: "777f89e6c453cca8dfdcc375304cd3f3059b55c4beab86dce3061c0ece1e0556" ) } }() @@ -1520,6 +1571,7 @@ func firestoreTargets() -> [Target] { "FirebaseSharedSwift", ], path: "Firestore/Swift/Source", + resources: [.process("Resources/PrivacyInfo.xcprivacy")], linkerSettings: [ .linkedFramework("SystemConfiguration", .when(platforms: [.iOS, .macOS, .tvOS])), .linkedFramework("UIKit", .when(platforms: [.iOS, .tvOS])), diff --git a/README.md b/README.md index aef2138f63b..6e0742ddb29 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ development with Swift Package Manager. ### CocoaPods Install the following: -* CocoaPods 1.10.0 (or later) +* CocoaPods 1.12.0 (or later) * [CocoaPods generate](https://github.com/square/cocoapods-generate) For the pod that you want to develop: @@ -153,7 +153,7 @@ GitHub Actions will verify that any code changes are done in a style-compliant way. Install `clang-format` and `mint`: ```console -brew install clang-format@17 +brew install clang-format@18 brew install mint ``` diff --git a/ROADMAP.md b/ROADMAP.md index 5d60f4db0d6..ded212a3e67 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -11,109 +11,7 @@ contributing to the Firebase iOS SDK. ## Modernization - More Swifty -As we go into 2022, it's a top priority for the Firebase team to improve -usability and functionality for Swift developers. We welcome the community's -input and contribution as we work through this. - -See the [Project Dashboard](SwiftDashboard.md). - -Please upvote existing feature requests, add new feature requests, and send PRs. -* [Example Feature Request](https://github.com/firebase/firebase-ios-sdk/issues/8827) -* [Example Pull Request](https://github.com/firebase/firebase-ios-sdk/pull/6568) - -See [Contributing.md](Contributing.md) for full details about contributing -code to the Firebase repo. - -Thanks in large part to community contributions, we already have several Swift -improvements: -* Analytics - * Enabling [SwiftUI Screen tracking](https://github.com/firebase/firebase-ios-sdk/blob/main/FirebaseAnalyticsSwift/CHANGELOG.md) - automated view logging for SwiftUI apps -* Firestore and RTDB - * Codable Support ([Firestore](https://github.com/firebase/firebase-ios-sdk/pull/3198), - [Database](https://github.com/firebase/firebase-ios-sdk/tree/main/FirebaseDatabaseSwift/Sources/Codable)) - eliminated manual data processing - * [Property wrappers](https://github.com/firebase/firebase-ios-sdk/pull/8408) for Firestore collections dramatically simplified client coding -* Storage - * Eliminated impossible states, provided new and improved async API usage via - [Result type](https://github.com/firebase/firebase-ios-sdk/blob/main/FirebaseStorage/CHANGELOG.md) - and [async/await](https://github.com/firebase/firebase-ios-sdk/blob/main/FirebaseStorage/CHANGELOG.md) - additions -* ML Model Downloader - * Full [SDK implementation in Swift](https://github.com/firebase/firebase-ios-sdk/tree/main/FirebaseMLModelDownloader/Sources) -* In App Messaging - * Vastly simplified usage from SwiftUI with - [SwiftUI modifiers](https://github.com/firebase/firebase-ios-sdk/pull/7496) to show messages and - [preview helpers](https://github.com/firebase/firebase-ios-sdk/pull/8351) - -### Phase 1 - Address Low Hanging Fruit for all Firebase Products -* Swift API tests -* async/await API evaluation, tests, and augmentation -* Fix non-Swifty APIs -* Fill API gaps -* Better Swift Error Handling -* Property Wrappers (Not necessarily low hanging, but can be high value) -* Identify larger projects for future phases - -### APIs - -Continue to evolve the Firebase API surface to be more -Swift-friendly. This is generally done with Swift specific extension libraries. - -[FirebaseFirestoreSwift](Firestore/Swift) is a larger library that adds -Codable support for Firestore. - -Add more such APIs to improve the Firebase Swift API. - -More examples in the -[feature requests](https://github.com/firebase/firebase-ios-sdk/issues?q=is%3Aopen+is%3Aissue+label%3A%22Swift+API%22). - -### SwiftUI - -Firebase should be better integrated with SwiftUI apps. See SwiftUI related -[issues](https://github.com/firebase/firebase-ios-sdk/issues?q=is%3Aissue+is%3Aopen++label%3ASwiftUI). - -### Swift Async/Await - -Evaluate impact on Firebase APIs of the -[Swift Async/await proposal](https://github.com/apple/swift-evolution/blob/main/proposals/0296-async-await.md). -For example, Objective-C callback APIs that return a value do not get an -async/await API automatically generated and an explicit function may need to be -added. See these -[Firebase Storage examples](https://github.com/firebase/firebase-ios-sdk/blob/main/FirebaseStorage/Sources/AsyncAwait.swift). - -### Combine - -Firebase has community support for Combine (Thanks!). See -[Combine Readme](FirebaseCombineSwift/README.md) for usage and project details. - -## More complete Apple platform support - -Continue to expand the range and quality of Firebase support across -all Apple platforms. - -Expand the -[current non-iOS platform support](README.md#community-supported-efforts) -from community supported to officially supported. - -Fill in the missing pieces of the support matrix, which is -primarily *watchOS* for several libraries. - -## Getting Started - -### Quickstarts - -Modernize the [Swift Quickstarts](https://github.com/firebase/quickstart-ios). -Continue the work done in 2020 and 2021 that used better Swift style, SwiftUI, -Swift Package Manager, async/await APIs, and multi-platform support for -[Analytics](https://github.com/firebase/quickstart-ios/tree/master/analytics), -[ABTesting](https://github.com/firebase/quickstart-ios/tree/master/abtesting), -[Auth](https://github.com/firebase/quickstart-ios/tree/master/authentication), -[Database](https://github.com/firebase/quickstart-ios/tree/master/database), -[Functions](https://github.com/firebase/quickstart-ios/tree/master/functions), -[Performance](https://github.com/firebase/quickstart-ios/tree/master/performance), -and -[RemoteConfig](https://github.com/firebase/quickstart-ios/tree/master/config). +We're continuing a long term journey to migrate from Objective-C to Swift. ## Product Improvements diff --git a/ReleaseTooling/CarthageJSON/FirebaseABTestingBinary.json b/ReleaseTooling/CarthageJSON/FirebaseABTestingBinary.json index 6de1cc558ce..606d9c06920 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseABTestingBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseABTestingBinary.json @@ -13,6 +13,8 @@ "10.19.0": "https://dl.google.com/dl/firebase/ios/carthage/10.19.0/FirebaseABTesting-1a1916232af9cd1a.zip", "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebaseABTesting-3c0e5c9ddc52bccd.zip", "10.20.0": "https://dl.google.com/dl/firebase/ios/carthage/10.20.0/FirebaseABTesting-8dad3d6af34cb26c.zip", + "10.21.0": "https://dl.google.com/dl/firebase/ios/carthage/10.21.0/FirebaseABTesting-0ad6c6c2f729706c.zip", + "10.22.0": "https://dl.google.com/dl/firebase/ios/carthage/10.22.0/FirebaseABTesting-2823ac22562f1fbe.zip", "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebaseABTesting-e87c686cee02758a.zip", "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebaseABTesting-6a65ab8b888172af.zip", "10.5.0": "https://dl.google.com/dl/firebase/ios/carthage/10.5.0/FirebaseABTesting-197f0cb4125363b6.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAdMobBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAdMobBinary.json index 383f955a6f6..8d3f22bb190 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAdMobBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAdMobBinary.json @@ -13,6 +13,8 @@ "10.19.0": "https://dl.google.com/dl/firebase/ios/carthage/10.19.0/Google-Mobile-Ads-SDK-db6e557426f37394.zip", "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/Google-Mobile-Ads-SDK-c8bc252ed3323212.zip", "10.20.0": "https://dl.google.com/dl/firebase/ios/carthage/10.20.0/Google-Mobile-Ads-SDK-5f8bb98bb2467b85.zip", + "10.21.0": "https://dl.google.com/dl/firebase/ios/carthage/10.21.0/Google-Mobile-Ads-SDK-23be5a73a2ce3dcc.zip", + "10.22.0": "https://dl.google.com/dl/firebase/ios/carthage/10.22.0/Google-Mobile-Ads-SDK-bf8077d30296e04a.zip", "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/Google-Mobile-Ads-SDK-8b0d1ce3d1162b67.zip", "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/Google-Mobile-Ads-SDK-046511c3fd0189eb.zip", "10.5.0": "https://dl.google.com/dl/firebase/ios/carthage/10.5.0/Google-Mobile-Ads-SDK-50008c143ad8f268.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAnalyticsBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAnalyticsBinary.json index 1b8cfd0f1c4..b28bbd0ea7f 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAnalyticsBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAnalyticsBinary.json @@ -13,6 +13,8 @@ "10.19.0": "https://dl.google.com/dl/firebase/ios/carthage/10.19.0/FirebaseAnalytics-040a907770027c1e.zip", "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebaseAnalytics-6f8b70c8ee2efc85.zip", "10.20.0": "https://dl.google.com/dl/firebase/ios/carthage/10.20.0/FirebaseAnalytics-4d7ca295e8b44c0c.zip", + "10.21.0": "https://dl.google.com/dl/firebase/ios/carthage/10.21.0/FirebaseAnalytics-620570dc24ce7d7b.zip", + "10.22.0": "https://dl.google.com/dl/firebase/ios/carthage/10.22.0/FirebaseAnalytics-a121058bc5824bfa.zip", "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebaseAnalytics-95669fcf109f74a2.zip", "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebaseAnalytics-c0db6cb0e858e397.zip", "10.5.0": "https://dl.google.com/dl/firebase/ios/carthage/10.5.0/FirebaseAnalytics-e8ebe991b5743f71.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAnalyticsOnDeviceConversionBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAnalyticsOnDeviceConversionBinary.json index 7dee6fe1842..e4dbe4f3dfc 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAnalyticsOnDeviceConversionBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAnalyticsOnDeviceConversionBinary.json @@ -13,6 +13,8 @@ "10.19.0": "https://dl.google.com/dl/firebase/ios/carthage/10.19.0/FirebaseAnalyticsOnDeviceConversion-219e668e914bee4c.zip", "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebaseAnalyticsOnDeviceConversion-37cf6277991d7d75.zip", "10.20.0": "https://dl.google.com/dl/firebase/ios/carthage/10.20.0/FirebaseAnalyticsOnDeviceConversion-d3913995b7344202.zip", + "10.21.0": "https://dl.google.com/dl/firebase/ios/carthage/10.21.0/FirebaseAnalyticsOnDeviceConversion-202ed30074984af7.zip", + "10.22.0": "https://dl.google.com/dl/firebase/ios/carthage/10.22.0/FirebaseAnalyticsOnDeviceConversion-4b5874979659af63.zip", "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebaseAnalyticsOnDeviceConversion-091f5252d693a9f9.zip", "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebaseAnalyticsOnDeviceConversion-7bbb73d46383a042.zip", "10.5.0": "https://dl.google.com/dl/firebase/ios/carthage/10.5.0/FirebaseAnalyticsOnDeviceConversion-eca2f83d40e0278d.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAppCheckBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAppCheckBinary.json index 5011e733d47..a1e400157fd 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAppCheckBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAppCheckBinary.json @@ -13,6 +13,8 @@ "10.19.0": "https://dl.google.com/dl/firebase/ios/carthage/10.19.0/FirebaseAppCheck-09ecedc08c2562d0.zip", "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebaseAppCheck-b0ead84a126d24d4.zip", "10.20.0": "https://dl.google.com/dl/firebase/ios/carthage/10.20.0/FirebaseAppCheck-8f7dfe411eeaccdf.zip", + "10.21.0": "https://dl.google.com/dl/firebase/ios/carthage/10.21.0/FirebaseAppCheck-a458ebf606a7b451.zip", + "10.22.0": "https://dl.google.com/dl/firebase/ios/carthage/10.22.0/FirebaseAppCheck-2b52807979acf863.zip", "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebaseAppCheck-d19e46a728b1ac4f.zip", "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebaseAppCheck-8339fde989fe8f24.zip", "10.5.0": "https://dl.google.com/dl/firebase/ios/carthage/10.5.0/FirebaseAppCheck-3ce0f074bfcd2596.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAppDistributionBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAppDistributionBinary.json index 93e41514e2b..d96e5583946 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAppDistributionBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAppDistributionBinary.json @@ -13,6 +13,8 @@ "10.19.0": "https://dl.google.com/dl/firebase/ios/carthage/10.19.0/FirebaseAppDistribution-30aec5c329204ede.zip", "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebaseAppDistribution-45b5c85bba08a85b.zip", "10.20.0": "https://dl.google.com/dl/firebase/ios/carthage/10.20.0/FirebaseAppDistribution-264a5e036b72a526.zip", + "10.21.0": "https://dl.google.com/dl/firebase/ios/carthage/10.21.0/FirebaseAppDistribution-e08ef26e391c7b0b.zip", + "10.22.0": "https://dl.google.com/dl/firebase/ios/carthage/10.22.0/FirebaseAppDistribution-139211bb5dd3dbc3.zip", "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebaseAppDistribution-cefc3327ddfceda6.zip", "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebaseAppDistribution-7931e42d39575534.zip", "10.5.0": "https://dl.google.com/dl/firebase/ios/carthage/10.5.0/FirebaseAppDistribution-79dc2b1348d9aee9.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseAuthBinary.json b/ReleaseTooling/CarthageJSON/FirebaseAuthBinary.json index fb86a849673..aef6428aef4 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseAuthBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseAuthBinary.json @@ -13,6 +13,8 @@ "10.19.0": "https://dl.google.com/dl/firebase/ios/carthage/10.19.0/FirebaseAuth-9b66f8a440352f9b.zip", "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebaseAuth-2165e27f89d4959e.zip", "10.20.0": "https://dl.google.com/dl/firebase/ios/carthage/10.20.0/FirebaseAuth-222a2417c3c21b41.zip", + "10.21.0": "https://dl.google.com/dl/firebase/ios/carthage/10.21.0/FirebaseAuth-da6796caf834f09f.zip", + "10.22.0": "https://dl.google.com/dl/firebase/ios/carthage/10.22.0/FirebaseAuth-529e82147fbbd402.zip", "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebaseAuth-e43e66353617f093.zip", "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebaseAuth-8a9591e6daa7e207.zip", "10.5.0": "https://dl.google.com/dl/firebase/ios/carthage/10.5.0/FirebaseAuth-7e18a510d0a5b02e.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseCrashlyticsBinary.json b/ReleaseTooling/CarthageJSON/FirebaseCrashlyticsBinary.json index de53576ff28..f321ad7f10a 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseCrashlyticsBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseCrashlyticsBinary.json @@ -13,6 +13,8 @@ "10.19.0": "https://dl.google.com/dl/firebase/ios/carthage/10.19.0/FirebaseCrashlytics-8126c26e6374c8b1.zip", "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebaseCrashlytics-054718c61ef054f9.zip", "10.20.0": "https://dl.google.com/dl/firebase/ios/carthage/10.20.0/FirebaseCrashlytics-029c76d79754388c.zip", + "10.21.0": "https://dl.google.com/dl/firebase/ios/carthage/10.21.0/FirebaseCrashlytics-0f5ccfdbf0de85f7.zip", + "10.22.0": "https://dl.google.com/dl/firebase/ios/carthage/10.22.0/FirebaseCrashlytics-47c05619edb8ae9b.zip", "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebaseCrashlytics-d29d3285a7d9fa1d.zip", "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebaseCrashlytics-165beb64483b4278.zip", "10.5.0": "https://dl.google.com/dl/firebase/ios/carthage/10.5.0/FirebaseCrashlytics-53604573442e756b.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseDatabaseBinary.json b/ReleaseTooling/CarthageJSON/FirebaseDatabaseBinary.json index af8fcab629a..c1a787120f4 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseDatabaseBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseDatabaseBinary.json @@ -13,6 +13,8 @@ "10.19.0": "https://dl.google.com/dl/firebase/ios/carthage/10.19.0/FirebaseDatabase-a62dd446dac4b89f.zip", "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebaseDatabase-8b7048f7890bb665.zip", "10.20.0": "https://dl.google.com/dl/firebase/ios/carthage/10.20.0/FirebaseDatabase-a7f5c6d032473b01.zip", + "10.21.0": "https://dl.google.com/dl/firebase/ios/carthage/10.21.0/FirebaseDatabase-a05cb524bec955b2.zip", + "10.22.0": "https://dl.google.com/dl/firebase/ios/carthage/10.22.0/FirebaseDatabase-f5156c8169b6358f.zip", "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebaseDatabase-5b22f689cb66d83a.zip", "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebaseDatabase-e1a9d1f0c4222cf7.zip", "10.5.0": "https://dl.google.com/dl/firebase/ios/carthage/10.5.0/FirebaseDatabase-aea9249d81841ee1.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseDynamicLinksBinary.json b/ReleaseTooling/CarthageJSON/FirebaseDynamicLinksBinary.json index 7f49cc9370c..b099f6ead0d 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseDynamicLinksBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseDynamicLinksBinary.json @@ -13,6 +13,8 @@ "10.19.0": "https://dl.google.com/dl/firebase/ios/carthage/10.19.0/FirebaseDynamicLinks-a4f0deb13e7b39fd.zip", "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebaseDynamicLinks-bfdce6ac5d591ab3.zip", "10.20.0": "https://dl.google.com/dl/firebase/ios/carthage/10.20.0/FirebaseDynamicLinks-693c6213bc87f8c0.zip", + "10.21.0": "https://dl.google.com/dl/firebase/ios/carthage/10.21.0/FirebaseDynamicLinks-ad0ac7b8fdf4c1b5.zip", + "10.22.0": "https://dl.google.com/dl/firebase/ios/carthage/10.22.0/FirebaseDynamicLinks-c17c59949b7cc573.zip", "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebaseDynamicLinks-7cf4ae5e96882ca8.zip", "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebaseDynamicLinks-c3bdeb37651a5d5d.zip", "10.5.0": "https://dl.google.com/dl/firebase/ios/carthage/10.5.0/FirebaseDynamicLinks-bcb5df6ec32f6684.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseFirestoreBinary.json b/ReleaseTooling/CarthageJSON/FirebaseFirestoreBinary.json index 56e3f67a9f3..b853a2b2d5e 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseFirestoreBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseFirestoreBinary.json @@ -13,6 +13,8 @@ "10.19.0": "https://dl.google.com/dl/firebase/ios/carthage/10.19.0/FirebaseFirestore-b46da5ea686c3422.zip", "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebaseFirestore-4c3d1568e379a98c.zip", "10.20.0": "https://dl.google.com/dl/firebase/ios/carthage/10.20.0/FirebaseFirestore-88b0aaac6fe277fe.zip", + "10.21.0": "https://dl.google.com/dl/firebase/ios/carthage/10.21.0/FirebaseFirestore-dcf15ce0975bfa3c.zip", + "10.22.0": "https://dl.google.com/dl/firebase/ios/carthage/10.22.0/FirebaseFirestore-e4570e4863fe2044.zip", "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebaseFirestore-73ba0700b1aa6d6a.zip", "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebaseFirestore-02eb8da05f81fca5.zip", "10.5.0": "https://dl.google.com/dl/firebase/ios/carthage/10.5.0/FirebaseFirestore-46fa68ddf287f76e.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseFunctionsBinary.json b/ReleaseTooling/CarthageJSON/FirebaseFunctionsBinary.json index f5220531424..448dfb89814 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseFunctionsBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseFunctionsBinary.json @@ -13,6 +13,8 @@ "10.19.0": "https://dl.google.com/dl/firebase/ios/carthage/10.19.0/FirebaseFunctions-8e91f34c0289f5ba.zip", "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebaseFunctions-b949cfeca4e7a80f.zip", "10.20.0": "https://dl.google.com/dl/firebase/ios/carthage/10.20.0/FirebaseFunctions-23d6ba97d95db62c.zip", + "10.21.0": "https://dl.google.com/dl/firebase/ios/carthage/10.21.0/FirebaseFunctions-b77aca8c98dba58d.zip", + "10.22.0": "https://dl.google.com/dl/firebase/ios/carthage/10.22.0/FirebaseFunctions-d98d21836c2f2130.zip", "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebaseFunctions-47189f2c99cdf806.zip", "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebaseFunctions-17c4b760141e38ad.zip", "10.5.0": "https://dl.google.com/dl/firebase/ios/carthage/10.5.0/FirebaseFunctions-688a38b567392fcf.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseGoogleSignInBinary.json b/ReleaseTooling/CarthageJSON/FirebaseGoogleSignInBinary.json index 1a9722b791f..4125b2318ca 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseGoogleSignInBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseGoogleSignInBinary.json @@ -13,6 +13,8 @@ "10.19.0": "https://dl.google.com/dl/firebase/ios/carthage/10.19.0/GoogleSignIn-7d252d83bad9f2b6.zip", "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/GoogleSignIn-c887dbc6bd07c787.zip", "10.20.0": "https://dl.google.com/dl/firebase/ios/carthage/10.20.0/GoogleSignIn-e55954e1a3ca9ee8.zip", + "10.21.0": "https://dl.google.com/dl/firebase/ios/carthage/10.21.0/GoogleSignIn-82fc8f5e20a9345b.zip", + "10.22.0": "https://dl.google.com/dl/firebase/ios/carthage/10.22.0/GoogleSignIn-a16b78c06ef8f77c.zip", "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/GoogleSignIn-a5b49807be66100b.zip", "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/GoogleSignIn-0d2e746eb3ff9f92.zip", "10.5.0": "https://dl.google.com/dl/firebase/ios/carthage/10.5.0/GoogleSignIn-5cb2a2f1f74efd5e.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseInAppMessagingBinary.json b/ReleaseTooling/CarthageJSON/FirebaseInAppMessagingBinary.json index 77a02b25789..7de061a8fd2 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseInAppMessagingBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseInAppMessagingBinary.json @@ -13,6 +13,8 @@ "10.19.0": "https://dl.google.com/dl/firebase/ios/carthage/10.19.0/FirebaseInAppMessaging-ab8486f9415476ca.zip", "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebaseInAppMessaging-f29d7b7839cda915.zip", "10.20.0": "https://dl.google.com/dl/firebase/ios/carthage/10.20.0/FirebaseInAppMessaging-c6a82f2dccc9a092.zip", + "10.21.0": "https://dl.google.com/dl/firebase/ios/carthage/10.21.0/FirebaseInAppMessaging-940786963f9ac384.zip", + "10.22.0": "https://dl.google.com/dl/firebase/ios/carthage/10.22.0/FirebaseInAppMessaging-fbb53083384bea1e.zip", "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebaseInAppMessaging-91e5426eade46bca.zip", "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebaseInAppMessaging-10801bd111df59de.zip", "10.5.0": "https://dl.google.com/dl/firebase/ios/carthage/10.5.0/FirebaseInAppMessaging-91d4dd9878a06b7e.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseMLModelDownloaderBinary.json b/ReleaseTooling/CarthageJSON/FirebaseMLModelDownloaderBinary.json index dd4d3949ac9..d81b3861de8 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseMLModelDownloaderBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseMLModelDownloaderBinary.json @@ -13,6 +13,8 @@ "10.19.0": "https://dl.google.com/dl/firebase/ios/carthage/10.19.0/FirebaseMLModelDownloader-865fc851458ff97e.zip", "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebaseMLModelDownloader-ee2af587027e74d3.zip", "10.20.0": "https://dl.google.com/dl/firebase/ios/carthage/10.20.0/FirebaseMLModelDownloader-e45969e88bf879cd.zip", + "10.21.0": "https://dl.google.com/dl/firebase/ios/carthage/10.21.0/FirebaseMLModelDownloader-d779b84cfdf214f3.zip", + "10.22.0": "https://dl.google.com/dl/firebase/ios/carthage/10.22.0/FirebaseMLModelDownloader-b3bffe302a074d0e.zip", "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebaseMLModelDownloader-559cb113c0cfd8f2.zip", "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebaseMLModelDownloader-9c909894999c92e4.zip", "10.5.0": "https://dl.google.com/dl/firebase/ios/carthage/10.5.0/FirebaseMLModelDownloader-9abf9b0e24bfb921.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseMessagingBinary.json b/ReleaseTooling/CarthageJSON/FirebaseMessagingBinary.json index 581cec20e53..dcd884a8317 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseMessagingBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseMessagingBinary.json @@ -13,6 +13,8 @@ "10.19.0": "https://dl.google.com/dl/firebase/ios/carthage/10.19.0/FirebaseMessaging-536acbc043d0e42a.zip", "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebaseMessaging-289a04c85f7e771d.zip", "10.20.0": "https://dl.google.com/dl/firebase/ios/carthage/10.20.0/FirebaseMessaging-236bb6f578c05ed1.zip", + "10.21.0": "https://dl.google.com/dl/firebase/ios/carthage/10.21.0/FirebaseMessaging-4a481ad8d3446844.zip", + "10.22.0": "https://dl.google.com/dl/firebase/ios/carthage/10.22.0/FirebaseMessaging-812bc4f1c2d27e93.zip", "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebaseMessaging-59ef1cc63c660712.zip", "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebaseMessaging-76c02a69e3fe1008.zip", "10.5.0": "https://dl.google.com/dl/firebase/ios/carthage/10.5.0/FirebaseMessaging-439a17dcc8b8172b.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebasePerformanceBinary.json b/ReleaseTooling/CarthageJSON/FirebasePerformanceBinary.json index 2a8ace17067..4ab36e1a255 100644 --- a/ReleaseTooling/CarthageJSON/FirebasePerformanceBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebasePerformanceBinary.json @@ -13,6 +13,8 @@ "10.19.0": "https://dl.google.com/dl/firebase/ios/carthage/10.19.0/FirebasePerformance-5ef59eac9e09086e.zip", "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebasePerformance-7a7398acc615dbb6.zip", "10.20.0": "https://dl.google.com/dl/firebase/ios/carthage/10.20.0/FirebasePerformance-6494eb8091be4e03.zip", + "10.21.0": "https://dl.google.com/dl/firebase/ios/carthage/10.21.0/FirebasePerformance-4b6c574e0645b449.zip", + "10.22.0": "https://dl.google.com/dl/firebase/ios/carthage/10.22.0/FirebasePerformance-2a39f03d02fcbc5f.zip", "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebasePerformance-36ac6dfb99caa11b.zip", "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebasePerformance-f9f5be8ffad5cbb0.zip", "10.5.0": "https://dl.google.com/dl/firebase/ios/carthage/10.5.0/FirebasePerformance-0ffe559f7554d8a5.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseRemoteConfigBinary.json b/ReleaseTooling/CarthageJSON/FirebaseRemoteConfigBinary.json index cc135d0dfc7..d9bdcfcdc9a 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseRemoteConfigBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseRemoteConfigBinary.json @@ -13,6 +13,8 @@ "10.19.0": "https://dl.google.com/dl/firebase/ios/carthage/10.19.0/FirebaseRemoteConfig-13b390a0fae2a496.zip", "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebaseRemoteConfig-45a7f541a654884c.zip", "10.20.0": "https://dl.google.com/dl/firebase/ios/carthage/10.20.0/FirebaseRemoteConfig-44d640335ebdfea7.zip", + "10.21.0": "https://dl.google.com/dl/firebase/ios/carthage/10.21.0/FirebaseRemoteConfig-933eae5291c343cc.zip", + "10.22.0": "https://dl.google.com/dl/firebase/ios/carthage/10.22.0/FirebaseRemoteConfig-be4764f1b3e07c4f.zip", "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebaseRemoteConfig-edd1b427b8bbe782.zip", "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebaseRemoteConfig-10b62ee5663aaab3.zip", "10.5.0": "https://dl.google.com/dl/firebase/ios/carthage/10.5.0/FirebaseRemoteConfig-2237eb5fcd4a4525.zip", diff --git a/ReleaseTooling/CarthageJSON/FirebaseStorageBinary.json b/ReleaseTooling/CarthageJSON/FirebaseStorageBinary.json index 9fae2c91442..857c97f7e05 100644 --- a/ReleaseTooling/CarthageJSON/FirebaseStorageBinary.json +++ b/ReleaseTooling/CarthageJSON/FirebaseStorageBinary.json @@ -13,6 +13,8 @@ "10.19.0": "https://dl.google.com/dl/firebase/ios/carthage/10.19.0/FirebaseStorage-93e56fb9268ffd5f.zip", "10.2.0": "https://dl.google.com/dl/firebase/ios/carthage/10.2.0/FirebaseStorage-347e6a3402706596.zip", "10.20.0": "https://dl.google.com/dl/firebase/ios/carthage/10.20.0/FirebaseStorage-fac47c17aae220e0.zip", + "10.21.0": "https://dl.google.com/dl/firebase/ios/carthage/10.21.0/FirebaseStorage-6f3adc4f2b871f04.zip", + "10.22.0": "https://dl.google.com/dl/firebase/ios/carthage/10.22.0/FirebaseStorage-e3b2849afc9f0f95.zip", "10.3.0": "https://dl.google.com/dl/firebase/ios/carthage/10.3.0/FirebaseStorage-ac463d14593d10a8.zip", "10.4.0": "https://dl.google.com/dl/firebase/ios/carthage/10.4.0/FirebaseStorage-fdf8479115660ce6.zip", "10.5.0": "https://dl.google.com/dl/firebase/ios/carthage/10.5.0/FirebaseStorage-04f255ea8c3a7420.zip", diff --git a/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift b/ReleaseTooling/Sources/FirebaseManifest/FirebaseManifest.swift index e0492f0f58a..8575cf37e36 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: "10.21.0", + version: "10.23.0", pods: [ Pod("FirebaseSharedSwift"), Pod("FirebaseCoreInternal"), @@ -32,6 +32,7 @@ public let shared = Manifest( Pod("FirebaseMessagingInterop"), Pod("FirebaseInstallations"), Pod("FirebaseSessions"), + Pod("FirebaseRemoteConfigInterop"), Pod("GoogleAppMeasurement", isClosedSource: true), Pod("GoogleAppMeasurementOnDeviceConversion", isClosedSource: true, platforms: ["ios"]), Pod("FirebaseAnalytics", isClosedSource: true, zip: true), diff --git a/ReleaseTooling/Sources/Utils/FileManager+Utils.swift b/ReleaseTooling/Sources/Utils/FileManager+Utils.swift index 14af5f0406a..5f25c4e5c83 100644 --- a/ReleaseTooling/Sources/Utils/FileManager+Utils.swift +++ b/ReleaseTooling/Sources/Utils/FileManager+Utils.swift @@ -159,7 +159,13 @@ public extension FileManager { // Recursively search using the enumerator, adding any matches to the array. var matches: [URL] = [] var foundXcframework = false // Ignore .frameworks after finding an xcframework. - while let fileURL = dirEnumerator.nextObject() as? URL { + for case let fileURL as URL in dirEnumerator { + // Never mess with Privacy.bundles + if fileURL.lastPathComponent.hasSuffix("_Privacy.bundle") { + dirEnumerator.skipDescendants() + continue + } + switch type { case .allFiles: // Skip directories, include everything else. diff --git a/ReleaseTooling/Sources/ZipBuilder/CarthageUtils.swift b/ReleaseTooling/Sources/ZipBuilder/CarthageUtils.swift index bb17e0e1fa8..5b87998bb37 100644 --- a/ReleaseTooling/Sources/ZipBuilder/CarthageUtils.swift +++ b/ReleaseTooling/Sources/ZipBuilder/CarthageUtils.swift @@ -241,16 +241,24 @@ extension CarthageUtils { } catch { fatalError("Couldn't copy dummy library for Firebase framework in Carthage. \(error)") } + + // Write the Info.plist. + generatePlistContents(forName: "Firebase", withVersion: version, to: frameworkDir) } static func generatePlistContents(forName name: String, withVersion version: String, to location: URL) { let ver = version.components(separatedBy: "-")[0] // remove any version suffix. + + // TODO(paulb777): Does MinimumOSVersion or anything else need + // to be adapted for other platforms? let plist: [String: String] = ["CFBundleIdentifier": "com.firebase.Firebase-\(name)", "CFBundleInfoDictionaryVersion": "6.0", "CFBundlePackageType": "FMWK", "CFBundleVersion": ver, + "CFBundleShortVersionString": ver, + "MinimumOSVersion": Platform.iOS.minimumVersion, "DTSDKName": "iphonesimulator11.2", "CFBundleExecutable": name, "CFBundleName": name] diff --git a/ReleaseTooling/Sources/ZipBuilder/FrameworkBuilder.swift b/ReleaseTooling/Sources/ZipBuilder/FrameworkBuilder.swift index 8f6f8f2fbbe..d322aa91ec4 100755 --- a/ReleaseTooling/Sources/ZipBuilder/FrameworkBuilder.swift +++ b/ReleaseTooling/Sources/ZipBuilder/FrameworkBuilder.swift @@ -283,8 +283,20 @@ struct FrameworkBuilder { /// - Returns: The corresponding framework/module name. private static func frameworkBuildName(_ framework: String) -> String { switch framework { + case "abseil": + return "absl" + case "BoringSSL-GRPC": + return "openssl_grpc" + case "gRPC-Core": + return "grpc" + case "gRPC-C++": + return "grpcpp" + case "leveldb-library": + return "leveldb" case "PromisesObjC": return "FBLPromises" + case "PromisesSwift": + return "Promises" case "Protobuf": return "protobuf" default: @@ -590,8 +602,8 @@ struct FrameworkBuilder { /// Groups slices for each platform into a minimal set of frameworks. /// - Parameter withName: The framework name. /// - Parameter isCarthage: Name the temp directory differently for Carthage. - /// - Parameter fromFolder: The almost complete framework folder. Includes - /// Headers, and Resources. + /// - Parameter fromFolder: The almost complete framework folder. Includes Headers, Info.plist, + /// and Resources. /// - Parameter slicedFrameworks: All the frameworks sliced by platform. /// - Parameter moduleMapContents: Module map contents for all frameworks in this pod. private func groupFrameworks(withName framework: String, @@ -635,6 +647,8 @@ struct FrameworkBuilder { "\(framework): \(error)") } + processPrivacyManifests(fileManager, frameworkPath, platformFrameworkDir) + // Headers from slice do { let headersSrc: URL = frameworkPath.appendingPathComponent("Headers") @@ -656,16 +670,49 @@ struct FrameworkBuilder { fatalError("Could not create framework directory needed to build \(framework): \(error)") } - // Copy the binary to the right location. + // Copy the binary and Info.plist to the right location. let binaryName = frameworkPath.lastPathComponent.replacingOccurrences(of: ".framework", with: "") let fatBinary = frameworkPath.appendingPathComponent(binaryName).resolvingSymlinksInPath() + let plistPathComponents = { + if platform == .catalyst || platform == .macOS { + // Frameworks for macOS and macCatalyst have a different directory + // structure so the framework-level `Info.plist` is found in a + // different spot. + return ["Versions", "A", "Resources", "Info.plist"] + } else { + return ["Info.plist"] + } + }() + let infoPlist = frameworkPath.appendingPathComponents(plistPathComponents) + .resolvingSymlinksInPath() + let infoPlistDestination = platformFrameworkDir.appendingPathComponent("Info.plist") let fatBinaryDestination = platformFrameworkDir.appendingPathComponent(framework) do { try fileManager.copyItem(at: fatBinary, to: fatBinaryDestination) } catch { fatalError("Could not copy fat binary to framework directory for \(framework): \(error)") } + do { + // The minimum OS version is set to 100.0 to work around b/327020913. + // TODO(ncooke3): Revert this logic once b/327020913 is fixed. + var plistDictionary = try PropertyListSerialization.propertyList( + from: Data(contentsOf: infoPlist), format: nil + ) as! [AnyHashable: Any] + plistDictionary["MinimumOSVersion"] = "100.0" + + let updatedPlistData = try PropertyListSerialization.data( + fromPropertyList: plistDictionary, + format: .xml, + options: 0 + ) + + try updatedPlistData.write(to: infoPlistDestination) + } catch { + fatalError( + "Could not copy framework-level plist to framework directory for \(framework): \(error)" + ) + } // Use the appropriate moduleMaps packageModuleMaps(inFrameworks: [frameworkPath], @@ -678,6 +725,39 @@ struct FrameworkBuilder { return frameworksBuilt } + /// Process privacy manifests. + /// + /// Move any privacy manifest-containing resource bundles into the platform framework. + func processPrivacyManifests(_ fileManager: FileManager, + _ frameworkPath: URL, + _ platformFrameworkDir: URL) { + try? fileManager.contentsOfDirectory( + at: frameworkPath.deletingLastPathComponent(), + includingPropertiesForKeys: nil + ) + .filter { $0.pathExtension == "bundle" } + // TODO(ncooke3): Once the zip is built with Xcode 15, the following + // `filter` can be removed. The following block exists to preserve + // how resources (e.g. like FIAM's) are packaged for use in Xcode 14. + .filter { bundleURL in + let dirEnum = fileManager.enumerator(atPath: bundleURL.path) + var containsPrivacyManifest = false + while let relativeFilePath = dirEnum?.nextObject() as? String { + if relativeFilePath.hasSuffix("PrivacyInfo.xcprivacy") { + containsPrivacyManifest = true + break + } + } + return containsPrivacyManifest + } + // Bundles are moved rather than copied to prevent them from being + // packaged in a `Resources` directory at the root of the xcframework. + .forEach { try! fileManager.moveItem( + at: $0, + to: platformFrameworkDir.appendingPathComponent($0.lastPathComponent) + ) } + } + /// Package the built frameworks into an XCFramework. /// - Parameter withName: The framework name. /// - Parameter frameworks: The grouped frameworks. diff --git a/ReleaseTooling/Sources/ZipBuilder/ModuleMapBuilder.swift b/ReleaseTooling/Sources/ZipBuilder/ModuleMapBuilder.swift index 5dbdc636546..dc781d6a0d4 100755 --- a/ReleaseTooling/Sources/ZipBuilder/ModuleMapBuilder.swift +++ b/ReleaseTooling/Sources/ZipBuilder/ModuleMapBuilder.swift @@ -49,9 +49,9 @@ struct ModuleMapBuilder { if module == "FirebaseFirestoreInternal" { content += """ - link framework "BoringSSL-GRPC" - link framework "gRPC-Core" - link framework "gRPC-C++" + link framework "openssl_grpc" + link framework "grpc" + link framework "grpcpp" """ } diff --git a/ReleaseTooling/Sources/ZipBuilder/ZipBuilder.swift b/ReleaseTooling/Sources/ZipBuilder/ZipBuilder.swift index aeca4a52dc5..ea46bd64085 100644 --- a/ReleaseTooling/Sources/ZipBuilder/ZipBuilder.swift +++ b/ReleaseTooling/Sources/ZipBuilder/ZipBuilder.swift @@ -243,7 +243,11 @@ struct ZipBuilder { podInfo: podInfo) carthageGoogleUtilitiesFrameworks += cdFrameworks } - if resourceContents != nil { + let fileManager = FileManager.default + if let resourceContents, + let contents = try? fileManager.contentsOfDirectory(at: resourceContents, + includingPropertiesForKeys: nil), + !contents.isEmpty { resources[podName] = resourceContents } } else if podsBuilt[podName] == nil { diff --git a/ReleaseTooling/Template/README.md b/ReleaseTooling/Template/README.md index 6b5f261311d..162363e5be7 100644 --- a/ReleaseTooling/Template/README.md +++ b/ReleaseTooling/Template/README.md @@ -34,17 +34,18 @@ To integrate a Firebase SDK with your app: box that appears, make sure the target you want this framework to be added to has a checkmark next to it, and that you've selected "Copy items if needed." - > ⚠ Do not add the Firebase frameworks to the **Embed Frameworks** Xcode build - > phase. The Firebase frameworks are not embedded dynamic frameworks, but are - > [static frameworks](https://www.raywenderlich.com/65964/create-a-framework-for-ios) - > which cannot be embedded into your application's bundle. +7. If using Xcode 15, embed each framework that was dragged in. Navigate to the + target's _General_ settings and find _Frameworks, Libraries, & Embedded + Content_. For each framework dragged in from the `Firebase.zip`, select + **Embed & Sign**. This step will enable privacy manifests to be picked up by + Xcode's tooling. -7. If the SDK has resources, go into the Resources folders, which will be in +8. If the SDK has resources, go into the Resources folders, which will be in the SDK folder. Drag all of those resources into the Project Navigator, just like the frameworks, again making sure that the target you want to add these resources to has a checkmark next to it, and that you've selected "Copy items if needed". -8. Add the `-ObjC` flag to **Other Linker Settings**: +9. Add the `-ObjC` flag to **Other Linker Settings**: a. In your project settings, open the **Settings** panel for your target. @@ -53,13 +54,22 @@ To integrate a Firebase SDK with your app: c. Double-click the setting, click the '+' button, and add `-ObjC` -9. Drag the `Firebase.h` header in this directory into your project. This will +10. Add the `-lc++` flag to **Other Linker Settings**: + + a. In your project settings, open the **Settings** panel for your target. + + b. Go to the Build Settings tab and find the **Other Linker Flags** setting + in the **Linking** section. + + c. Double-click the setting, click the '+' button, and add `-lc++` + +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. -10. 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. -11. 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. @@ -67,7 +77,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**. -12. You're done! Compile 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/SharedTestUtilities/FIRAuthInteropFake.h b/SharedTestUtilities/FIRAuthInteropFake.h index dd5d0d82ab1..ef245e20eda 100644 --- a/SharedTestUtilities/FIRAuthInteropFake.h +++ b/SharedTestUtilities/FIRAuthInteropFake.h @@ -16,7 +16,7 @@ #import -#import "FirebaseAuth/Interop/FIRAuthInterop.h" +#import "FirebaseAuth/Interop/Public/FirebaseAuthInterop/FIRAuthInterop.h" NS_ASSUME_NONNULL_BEGIN diff --git a/SharedTestUtilities/FIRAuthInteropFake.m b/SharedTestUtilities/FIRAuthInteropFake.m index 2b66411bd88..ca7aeab94e1 100644 --- a/SharedTestUtilities/FIRAuthInteropFake.m +++ b/SharedTestUtilities/FIRAuthInteropFake.m @@ -16,7 +16,7 @@ #import "SharedTestUtilities/FIRAuthInteropFake.h" -#import "FirebaseAuth/Interop/FIRAuthInterop.h" +#import "FirebaseAuth/Interop/Public/FirebaseAuthInterop/FIRAuthInterop.h" NS_ASSUME_NONNULL_BEGIN diff --git a/SwiftDashboard.md b/SwiftDashboard.md deleted file mode 100644 index 0484b6dcd91..00000000000 --- a/SwiftDashboard.md +++ /dev/null @@ -1,70 +0,0 @@ -# Firebase Swift Modernization Dashboard - -This dashboard summarizes the status of Firebase's [2022 Swift Modernization Project](ROADMAP.md). -Please upvote or create a [feature request](https://github.com/firebase/firebase-ios-sdk/issues) -to help prioritize any particular cell(s). - -This dashboard is intended to track an initial full Swift review of Firebase along with addressing low-hanging fruit. We would expect it to identify additional follow up -tasks for additional Swift improvements. - -| | An | ApC | ApD | Aut | Cor | Crs | DB | DL | Fst | Fn | IAM | Ins | Msg | MLM | Prf | RC | Str | -| :--- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | -| **Swift Library** | ✅ | ❌ |❌ | ❌ | n/a | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ | ✅ | ✅ | -| **Single Module** | ❌ | ✅ |✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | -| **API Tests** | ✅ | ✅ |❌ | ✅ | ✅ | ❌ | ✅ | ❌ | 1 | ✅ | 1 | ✅ | ✅ | 1 | ❌ | ✅ | ✅ | -| **async/await** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | -| **Swift Errors** | ✅ | ✅ | ✅ | 2 | ✅ | 5 | ❌ | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | 3 | -| **Codable** | n/a | n/a | n/a | n/a | n/a | n/a | ✅ | n/a | ✅ | ✅ | n/a | n/a | n/a | n/a | n/a | ✅ | n/a | -| **SwiftUI Lifecycle** | ❌ | n/a | n/a | ❌ | n/a | n/a | n/a | ❌ | n/a | n/a | n/a | n/a | ❌ | n/a | ❌ | n/a | n/a | -| **SwiftUI Interop** | ✅ | n/a | ❌ | ❌ | n/a | ❌ | ❌ | n/a | ✅ | n/a | ✅ | n/a | n/a | n/a | ❌ | n/a | n/a | -| **Property Wrappers** | n/a | n/a | n/a | ❌ | n/a | n/a | ❌ | n/a | 4 | n/a | n/a | n/a | n/a | n/a | n/a | ✅ | n/a | -| **Swift Doc Scrub** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅| - -### Other Projects -- Tooling to surface full list of automatically generated Swift API from Objective-C and validate. -- Improve singleton naming scheme. Move singletons into a Firebase namespace, like `Firebase.auth()`, `Firebase.storage()`, etc. -- Swift Generics. Update APIs that are using weakly typed information to use proper generics. - -## Notes -1. Tests exist. Coverage to be confirmed. -2. `NS_ERROR_ENUM` used but a larger audit is still needed for more localized errors. -3. Still needs to unify Objective-C and Swift errors. -4. One property wrapper added in [#8614](https://github.com/firebase/firebase-ios-sdk/pull/8614). More to go. -5. `record(Error)` API should be expanded to collect Swift Errors as well as NSErrors. - -## Rows (Swift Capabilities) -* **Swift Library**: SDK includes public APIs written in Swift, either in the main product library or a Swift-specific extension. -* **Single Module**: Public API surface in a single module. -* **API Tests**: Tests exist for all Swift APIs. Integration tests are preferred, but compile-only tests are acceptable. -* **async/await**:API tests include tests for all auto-generated async/await APIs. Implementations are added for -asynchronous APIs that don't have auto-generated counterparts like -[these](https://github.com/firebase/firebase-ios-sdk/blob/main/FirebaseStorage/Tests/Integration/StorageAsyncAwait.swift) -for Storage. -* **Swift Errors**: Swift Error Codes are available instead of NSErrors. -* **Codable**: Codable is implemented where appropriate. -* **SwiftUI Lifecycle**: Dependencies on the AppDelegate Lifecycle are migrated to the Multicast AppDelegate. -* **SwiftUI Interop**: Update APIs that include UIViewControllers (or implementations that depend on them) to work with SwiftUI. This will overlap with -Property Wrappers and likely the SwiftUI lifecycle bits, but an audit and improvements could likely be made. The existing FIAM and Analytics View modifier -APIs would fit into this category. -* **Property Wrappers**: Property wrappers are used to improve the API. -* **Swift Doc Scrub**: Review and update to change Objective-C types and call examples to Swift. In addition to updating the documentation content, we -should also investigate using DocC to format the docs. - -## Columns (Firebase Products) -* An - Analytics -* ApC - App Check -* ApD - App Distribution -* Aut - Auth -* Cor - Core -* Crs - Crashlytics -* DB - Real-time Database -* DL - Dynamic Links -* Fst - Firestore -* Fn - Functions -* IAM - In App Messaging -* Ins - Installations -* Msg - Messaging -* MLM - MLModel Downloader -* Prf - Performance -* RC - Remote Config -* Str - Storage diff --git a/cmake/external/abseil-cpp.cmake b/cmake/external/abseil-cpp.cmake index 8546dbfb875..7fde8522ccf 100644 --- a/cmake/external/abseil-cpp.cmake +++ b/cmake/external/abseil-cpp.cmake @@ -14,6 +14,7 @@ include(ExternalProject) +# Note: When updating to 20230802.0 or later, remove the PATCH_COMMAND below. set(version 20220623.0) ExternalProject_Add( @@ -31,4 +32,6 @@ ExternalProject_Add( INSTALL_COMMAND "" TEST_COMMAND "" HTTP_HEADER "${EXTERNAL_PROJECT_HTTP_HEADER}" + + PATCH_COMMAND patch -Np1 -i ${CMAKE_CURRENT_LIST_DIR}/abseil-cpp.patch.txt ) diff --git a/cmake/external/abseil-cpp.patch.txt b/cmake/external/abseil-cpp.patch.txt new file mode 100644 index 00000000000..1624ee06fab --- /dev/null +++ b/cmake/external/abseil-cpp.patch.txt @@ -0,0 +1,27 @@ +diff --git a/absl/meta/type_traits.h b/absl/meta/type_traits.h +index d886cb30..c2a2d15e 100644 +--- a/absl/meta/type_traits.h ++++ b/absl/meta/type_traits.h +@@ -35,6 +35,12 @@ + #ifndef ABSL_META_TYPE_TRAITS_H_ + #define ABSL_META_TYPE_TRAITS_H_ + ++// Added by firebase-ios-sdk/cmake/external/abseil-cpp.patch.txt ++#if __clang__ ++#pragma clang diagnostic push ++#pragma clang diagnostic ignored "-Wdeprecated" ++#endif // __clang__ ++ + #include + #include + #include +@@ -794,4 +800,9 @@ using swap_internal::StdSwapIsUnconstrained; + ABSL_NAMESPACE_END + } // namespace absl + ++// Added by firebase-ios-sdk/cmake/external/abseil-cpp.patch.txt ++#if __clang__ ++#pragma clang diagnostic pop ++#endif // __clang__ ++ + #endif // ABSL_META_TYPE_TRAITS_H_ diff --git a/scripts/check_firestore_symbols.sh b/scripts/check_firestore_symbols.sh index fe2d41b9b50..5fb2de75acc 100755 --- a/scripts/check_firestore_symbols.sh +++ b/scripts/check_firestore_symbols.sh @@ -153,7 +153,7 @@ nm ~/Library/Developer/Xcode/DerivedData/TestPkg-ObjC/Build/Products/Debug/TestP # return exit code 1, which will cause the set pipefail to terminate execution. # To avoid this, `|| true` ensures the exit code always indicates success. DIFF=$( - git diff --no-index \ + git diff --no-index --output-indicator-new="?" \ objc_symbols_without_linker_flag.txt \ objc_symbols_with_linker_flag.txt \ || true @@ -161,6 +161,9 @@ DIFF=$( if [[ -n "$DIFF" ]]; then echo "Failure: Unlinked Objective-C symbols have been detected:" echo "$DIFF" + echo -n "💡 To fix, follow the process shown in " + echo -n "https://github.com/firebase/firebase-ios-sdk/pull/12534 for the " + echo "above symbols that are prefixed with ?" exit 1 else echo "Success: No unlinked Objective-C symbols have been detected." diff --git a/scripts/health_metrics/README.md b/scripts/health_metrics/README.md index d3f22f25412..829cfeae24c 100644 --- a/scripts/health_metrics/README.md +++ b/scripts/health_metrics/README.md @@ -33,7 +33,7 @@ pod-lib-lint-newsdk: run: scripts/setup_bundler.sh - name: Build and test run: ./scripts/health_metrics/pod_test_code_coverage_report.sh FirebaseNewSDK "${{ matrix.target }}" - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: codecoverage path: /Users/runner/*.xcresult diff --git a/scripts/localize_podfile.swift b/scripts/localize_podfile.swift index f07cb3124a0..8b60a2cbdd5 100755 --- a/scripts/localize_podfile.swift +++ b/scripts/localize_podfile.swift @@ -39,6 +39,7 @@ let implicitPods = [ "FirebaseAppCheckInterop", "FirebaseAuthInterop", "FirebaseMessagingInterop", "FirebaseCoreInternal", "FirebaseSessions", "FirebaseSharedSwift", + "FirebaseRemoteConfigInterop", ] let binaryPods = [ diff --git a/scripts/setup_check.sh b/scripts/setup_check.sh index 057abc47c0b..eb323eb0473 100755 --- a/scripts/setup_check.sh +++ b/scripts/setup_check.sh @@ -35,7 +35,7 @@ fi # install clang-format brew update -brew install clang-format@17 +brew install clang-format@18 # mint installs tools from Mintfile on demand. brew install mint diff --git a/scripts/style.sh b/scripts/style.sh index 58feb1d3da8..e8fcf12f37a 100755 --- a/scripts/style.sh +++ b/scripts/style.sh @@ -56,7 +56,7 @@ version="${version/ (*)/}" version="${version/.*/}" case "$version" in - 17) + 18) ;; google3-trunk) echo "Please use a publicly released clang-format; a recent LLVM release" @@ -65,7 +65,7 @@ case "$version" in exit 1 ;; *) - echo "Please upgrade to clang-format version 17." + echo "Please upgrade to clang-format version 18." echo "If it's installed via homebrew you can run:" echo "brew upgrade clang-format" exit 1 diff --git a/scripts/update_xcode_target.rb b/scripts/update_xcode_target.rb index f4eb2d661bb..f9f58387f90 100755 --- a/scripts/update_xcode_target.rb +++ b/scripts/update_xcode_target.rb @@ -15,7 +15,7 @@ # limitations under the License. # Script to add a file to an Xcode target. -# Adapted from https://github.com/firebase/quickstart-ios/blob/master/scripts/info_script.rb +# Adapted from https://github.com/firebase/quickstart-ios/blob/main/scripts/info_script.rb require 'xcodeproj' project_path = ARGV[0]